diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e5d8caad..811bd636 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,10 +3,11 @@ "isRoot": true, "tools": { "sign": { - "version": "0.9.1-beta.23530.1", + "version": "0.9.1-beta.25379.1", "commands": [ "sign" - ] + ], + "rollForward": false } } } \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0ee6f006..f5b7641d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,7 +20,7 @@ "remoteEnv": { "PATH": "${containerWorkspaceFolder}/.dotnet:${containerEnv:PATH}", "DOTNET_MULTILEVEL_LOOKUP": "0", - "TARGET": "net8.0", + "TARGET": "net10.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 6e6a94e7..54b73bb8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -120,6 +120,13 @@ dotnet_diagnostic.SA1010.severity = none # Opening square brackets should be spa dotnet_diagnostic.ASP0022.severity = none # Route conflict detected between route handlers dotnet_diagnostic.ASP0023.severity = none # Route conflict detected between route handlers +# TEMP: temporary suppression for false positives +# BUG: https://github.com/dotnet/sdk/issues/51681 +# BUG: https://github.com/dotnet/sdk/issues/51716 +[*Extensions.cs] +dotnet_diagnostic.CA1034.severity = none +dotnet_diagnostic.CA1708.severity = none + # test settings # Default severity for analyzer diagnostics with category 'Reliability' diff --git a/.gitattributes b/.gitattributes index 1ff0c423..a8b3f709 100644 --- a/.gitattributes +++ b/.gitattributes @@ -23,6 +23,8 @@ # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary +#*.slnx merge=binary +#*.slnf merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ec3be18c..a20bb427 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v3 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v4 with: queries: security-and-quality languages: csharp @@ -31,7 +31,7 @@ jobs: uses: actions/setup-dotnet@v3 id: installdotnet with: - dotnet-version: 8.0.x + dotnet-version: 10.0.x - name: Create temporary global.json run: echo '{"sdk":{"version":"${{ steps.installdotnet.outputs.dotnet-version }}"}}' > ./global.json @@ -43,7 +43,7 @@ jobs: shell: pwsh run: | $start = (Get-Location).Path.Length + 1 - $sln = Join-Path '..' 'asp.sln' + $sln = Join-Path '..' 'asp.slnx' $projects = Get-ChildItem src -Include src -Recurse | ` ForEach-Object { Get-ChildItem $_.FullName -Filter *.csproj -Recurse } | ` ForEach-Object { $_.FullName.Substring($start) } @@ -54,4 +54,4 @@ jobs: dotnet build $slnf.FullName --configuration Release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 \ No newline at end of file + uses: github/codeql-action/analyze@v4 \ No newline at end of file diff --git a/README.md b/README.md index d6828747..f7cca3a7 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,6 @@ # ASP.NET API Versioning -| :mega: Check out the [announcement](../../discussions/807) regarding upcoming changes | -|-| - The _"Asp"_ project, more formally known as ASP.NET API Versioning, gives you a powerful, but easy-to-use method for diff --git a/asp.sln b/asp.sln deleted file mode 100644 index 0df9e4ed..00000000 --- a/asp.sln +++ /dev/null @@ -1,512 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32014.148 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{A8AC7A1C-66BA-4200-9B92-4E99D7AEAA23}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{65E93433-226D-45F1-B7D9-F55D178A5B08}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitattributes = .gitattributes - .gitignore = .gitignore - azure-pipelines.yml = azure-pipelines.yml - LICENSE.txt = LICENSE.txt - logo.svg = logo.svg - nuget.config = nuget.config - README.md = README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5222A069-BA25-4A6C-9758-C3354C562256}" - ProjectSection(SolutionItems) = preProject - src\Directory.Build.props = src\Directory.Build.props - src\Directory.Build.targets = src\Directory.Build.targets - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Abstractions", "Abstractions", "{7B0FA6C2-47BA-4C34-90E0-B75DF44F2124}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNet", "AspNet", "{34A0373F-12C9-44A8-9A1C-5EEE7218C877}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCore", "AspNetCore", "{EBC9F217-E8BC-4DCE-9C67-F22150959EAF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{EC18BF79-E361-4B71-9B04-45C8DC11F5FF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{60664BE9-1A12-413D-8CB6-37BD779D6C6D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{66270218-B6B8-4A2C-B4C0-60DFFDA8FC1F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Abstractions", "src\Abstractions\src\Asp.Versioning.Abstractions\Asp.Versioning.Abstractions.csproj", "{DA404A7B-E712-4F6A-9994-DCF49347ABF8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Abstractions.Tests", "src\Abstractions\test\Asp.Versioning.Abstractions.Tests\Asp.Versioning.Abstractions.Tests.csproj", "{2BC04731-9816-4B29-923A-3A6B6948F24A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A5449BD2-FE03-4075-9EDC-991312683B39}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0ECDD9F8-D800-4019-AEDA-99E53345707F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2FDCDA3F-290B-4E45-8933-323E9D84E617}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E9ECEC96-1945-45EB-8683-8024B94B3DF2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D5F9C640-7845-45A5-9222-E0A8FD290CE8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{FDFA0229-36E8-4A35-9C2E-8401297BB614}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{E44AA357-A25E-4F0B-A6F6-EF75ED76DCED}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.WebApi", "src\AspNet\WebApi\src\Asp.Versioning.WebApi\Asp.Versioning.WebApi.csproj", "{4D876BCC-62EA-432B-86D9-3802C9F20837}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E0CB0D80-4FCE-412C-9804-071DFA1349E0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{031927C1-BF12-42A9-A91D-6907E8C7F1C7}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common", "src\Common\src\Common\Common.shproj", "{A2DF7CB6-142E-43D0-82C0-47AD5E89F4E3}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.ApiExplorer", "src\Common\src\Common.ApiExplorer\Common.ApiExplorer.shproj", "{1E4B750A-60B7-43A9-9B1A-BC4359EF1AC5}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.Mvc", "src\Common\src\Common.Mvc\Common.Mvc.shproj", "{6629A038-4FF4-45FA-8D32-3A640D831601}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.ProblemDetails", "src\Common\src\Common.ProblemDetails\Common.ProblemDetails.shproj", "{0FA0AA78-4356-4593-854A-E9698D27AB3D}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.TypeInfo", "src\Common\src\Common.TypeInfo\Common.TypeInfo.shproj", "{7A5F3994-0DF5-48B5-AF3D-3F88A1D4EB04}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.WebApi.Tests", "src\AspNet\WebApi\test\Asp.Versioning.WebApi.Tests\Asp.Versioning.WebApi.Tests.csproj", "{F844C122-1F9C-4290-B1AC-829072A5DB77}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.Mvc.Tests", "src\Common\test\Common.Mvc.Tests\Common.Mvc.Tests.shproj", "{E3E486E4-107B-488F-835B-D53A727C2C5E}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.Tests", "src\Common\test\Common.Tests\Common.Tests.shproj", "{FEB58F0F-CFDE-4DA7-9336-AF593E33634F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Mvc.Acceptance.Tests", "src\AspNetCore\Acceptance\Asp.Versioning.Mvc.Acceptance.Tests\Asp.Versioning.Mvc.Acceptance.Tests.csproj", "{0BE9EFAA-3627-46FE-9861-9121EE8F0E26}" -EndProject -Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "assets", "build\assets.msbuildproj", "{4CA99B14-1D6E-4E2A-BB79-6FEAD0B4D95D}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.Acceptance.Tests", "src\Common\test\Common.Acceptance.Tests\Common.Acceptance.Tests.shproj", "{75B0A776-45A2-4167-9D15-145E5352F99F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.WebApi.Acceptance.Tests", "src\AspNet\Acceptance\Asp.Versioning.WebApi.Acceptance.Tests\Asp.Versioning.WebApi.Acceptance.Tests.csproj", "{77070FF4-1F7B-41D2-A06C-E81E985AFD2E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Http.Client", "src\Client\src\Asp.Versioning.Http.Client\Asp.Versioning.Http.Client.csproj", "{B588F124-61E7-4BE5-BDEF-1BF87DBE28A2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Http.Client.Tests", "src\Client\test\Asp.Versioning.Http.Client.Tests\Asp.Versioning.Http.Client.Tests.csproj", "{B612F8D2-E65C-469D-BA93-0D39A3B0C7F5}" -EndProject -Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "Common.Backport", "src\Common\src\Common.Backport\Common.Backport.msbuildproj", "{FEB867DA-09A6-40C1-BDFB-C3FCD8A5F1C2}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.OData", "src\Common\src\Common.OData\Common.OData.shproj", "{1ED0D3EF-16A1-40D1-A3DC-978DF1EB7D3F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.WebApi.ApiExplorer", "src\AspNet\WebApi\src\Asp.Versioning.WebApi.ApiExplorer\Asp.Versioning.WebApi.ApiExplorer.csproj", "{3E60A78C-F6CF-4071-A366-B00FC15082F8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.WebApi.ApiExplorer.Tests", "src\AspNet\WebApi\test\Asp.Versioning.WebApi.ApiExplorer.Tests\Asp.Versioning.WebApi.ApiExplorer.Tests.csproj", "{C73A9C82-5EA2-4985-8181-1F67334AA1EE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.WebApi.OData", "src\AspNet\OData\src\Asp.Versioning.WebApi.OData\Asp.Versioning.WebApi.OData.csproj", "{79C5999C-86BD-4AEF-A018-02D3AE8FA40B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.WebApi.OData.Tests", "src\AspNet\OData\test\Asp.Versioning.WebApi.OData.Tests\Asp.Versioning.WebApi.OData.Tests.csproj", "{130D569D-AE59-43A6-977C-4FE6062AC0BA}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.OData.Tests", "src\Common\test\Common.OData.Tests\Common.OData.Tests.shproj", "{62C25010-2F1D-4146-BDFC-89831D5993D4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.WebApi.OData.ApiExplorer", "src\AspNet\OData\src\Asp.Versioning.WebApi.OData.ApiExplorer\Asp.Versioning.WebApi.OData.ApiExplorer.csproj", "{EF10C285-7950-469D-81A2-2DF5FAB7C613}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.WebApi.OData.ApiExplorer.Tests", "src\AspNet\OData\test\Asp.Versioning.WebApi.OData.ApiExplorer.Tests\Asp.Versioning.WebApi.OData.ApiExplorer.Tests.csproj", "{6164F975-0D7D-44BE-BB8A-1E79E3036A30}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.OData.ApiExplorer", "src\Common\src\Common.OData.ApiExplorer\Common.OData.ApiExplorer.shproj", "{75B059A2-6656-4FFD-AB41-75D272B78E9D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{F969BECF-B849-41A2-BCB3-D2DFE7F481BC}" - ProjectSection(SolutionItems) = preProject - examples\.editorconfig = examples\.editorconfig - examples\Directory.Build.props = examples\Directory.Build.props - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNet", "AspNet", "{20CEA68A-6E76-4285-99B0-52EDC8F9A03C}" - ProjectSection(SolutionItems) = preProject - examples\AspNet\Directory.Build.props = examples\AspNet\Directory.Build.props - examples\AspNet\Startup.Newtonsoft.cs = examples\AspNet\Startup.Newtonsoft.cs - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCore", "AspNetCore", "{4AA0D9F1-F837-430B-9CE2-AAC988E339E4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebApi", "WebApi", "{99187F21-8483-4CAE-A29D-6E1BF5640ABF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OData", "OData", "{7BB01633-6E2C-4837-B618-C7F09B18E99E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebApi", "WebApi", "{E0E64F6F-FB0C-4534-B815-2217700B50BA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OData", "OData", "{49EA6476-901C-4D4F-8E45-98BC8A2780EB}" - ProjectSection(SolutionItems) = preProject - examples\AspNetCore\OData\Directory.Build.props = examples\AspNetCore\OData\Directory.Build.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicWebApiExample", "examples\AspNet\WebApi\BasicWebApiExample\BasicWebApiExample.csproj", "{B0457E07-161A-4ED0-949A-8CF7EFA765F5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ByNamespaceWebApiExample", "examples\AspNet\WebApi\ByNamespaceWebApiExample\ByNamespaceWebApiExample.csproj", "{2251CB7E-E92A-4500-8DF1-F4D3217C312B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConventionsWebApiExample", "examples\AspNet\WebApi\ConventionsWebApiExample\ConventionsWebApiExample.csproj", "{2F38E2CD-86A5-443B-8177-5ED81837FCC7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApiWebApiExample", "examples\AspNet\WebApi\OpenApiWebApiExample\OpenApiWebApiExample.csproj", "{93270D63-34D6-4949-80AF-EF7583F48BEF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdvancedODataWebApiExample", "examples\AspNet\OData\AdvancedODataWebApiExample\AdvancedODataWebApiExample.csproj", "{5CD1CDBA-936F-47D3-A49E-C62562A0B67B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicODataWebApiExample", "examples\AspNet\OData\BasicODataWebApiExample\BasicODataWebApiExample.csproj", "{D2AD06B9-68AE-4173-8041-014A63876C9D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConventionsODataWebApiExample", "examples\AspNet\OData\ConventionsODataWebApiExample\ConventionsODataWebApiExample.csproj", "{87359D05-8370-4B9B-A7CC-F8F036E93BA5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApiODataWebApiExample", "examples\AspNet\OData\OpenApiODataWebApiExample\OpenApiODataWebApiExample.csproj", "{AEA4D951-1772-42DE-BC58-338120B594CB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicExample", "examples\AspNetCore\WebApi\BasicExample\BasicExample.csproj", "{D7A41A07-5A60-4A4D-AE91-82E48F0457BF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ByNamespaceExample", "examples\AspNetCore\WebApi\ByNamespaceExample\ByNamespaceExample.csproj", "{7B44BF89-34D3-4EE8-90ED-30DC7609BDC7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConventionsExample", "examples\AspNetCore\WebApi\ConventionsExample\ConventionsExample.csproj", "{689F3993-46AC-49B7-93B7-429A1CD7665E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApiExample", "examples\AspNetCore\WebApi\OpenApiExample\OpenApiExample.csproj", "{C025AFA3-5855-48EB-8D62-B6C899F3E1FE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalApiExample", "examples\AspNetCore\WebApi\MinimalApiExample\MinimalApiExample.csproj", "{4299ED9F-39DB-4AD8-B89D-CF73489FC0C2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.OData.ApiExplorer", "src\AspNetCore\OData\src\Asp.Versioning.OData.ApiExplorer\Asp.Versioning.OData.ApiExplorer.csproj", "{C7E8692D-E286-4C41-A49E-0BE65AEB3AA5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.OData", "src\AspNetCore\OData\src\Asp.Versioning.OData\Asp.Versioning.OData.csproj", "{C7581EFD-32F9-4ADC-AFA6-3EC9DA8AA628}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Http", "src\AspNetCore\WebApi\src\Asp.Versioning.Http\Asp.Versioning.Http.csproj", "{71AB5EA3-6E24-4C45-B3D9-38E935437686}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Mvc", "src\AspNetCore\WebApi\src\Asp.Versioning.Mvc\Asp.Versioning.Mvc.csproj", "{2FAA780D-BC3A-4FB3-BA3C-A21226ABA7CD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Mvc.ApiExplorer", "src\AspNetCore\WebApi\src\Asp.Versioning.Mvc.ApiExplorer\Asp.Versioning.Mvc.ApiExplorer.csproj", "{15BB5197-EF0D-4B30-954F-344EEB33C46E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Http.Tests", "src\AspNetCore\WebApi\test\Asp.Versioning.Http.Tests\Asp.Versioning.Http.Tests.csproj", "{91FBDDED-442B-4FF7-B096-10305BFE1899}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Mvc.Tests", "src\AspNetCore\WebApi\test\Asp.Versioning.Mvc.Tests\Asp.Versioning.Mvc.Tests.csproj", "{17445B9A-9D8D-46C2-A5CF-2AB6449DF187}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.Mvc.ApiExplorer.Tests", "src\AspNetCore\WebApi\test\Asp.Versioning.Mvc.ApiExplorer.Tests\Asp.Versioning.Mvc.ApiExplorer.Tests.csproj", "{75B82139-821A-4CC2-99C1-27D988271E43}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.OData.Tests", "src\AspNetCore\OData\test\Asp.Versioning.OData.Tests\Asp.Versioning.OData.Tests.csproj", "{FA4DA84B-9E9E-4BA9-BF96-5547B33DD1BB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asp.Versioning.OData.ApiExplorer.Tests", "src\AspNetCore\OData\test\Asp.Versioning.OData.ApiExplorer.Tests\Asp.Versioning.OData.ApiExplorer.Tests.csproj", "{897167D5-3959-45AD-837C-9ED0B5CD9213}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ODataBasicExample", "examples\AspNetCore\OData\ODataBasicExample\ODataBasicExample.csproj", "{2119A7FA-0E3E-4F9D-9B4C-CFEF7CCF7698}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ODataConventionsExample", "examples\AspNetCore\OData\ODataConventionsExample\ODataConventionsExample.csproj", "{BCFF37D7-0D4D-4D8D-A4C2-7EC70AD3F7D4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ODataAdvancedExample", "examples\AspNetCore\OData\ODataAdvancedExample\ODataAdvancedExample.csproj", "{BEF5739D-7E87-4D2A-B3E8-958CBFE16DE0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ODataOpenApiExample", "examples\AspNetCore\OData\ODataOpenApiExample\ODataOpenApiExample.csproj", "{B39C3FE5-227F-4403-B246-1277906ACF7D}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.OData.ApiExplorer.Tests", "src\Common\test\Common.OData.ApiExplorer.Tests\Common.OData.ApiExplorer.Tests.shproj", "{496A5B79-AFD2-45AC-AF9A-1CD28A7E1CDB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalOpenApiExample", "examples\AspNetCore\WebApi\MinimalOpenApiExample\MinimalOpenApiExample.csproj", "{124C18D1-F72A-4380-AE40-E7511AC16C62}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SomeODataOpenApiExample", "examples\AspNetCore\OData\SomeODataOpenApiExample\SomeODataOpenApiExample.csproj", "{94A9AF81-A7BE-4E6C-81B1-8BFF4B6E1B78}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SomeOpenApiODataWebApiExample", "examples\AspNet\OData\SomeOpenApiODataWebApiExample\SomeOpenApiODataWebApiExample.csproj", "{AC952FBF-D7DC-4DE4-AD1C-4CEA589034F5}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DA404A7B-E712-4F6A-9994-DCF49347ABF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DA404A7B-E712-4F6A-9994-DCF49347ABF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DA404A7B-E712-4F6A-9994-DCF49347ABF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DA404A7B-E712-4F6A-9994-DCF49347ABF8}.Release|Any CPU.Build.0 = Release|Any CPU - {2BC04731-9816-4B29-923A-3A6B6948F24A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2BC04731-9816-4B29-923A-3A6B6948F24A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BC04731-9816-4B29-923A-3A6B6948F24A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2BC04731-9816-4B29-923A-3A6B6948F24A}.Release|Any CPU.Build.0 = Release|Any CPU - {4D876BCC-62EA-432B-86D9-3802C9F20837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D876BCC-62EA-432B-86D9-3802C9F20837}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D876BCC-62EA-432B-86D9-3802C9F20837}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D876BCC-62EA-432B-86D9-3802C9F20837}.Release|Any CPU.Build.0 = Release|Any CPU - {F844C122-1F9C-4290-B1AC-829072A5DB77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F844C122-1F9C-4290-B1AC-829072A5DB77}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F844C122-1F9C-4290-B1AC-829072A5DB77}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F844C122-1F9C-4290-B1AC-829072A5DB77}.Release|Any CPU.Build.0 = Release|Any CPU - {0BE9EFAA-3627-46FE-9861-9121EE8F0E26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BE9EFAA-3627-46FE-9861-9121EE8F0E26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BE9EFAA-3627-46FE-9861-9121EE8F0E26}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BE9EFAA-3627-46FE-9861-9121EE8F0E26}.Release|Any CPU.Build.0 = Release|Any CPU - {4CA99B14-1D6E-4E2A-BB79-6FEAD0B4D95D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4CA99B14-1D6E-4E2A-BB79-6FEAD0B4D95D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4CA99B14-1D6E-4E2A-BB79-6FEAD0B4D95D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4CA99B14-1D6E-4E2A-BB79-6FEAD0B4D95D}.Release|Any CPU.Build.0 = Release|Any CPU - {77070FF4-1F7B-41D2-A06C-E81E985AFD2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77070FF4-1F7B-41D2-A06C-E81E985AFD2E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77070FF4-1F7B-41D2-A06C-E81E985AFD2E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77070FF4-1F7B-41D2-A06C-E81E985AFD2E}.Release|Any CPU.Build.0 = Release|Any CPU - {B588F124-61E7-4BE5-BDEF-1BF87DBE28A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B588F124-61E7-4BE5-BDEF-1BF87DBE28A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B588F124-61E7-4BE5-BDEF-1BF87DBE28A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B588F124-61E7-4BE5-BDEF-1BF87DBE28A2}.Release|Any CPU.Build.0 = Release|Any CPU - {B612F8D2-E65C-469D-BA93-0D39A3B0C7F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B612F8D2-E65C-469D-BA93-0D39A3B0C7F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B612F8D2-E65C-469D-BA93-0D39A3B0C7F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B612F8D2-E65C-469D-BA93-0D39A3B0C7F5}.Release|Any CPU.Build.0 = Release|Any CPU - {FEB867DA-09A6-40C1-BDFB-C3FCD8A5F1C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FEB867DA-09A6-40C1-BDFB-C3FCD8A5F1C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FEB867DA-09A6-40C1-BDFB-C3FCD8A5F1C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FEB867DA-09A6-40C1-BDFB-C3FCD8A5F1C2}.Release|Any CPU.Build.0 = Release|Any CPU - {3E60A78C-F6CF-4071-A366-B00FC15082F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3E60A78C-F6CF-4071-A366-B00FC15082F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3E60A78C-F6CF-4071-A366-B00FC15082F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3E60A78C-F6CF-4071-A366-B00FC15082F8}.Release|Any CPU.Build.0 = Release|Any CPU - {C73A9C82-5EA2-4985-8181-1F67334AA1EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C73A9C82-5EA2-4985-8181-1F67334AA1EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C73A9C82-5EA2-4985-8181-1F67334AA1EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C73A9C82-5EA2-4985-8181-1F67334AA1EE}.Release|Any CPU.Build.0 = Release|Any CPU - {79C5999C-86BD-4AEF-A018-02D3AE8FA40B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79C5999C-86BD-4AEF-A018-02D3AE8FA40B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79C5999C-86BD-4AEF-A018-02D3AE8FA40B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79C5999C-86BD-4AEF-A018-02D3AE8FA40B}.Release|Any CPU.Build.0 = Release|Any CPU - {130D569D-AE59-43A6-977C-4FE6062AC0BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {130D569D-AE59-43A6-977C-4FE6062AC0BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {130D569D-AE59-43A6-977C-4FE6062AC0BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {130D569D-AE59-43A6-977C-4FE6062AC0BA}.Release|Any CPU.Build.0 = Release|Any CPU - {EF10C285-7950-469D-81A2-2DF5FAB7C613}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EF10C285-7950-469D-81A2-2DF5FAB7C613}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF10C285-7950-469D-81A2-2DF5FAB7C613}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EF10C285-7950-469D-81A2-2DF5FAB7C613}.Release|Any CPU.Build.0 = Release|Any CPU - {6164F975-0D7D-44BE-BB8A-1E79E3036A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6164F975-0D7D-44BE-BB8A-1E79E3036A30}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6164F975-0D7D-44BE-BB8A-1E79E3036A30}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6164F975-0D7D-44BE-BB8A-1E79E3036A30}.Release|Any CPU.Build.0 = Release|Any CPU - {B0457E07-161A-4ED0-949A-8CF7EFA765F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B0457E07-161A-4ED0-949A-8CF7EFA765F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B0457E07-161A-4ED0-949A-8CF7EFA765F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B0457E07-161A-4ED0-949A-8CF7EFA765F5}.Release|Any CPU.Build.0 = Release|Any CPU - {2251CB7E-E92A-4500-8DF1-F4D3217C312B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2251CB7E-E92A-4500-8DF1-F4D3217C312B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2251CB7E-E92A-4500-8DF1-F4D3217C312B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2251CB7E-E92A-4500-8DF1-F4D3217C312B}.Release|Any CPU.Build.0 = Release|Any CPU - {2F38E2CD-86A5-443B-8177-5ED81837FCC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F38E2CD-86A5-443B-8177-5ED81837FCC7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F38E2CD-86A5-443B-8177-5ED81837FCC7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F38E2CD-86A5-443B-8177-5ED81837FCC7}.Release|Any CPU.Build.0 = Release|Any CPU - {93270D63-34D6-4949-80AF-EF7583F48BEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {93270D63-34D6-4949-80AF-EF7583F48BEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93270D63-34D6-4949-80AF-EF7583F48BEF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {93270D63-34D6-4949-80AF-EF7583F48BEF}.Release|Any CPU.Build.0 = Release|Any CPU - {5CD1CDBA-936F-47D3-A49E-C62562A0B67B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5CD1CDBA-936F-47D3-A49E-C62562A0B67B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CD1CDBA-936F-47D3-A49E-C62562A0B67B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5CD1CDBA-936F-47D3-A49E-C62562A0B67B}.Release|Any CPU.Build.0 = Release|Any CPU - {D2AD06B9-68AE-4173-8041-014A63876C9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D2AD06B9-68AE-4173-8041-014A63876C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D2AD06B9-68AE-4173-8041-014A63876C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D2AD06B9-68AE-4173-8041-014A63876C9D}.Release|Any CPU.Build.0 = Release|Any CPU - {87359D05-8370-4B9B-A7CC-F8F036E93BA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87359D05-8370-4B9B-A7CC-F8F036E93BA5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87359D05-8370-4B9B-A7CC-F8F036E93BA5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87359D05-8370-4B9B-A7CC-F8F036E93BA5}.Release|Any CPU.Build.0 = Release|Any CPU - {AEA4D951-1772-42DE-BC58-338120B594CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AEA4D951-1772-42DE-BC58-338120B594CB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AEA4D951-1772-42DE-BC58-338120B594CB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AEA4D951-1772-42DE-BC58-338120B594CB}.Release|Any CPU.Build.0 = Release|Any CPU - {D7A41A07-5A60-4A4D-AE91-82E48F0457BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7A41A07-5A60-4A4D-AE91-82E48F0457BF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7A41A07-5A60-4A4D-AE91-82E48F0457BF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D7A41A07-5A60-4A4D-AE91-82E48F0457BF}.Release|Any CPU.Build.0 = Release|Any CPU - {7B44BF89-34D3-4EE8-90ED-30DC7609BDC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7B44BF89-34D3-4EE8-90ED-30DC7609BDC7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B44BF89-34D3-4EE8-90ED-30DC7609BDC7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7B44BF89-34D3-4EE8-90ED-30DC7609BDC7}.Release|Any CPU.Build.0 = Release|Any CPU - {689F3993-46AC-49B7-93B7-429A1CD7665E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {689F3993-46AC-49B7-93B7-429A1CD7665E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {689F3993-46AC-49B7-93B7-429A1CD7665E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {689F3993-46AC-49B7-93B7-429A1CD7665E}.Release|Any CPU.Build.0 = Release|Any CPU - {C025AFA3-5855-48EB-8D62-B6C899F3E1FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C025AFA3-5855-48EB-8D62-B6C899F3E1FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C025AFA3-5855-48EB-8D62-B6C899F3E1FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C025AFA3-5855-48EB-8D62-B6C899F3E1FE}.Release|Any CPU.Build.0 = Release|Any CPU - {4299ED9F-39DB-4AD8-B89D-CF73489FC0C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4299ED9F-39DB-4AD8-B89D-CF73489FC0C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4299ED9F-39DB-4AD8-B89D-CF73489FC0C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4299ED9F-39DB-4AD8-B89D-CF73489FC0C2}.Release|Any CPU.Build.0 = Release|Any CPU - {C7E8692D-E286-4C41-A49E-0BE65AEB3AA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C7E8692D-E286-4C41-A49E-0BE65AEB3AA5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C7E8692D-E286-4C41-A49E-0BE65AEB3AA5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C7E8692D-E286-4C41-A49E-0BE65AEB3AA5}.Release|Any CPU.Build.0 = Release|Any CPU - {C7581EFD-32F9-4ADC-AFA6-3EC9DA8AA628}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C7581EFD-32F9-4ADC-AFA6-3EC9DA8AA628}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C7581EFD-32F9-4ADC-AFA6-3EC9DA8AA628}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C7581EFD-32F9-4ADC-AFA6-3EC9DA8AA628}.Release|Any CPU.Build.0 = Release|Any CPU - {71AB5EA3-6E24-4C45-B3D9-38E935437686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71AB5EA3-6E24-4C45-B3D9-38E935437686}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71AB5EA3-6E24-4C45-B3D9-38E935437686}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71AB5EA3-6E24-4C45-B3D9-38E935437686}.Release|Any CPU.Build.0 = Release|Any CPU - {2FAA780D-BC3A-4FB3-BA3C-A21226ABA7CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2FAA780D-BC3A-4FB3-BA3C-A21226ABA7CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2FAA780D-BC3A-4FB3-BA3C-A21226ABA7CD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2FAA780D-BC3A-4FB3-BA3C-A21226ABA7CD}.Release|Any CPU.Build.0 = Release|Any CPU - {15BB5197-EF0D-4B30-954F-344EEB33C46E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15BB5197-EF0D-4B30-954F-344EEB33C46E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15BB5197-EF0D-4B30-954F-344EEB33C46E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15BB5197-EF0D-4B30-954F-344EEB33C46E}.Release|Any CPU.Build.0 = Release|Any CPU - {91FBDDED-442B-4FF7-B096-10305BFE1899}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91FBDDED-442B-4FF7-B096-10305BFE1899}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91FBDDED-442B-4FF7-B096-10305BFE1899}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91FBDDED-442B-4FF7-B096-10305BFE1899}.Release|Any CPU.Build.0 = Release|Any CPU - {17445B9A-9D8D-46C2-A5CF-2AB6449DF187}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17445B9A-9D8D-46C2-A5CF-2AB6449DF187}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17445B9A-9D8D-46C2-A5CF-2AB6449DF187}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17445B9A-9D8D-46C2-A5CF-2AB6449DF187}.Release|Any CPU.Build.0 = Release|Any CPU - {75B82139-821A-4CC2-99C1-27D988271E43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {75B82139-821A-4CC2-99C1-27D988271E43}.Debug|Any CPU.Build.0 = Debug|Any CPU - {75B82139-821A-4CC2-99C1-27D988271E43}.Release|Any CPU.ActiveCfg = Release|Any CPU - {75B82139-821A-4CC2-99C1-27D988271E43}.Release|Any CPU.Build.0 = Release|Any CPU - {FA4DA84B-9E9E-4BA9-BF96-5547B33DD1BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA4DA84B-9E9E-4BA9-BF96-5547B33DD1BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA4DA84B-9E9E-4BA9-BF96-5547B33DD1BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA4DA84B-9E9E-4BA9-BF96-5547B33DD1BB}.Release|Any CPU.Build.0 = Release|Any CPU - {897167D5-3959-45AD-837C-9ED0B5CD9213}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {897167D5-3959-45AD-837C-9ED0B5CD9213}.Debug|Any CPU.Build.0 = Debug|Any CPU - {897167D5-3959-45AD-837C-9ED0B5CD9213}.Release|Any CPU.ActiveCfg = Release|Any CPU - {897167D5-3959-45AD-837C-9ED0B5CD9213}.Release|Any CPU.Build.0 = Release|Any CPU - {2119A7FA-0E3E-4F9D-9B4C-CFEF7CCF7698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2119A7FA-0E3E-4F9D-9B4C-CFEF7CCF7698}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2119A7FA-0E3E-4F9D-9B4C-CFEF7CCF7698}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2119A7FA-0E3E-4F9D-9B4C-CFEF7CCF7698}.Release|Any CPU.Build.0 = Release|Any CPU - {BCFF37D7-0D4D-4D8D-A4C2-7EC70AD3F7D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BCFF37D7-0D4D-4D8D-A4C2-7EC70AD3F7D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BCFF37D7-0D4D-4D8D-A4C2-7EC70AD3F7D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BCFF37D7-0D4D-4D8D-A4C2-7EC70AD3F7D4}.Release|Any CPU.Build.0 = Release|Any CPU - {BEF5739D-7E87-4D2A-B3E8-958CBFE16DE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BEF5739D-7E87-4D2A-B3E8-958CBFE16DE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BEF5739D-7E87-4D2A-B3E8-958CBFE16DE0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BEF5739D-7E87-4D2A-B3E8-958CBFE16DE0}.Release|Any CPU.Build.0 = Release|Any CPU - {B39C3FE5-227F-4403-B246-1277906ACF7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B39C3FE5-227F-4403-B246-1277906ACF7D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B39C3FE5-227F-4403-B246-1277906ACF7D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B39C3FE5-227F-4403-B246-1277906ACF7D}.Release|Any CPU.Build.0 = Release|Any CPU - {124C18D1-F72A-4380-AE40-E7511AC16C62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {124C18D1-F72A-4380-AE40-E7511AC16C62}.Debug|Any CPU.Build.0 = Debug|Any CPU - {124C18D1-F72A-4380-AE40-E7511AC16C62}.Release|Any CPU.ActiveCfg = Release|Any CPU - {124C18D1-F72A-4380-AE40-E7511AC16C62}.Release|Any CPU.Build.0 = Release|Any CPU - {94A9AF81-A7BE-4E6C-81B1-8BFF4B6E1B78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94A9AF81-A7BE-4E6C-81B1-8BFF4B6E1B78}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94A9AF81-A7BE-4E6C-81B1-8BFF4B6E1B78}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94A9AF81-A7BE-4E6C-81B1-8BFF4B6E1B78}.Release|Any CPU.Build.0 = Release|Any CPU - {AC952FBF-D7DC-4DE4-AD1C-4CEA589034F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AC952FBF-D7DC-4DE4-AD1C-4CEA589034F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AC952FBF-D7DC-4DE4-AD1C-4CEA589034F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AC952FBF-D7DC-4DE4-AD1C-4CEA589034F5}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {7B0FA6C2-47BA-4C34-90E0-B75DF44F2124} = {5222A069-BA25-4A6C-9758-C3354C562256} - {34A0373F-12C9-44A8-9A1C-5EEE7218C877} = {5222A069-BA25-4A6C-9758-C3354C562256} - {EBC9F217-E8BC-4DCE-9C67-F22150959EAF} = {5222A069-BA25-4A6C-9758-C3354C562256} - {EC18BF79-E361-4B71-9B04-45C8DC11F5FF} = {5222A069-BA25-4A6C-9758-C3354C562256} - {60664BE9-1A12-413D-8CB6-37BD779D6C6D} = {7B0FA6C2-47BA-4C34-90E0-B75DF44F2124} - {66270218-B6B8-4A2C-B4C0-60DFFDA8FC1F} = {7B0FA6C2-47BA-4C34-90E0-B75DF44F2124} - {DA404A7B-E712-4F6A-9994-DCF49347ABF8} = {60664BE9-1A12-413D-8CB6-37BD779D6C6D} - {2BC04731-9816-4B29-923A-3A6B6948F24A} = {66270218-B6B8-4A2C-B4C0-60DFFDA8FC1F} - {A5449BD2-FE03-4075-9EDC-991312683B39} = {34A0373F-12C9-44A8-9A1C-5EEE7218C877} - {0ECDD9F8-D800-4019-AEDA-99E53345707F} = {34A0373F-12C9-44A8-9A1C-5EEE7218C877} - {2FDCDA3F-290B-4E45-8933-323E9D84E617} = {EBC9F217-E8BC-4DCE-9C67-F22150959EAF} - {E9ECEC96-1945-45EB-8683-8024B94B3DF2} = {EBC9F217-E8BC-4DCE-9C67-F22150959EAF} - {D5F9C640-7845-45A5-9222-E0A8FD290CE8} = {EC18BF79-E361-4B71-9B04-45C8DC11F5FF} - {FDFA0229-36E8-4A35-9C2E-8401297BB614} = {EC18BF79-E361-4B71-9B04-45C8DC11F5FF} - {E44AA357-A25E-4F0B-A6F6-EF75ED76DCED} = {5222A069-BA25-4A6C-9758-C3354C562256} - {4D876BCC-62EA-432B-86D9-3802C9F20837} = {A5449BD2-FE03-4075-9EDC-991312683B39} - {E0CB0D80-4FCE-412C-9804-071DFA1349E0} = {E44AA357-A25E-4F0B-A6F6-EF75ED76DCED} - {031927C1-BF12-42A9-A91D-6907E8C7F1C7} = {E44AA357-A25E-4F0B-A6F6-EF75ED76DCED} - {A2DF7CB6-142E-43D0-82C0-47AD5E89F4E3} = {E0CB0D80-4FCE-412C-9804-071DFA1349E0} - {1E4B750A-60B7-43A9-9B1A-BC4359EF1AC5} = {E0CB0D80-4FCE-412C-9804-071DFA1349E0} - {6629A038-4FF4-45FA-8D32-3A640D831601} = {E0CB0D80-4FCE-412C-9804-071DFA1349E0} - {0FA0AA78-4356-4593-854A-E9698D27AB3D} = {E0CB0D80-4FCE-412C-9804-071DFA1349E0} - {7A5F3994-0DF5-48B5-AF3D-3F88A1D4EB04} = {E0CB0D80-4FCE-412C-9804-071DFA1349E0} - {F844C122-1F9C-4290-B1AC-829072A5DB77} = {0ECDD9F8-D800-4019-AEDA-99E53345707F} - {E3E486E4-107B-488F-835B-D53A727C2C5E} = {031927C1-BF12-42A9-A91D-6907E8C7F1C7} - {FEB58F0F-CFDE-4DA7-9336-AF593E33634F} = {031927C1-BF12-42A9-A91D-6907E8C7F1C7} - {0BE9EFAA-3627-46FE-9861-9121EE8F0E26} = {E9ECEC96-1945-45EB-8683-8024B94B3DF2} - {4CA99B14-1D6E-4E2A-BB79-6FEAD0B4D95D} = {A8AC7A1C-66BA-4200-9B92-4E99D7AEAA23} - {75B0A776-45A2-4167-9D15-145E5352F99F} = {031927C1-BF12-42A9-A91D-6907E8C7F1C7} - {77070FF4-1F7B-41D2-A06C-E81E985AFD2E} = {0ECDD9F8-D800-4019-AEDA-99E53345707F} - {B588F124-61E7-4BE5-BDEF-1BF87DBE28A2} = {D5F9C640-7845-45A5-9222-E0A8FD290CE8} - {B612F8D2-E65C-469D-BA93-0D39A3B0C7F5} = {FDFA0229-36E8-4A35-9C2E-8401297BB614} - {FEB867DA-09A6-40C1-BDFB-C3FCD8A5F1C2} = {E0CB0D80-4FCE-412C-9804-071DFA1349E0} - {1ED0D3EF-16A1-40D1-A3DC-978DF1EB7D3F} = {E0CB0D80-4FCE-412C-9804-071DFA1349E0} - {3E60A78C-F6CF-4071-A366-B00FC15082F8} = {A5449BD2-FE03-4075-9EDC-991312683B39} - {C73A9C82-5EA2-4985-8181-1F67334AA1EE} = {0ECDD9F8-D800-4019-AEDA-99E53345707F} - {79C5999C-86BD-4AEF-A018-02D3AE8FA40B} = {A5449BD2-FE03-4075-9EDC-991312683B39} - {130D569D-AE59-43A6-977C-4FE6062AC0BA} = {0ECDD9F8-D800-4019-AEDA-99E53345707F} - {62C25010-2F1D-4146-BDFC-89831D5993D4} = {031927C1-BF12-42A9-A91D-6907E8C7F1C7} - {EF10C285-7950-469D-81A2-2DF5FAB7C613} = {A5449BD2-FE03-4075-9EDC-991312683B39} - {6164F975-0D7D-44BE-BB8A-1E79E3036A30} = {0ECDD9F8-D800-4019-AEDA-99E53345707F} - {75B059A2-6656-4FFD-AB41-75D272B78E9D} = {E0CB0D80-4FCE-412C-9804-071DFA1349E0} - {20CEA68A-6E76-4285-99B0-52EDC8F9A03C} = {F969BECF-B849-41A2-BCB3-D2DFE7F481BC} - {4AA0D9F1-F837-430B-9CE2-AAC988E339E4} = {F969BECF-B849-41A2-BCB3-D2DFE7F481BC} - {99187F21-8483-4CAE-A29D-6E1BF5640ABF} = {20CEA68A-6E76-4285-99B0-52EDC8F9A03C} - {7BB01633-6E2C-4837-B618-C7F09B18E99E} = {20CEA68A-6E76-4285-99B0-52EDC8F9A03C} - {E0E64F6F-FB0C-4534-B815-2217700B50BA} = {4AA0D9F1-F837-430B-9CE2-AAC988E339E4} - {49EA6476-901C-4D4F-8E45-98BC8A2780EB} = {4AA0D9F1-F837-430B-9CE2-AAC988E339E4} - {B0457E07-161A-4ED0-949A-8CF7EFA765F5} = {99187F21-8483-4CAE-A29D-6E1BF5640ABF} - {2251CB7E-E92A-4500-8DF1-F4D3217C312B} = {99187F21-8483-4CAE-A29D-6E1BF5640ABF} - {2F38E2CD-86A5-443B-8177-5ED81837FCC7} = {99187F21-8483-4CAE-A29D-6E1BF5640ABF} - {93270D63-34D6-4949-80AF-EF7583F48BEF} = {99187F21-8483-4CAE-A29D-6E1BF5640ABF} - {5CD1CDBA-936F-47D3-A49E-C62562A0B67B} = {7BB01633-6E2C-4837-B618-C7F09B18E99E} - {D2AD06B9-68AE-4173-8041-014A63876C9D} = {7BB01633-6E2C-4837-B618-C7F09B18E99E} - {87359D05-8370-4B9B-A7CC-F8F036E93BA5} = {7BB01633-6E2C-4837-B618-C7F09B18E99E} - {AEA4D951-1772-42DE-BC58-338120B594CB} = {7BB01633-6E2C-4837-B618-C7F09B18E99E} - {D7A41A07-5A60-4A4D-AE91-82E48F0457BF} = {E0E64F6F-FB0C-4534-B815-2217700B50BA} - {7B44BF89-34D3-4EE8-90ED-30DC7609BDC7} = {E0E64F6F-FB0C-4534-B815-2217700B50BA} - {689F3993-46AC-49B7-93B7-429A1CD7665E} = {E0E64F6F-FB0C-4534-B815-2217700B50BA} - {C025AFA3-5855-48EB-8D62-B6C899F3E1FE} = {E0E64F6F-FB0C-4534-B815-2217700B50BA} - {4299ED9F-39DB-4AD8-B89D-CF73489FC0C2} = {E0E64F6F-FB0C-4534-B815-2217700B50BA} - {C7E8692D-E286-4C41-A49E-0BE65AEB3AA5} = {2FDCDA3F-290B-4E45-8933-323E9D84E617} - {C7581EFD-32F9-4ADC-AFA6-3EC9DA8AA628} = {2FDCDA3F-290B-4E45-8933-323E9D84E617} - {71AB5EA3-6E24-4C45-B3D9-38E935437686} = {2FDCDA3F-290B-4E45-8933-323E9D84E617} - {2FAA780D-BC3A-4FB3-BA3C-A21226ABA7CD} = {2FDCDA3F-290B-4E45-8933-323E9D84E617} - {15BB5197-EF0D-4B30-954F-344EEB33C46E} = {2FDCDA3F-290B-4E45-8933-323E9D84E617} - {91FBDDED-442B-4FF7-B096-10305BFE1899} = {E9ECEC96-1945-45EB-8683-8024B94B3DF2} - {17445B9A-9D8D-46C2-A5CF-2AB6449DF187} = {E9ECEC96-1945-45EB-8683-8024B94B3DF2} - {75B82139-821A-4CC2-99C1-27D988271E43} = {E9ECEC96-1945-45EB-8683-8024B94B3DF2} - {FA4DA84B-9E9E-4BA9-BF96-5547B33DD1BB} = {E9ECEC96-1945-45EB-8683-8024B94B3DF2} - {897167D5-3959-45AD-837C-9ED0B5CD9213} = {E9ECEC96-1945-45EB-8683-8024B94B3DF2} - {2119A7FA-0E3E-4F9D-9B4C-CFEF7CCF7698} = {49EA6476-901C-4D4F-8E45-98BC8A2780EB} - {BCFF37D7-0D4D-4D8D-A4C2-7EC70AD3F7D4} = {49EA6476-901C-4D4F-8E45-98BC8A2780EB} - {BEF5739D-7E87-4D2A-B3E8-958CBFE16DE0} = {49EA6476-901C-4D4F-8E45-98BC8A2780EB} - {B39C3FE5-227F-4403-B246-1277906ACF7D} = {49EA6476-901C-4D4F-8E45-98BC8A2780EB} - {496A5B79-AFD2-45AC-AF9A-1CD28A7E1CDB} = {031927C1-BF12-42A9-A91D-6907E8C7F1C7} - {124C18D1-F72A-4380-AE40-E7511AC16C62} = {E0E64F6F-FB0C-4534-B815-2217700B50BA} - {94A9AF81-A7BE-4E6C-81B1-8BFF4B6E1B78} = {49EA6476-901C-4D4F-8E45-98BC8A2780EB} - {AC952FBF-D7DC-4DE4-AD1C-4CEA589034F5} = {7BB01633-6E2C-4837-B618-C7F09B18E99E} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {91FE116A-CEFB-4304-A8A6-CFF021C7453A} - EndGlobalSection - GlobalSection(SharedMSBuildProjectFiles) = preSolution - src\Common\test\Common.Acceptance.Tests\Common.Acceptance.Tests.projitems*{0be9efaa-3627-46fe-9861-9121ee8f0e26}*SharedItemsImports = 5 - src\Common\src\Common.ProblemDetails\Common.ProblemDetails.projitems*{0fa0aa78-4356-4593-854a-e9698d27ab3d}*SharedItemsImports = 13 - src\Common\test\Common.OData.Tests\Common.OData.Tests.projitems*{130d569d-ae59-43a6-977c-4fe6062ac0ba}*SharedItemsImports = 5 - src\Common\src\Common.ApiExplorer\Common.ApiExplorer.projitems*{15bb5197-ef0d-4b30-954f-344eeb33c46e}*SharedItemsImports = 5 - src\Common\test\Common.Mvc.Tests\Common.Mvc.Tests.projitems*{17445b9a-9d8d-46c2-a5cf-2ab6449df187}*SharedItemsImports = 5 - src\Common\src\Common.ApiExplorer\Common.ApiExplorer.projitems*{1e4b750a-60b7-43a9-9b1a-bc4359ef1ac5}*SharedItemsImports = 13 - src\Common\src\Common.OData\Common.OData.projitems*{1ed0d3ef-16a1-40d1-a3dc-978df1eb7d3f}*SharedItemsImports = 13 - src\Common\src\Common.Mvc\Common.Mvc.projitems*{2faa780d-bc3a-4fb3-ba3c-a21226aba7cd}*SharedItemsImports = 5 - src\Common\src\Common.ApiExplorer\Common.ApiExplorer.projitems*{3e60a78c-f6cf-4071-a366-b00fc15082f8}*SharedItemsImports = 5 - src\Common\test\Common.OData.ApiExplorer.Tests\Common.OData.ApiExplorer.Tests.projitems*{496a5b79-afd2-45ac-af9a-1cd28a7e1cdb}*SharedItemsImports = 13 - src\Common\src\Common.Mvc\Common.Mvc.projitems*{4d876bcc-62ea-432b-86d9-3802c9f20837}*SharedItemsImports = 5 - src\Common\src\Common.ProblemDetails\Common.ProblemDetails.projitems*{4d876bcc-62ea-432b-86d9-3802c9f20837}*SharedItemsImports = 5 - src\Common\src\Common.TypeInfo\Common.TypeInfo.projitems*{4d876bcc-62ea-432b-86d9-3802c9f20837}*SharedItemsImports = 5 - src\Common\src\Common\Common.projitems*{4d876bcc-62ea-432b-86d9-3802c9f20837}*SharedItemsImports = 5 - src\Common\test\Common.OData.ApiExplorer.Tests\Common.OData.ApiExplorer.Tests.projitems*{6164f975-0d7d-44be-bb8a-1e79e3036a30}*SharedItemsImports = 5 - src\Common\test\Common.OData.Tests\Common.OData.Tests.projitems*{62c25010-2f1d-4146-bdfc-89831d5993d4}*SharedItemsImports = 13 - src\Common\src\Common.Mvc\Common.Mvc.projitems*{6629a038-4ff4-45fa-8d32-3a640d831601}*SharedItemsImports = 13 - src\Common\src\Common.ProblemDetails\Common.ProblemDetails.projitems*{71ab5ea3-6e24-4c45-b3d9-38e935437686}*SharedItemsImports = 5 - src\Common\src\Common\Common.projitems*{71ab5ea3-6e24-4c45-b3d9-38e935437686}*SharedItemsImports = 5 - src\Common\src\Common.OData.ApiExplorer\Common.OData.ApiExplorer.projitems*{75b059a2-6656-4ffd-ab41-75d272b78e9d}*SharedItemsImports = 13 - src\Common\test\Common.Acceptance.Tests\Common.Acceptance.Tests.projitems*{75b0a776-45a2-4167-9d15-145e5352f99f}*SharedItemsImports = 13 - src\Common\test\Common.Acceptance.Tests\Common.Acceptance.Tests.projitems*{77070ff4-1f7b-41d2-a06c-e81e985afd2e}*SharedItemsImports = 5 - src\Common\src\Common.OData\Common.OData.projitems*{79c5999c-86bd-4aef-a018-02d3ae8fa40b}*SharedItemsImports = 5 - src\Common\src\Common.TypeInfo\Common.TypeInfo.projitems*{7a5f3994-0df5-48b5-af3d-3f88a1d4eb04}*SharedItemsImports = 13 - src\Common\test\Common.OData.ApiExplorer.Tests\Common.OData.ApiExplorer.Tests.projitems*{897167d5-3959-45ad-837c-9ed0b5cd9213}*SharedItemsImports = 5 - src\Common\test\Common.Tests\Common.Tests.projitems*{91fbdded-442b-4ff7-b096-10305bfe1899}*SharedItemsImports = 5 - src\Common\src\Common\Common.projitems*{a2df7cb6-142e-43d0-82c0-47ad5e89f4e3}*SharedItemsImports = 13 - src\Common\src\Common.OData\Common.OData.projitems*{c7581efd-32f9-4adc-afa6-3ec9da8aa628}*SharedItemsImports = 5 - src\Common\src\Common.OData.ApiExplorer\Common.OData.ApiExplorer.projitems*{c7e8692d-e286-4c41-a49e-0be65aeb3aa5}*SharedItemsImports = 5 - src\Common\test\Common.Mvc.Tests\Common.Mvc.Tests.projitems*{e3e486e4-107b-488f-835b-d53a727c2c5e}*SharedItemsImports = 13 - src\Common\src\Common.OData.ApiExplorer\Common.OData.ApiExplorer.projitems*{ef10c285-7950-469d-81a2-2df5fab7c613}*SharedItemsImports = 5 - src\Common\test\Common.Mvc.Tests\Common.Mvc.Tests.projitems*{f844c122-1f9c-4290-b1ac-829072a5db77}*SharedItemsImports = 5 - src\Common\test\Common.Tests\Common.Tests.projitems*{f844c122-1f9c-4290-b1ac-829072a5db77}*SharedItemsImports = 5 - src\Common\test\Common.OData.Tests\Common.OData.Tests.projitems*{fa4da84b-9e9e-4ba9-bf96-5547b33dd1bb}*SharedItemsImports = 5 - src\Common\test\Common.Tests\Common.Tests.projitems*{feb58f0f-cfde-4da7-9336-af593e33634f}*SharedItemsImports = 13 - EndGlobalSection -EndGlobal diff --git a/asp.slnx b/asp.slnx new file mode 100644 index 00000000..0ce4a2ce --- /dev/null +++ b/asp.slnx @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/nuget.props b/build/nuget.props index f679d6d3..066bd7bf 100644 --- a/build/nuget.props +++ b/build/nuget.props @@ -36,7 +36,7 @@ - + diff --git a/build/steps-ci.yml b/build/steps-ci.yml index c2304d7d..2e249c3d 100644 --- a/build/steps-ci.yml +++ b/build/steps-ci.yml @@ -11,7 +11,7 @@ steps: displayName: Install .NET SDK inputs: packageType: sdk - version: 8.0.x # https://github.com/dotnet/core/blob/main/release-notes/releases-index.json + version: 10.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/test.props b/build/test.props index 38dde87c..d33a80b5 100644 --- a/build/test.props +++ b/build/test.props @@ -2,9 +2,13 @@ + Exe false disable false + $(NoWarn);CA1515 + true + true acceptance. diff --git a/build/test.targets b/build/test.targets index a3fc5fea..3fe42046 100644 --- a/build/test.targets +++ b/build/test.targets @@ -1,40 +1,24 @@ - - 6.8.0 - - - 4.20.69 - 2.5.0 - - - - 5.10.3 - 4.17.2 - 2.4.3 - - - - - - - + + + + + - + - + + \ No newline at end of file diff --git a/examples/AspNet/Directory.Build.props b/examples/AspNet/Directory.Build.props index 1c79dda3..7b79f5c1 100644 --- a/examples/AspNet/Directory.Build.props +++ b/examples/AspNet/Directory.Build.props @@ -8,7 +8,7 @@ - + diff --git a/examples/AspNet/OData/AdvancedODataWebApiExample/Controllers/OrdersController.cs b/examples/AspNet/OData/AdvancedODataWebApiExample/Controllers/OrdersController.cs index 21c53244..fb0e877d 100644 --- a/examples/AspNet/OData/AdvancedODataWebApiExample/Controllers/OrdersController.cs +++ b/examples/AspNet/OData/AdvancedODataWebApiExample/Controllers/OrdersController.cs @@ -10,7 +10,7 @@ public class OrdersController : ApiController { // GET ~/api/orders // GET ~/api/orders?api-version=1.0 - public IHttpActionResult Get( ApiVersion version ) => + public IHttpActionResult Get( ApiVersion version ) => Ok( new[] { new Order() { Id = 1, Customer = $"Customer v{version}" } } ); // GET ~/api/orders/{id} diff --git a/examples/AspNet/OData/AdvancedODataWebApiExample/Controllers/PeopleController.cs b/examples/AspNet/OData/AdvancedODataWebApiExample/Controllers/PeopleController.cs index 8f14b292..182b0365 100644 --- a/examples/AspNet/OData/AdvancedODataWebApiExample/Controllers/PeopleController.cs +++ b/examples/AspNet/OData/AdvancedODataWebApiExample/Controllers/PeopleController.cs @@ -35,32 +35,32 @@ public IHttpActionResult Get( ODataQueryOptions options, ApiVersion vers [ODataRoute( "{id}" )] public IHttpActionResult Get( int id, ODataQueryOptions options, ApiVersion version ) => Ok( new Person() - { - Id = id, - FirstName = "Bill", - LastName = "Mei", - Email = "bill.mei@somewhere.com", + { + Id = id, + FirstName = "Bill", + LastName = "Mei", + Email = "bill.mei@somewhere.com", Phone = "555-555-5555", } ); // PATCH ~/api/people/{id}?api-version=2.0 [MapToApiVersion( "2.0" )] [ODataRoute( "{id}" )] - public IHttpActionResult Patch( - int id, - Delta delta, - ODataQueryOptions options, + public IHttpActionResult Patch( + int id, + Delta delta, + ODataQueryOptions options, ApiVersion version ) { if ( !ModelState.IsValid ) return BadRequest( ModelState ); var person = new Person() - { - Id = id, - FirstName = "Bill", - LastName = "Mei", - Email = "bill.mei@somewhere.com", + { + Id = id, + FirstName = "Bill", + LastName = "Mei", + Email = "bill.mei@somewhere.com", Phone = "555-555-5555", }; diff --git a/examples/AspNet/OData/BasicODataWebApiExample/Controllers/PeopleController.cs b/examples/AspNet/OData/BasicODataWebApiExample/Controllers/PeopleController.cs index 8d12d57b..925cadef 100644 --- a/examples/AspNet/OData/BasicODataWebApiExample/Controllers/PeopleController.cs +++ b/examples/AspNet/OData/BasicODataWebApiExample/Controllers/PeopleController.cs @@ -16,9 +16,9 @@ public class PeopleController : ODataController [ODataRoute] public IHttpActionResult Get( ODataQueryOptions options ) => Ok( new Person[] - { + { new() - { + { Id = 1, FirstName = "Bill", LastName = "Mei", @@ -30,11 +30,11 @@ public IHttpActionResult Get( ODataQueryOptions options ) => // GET ~/api/people/{id}?api-version=[1.0|2.0] [ODataRoute( "{id}" )] public IHttpActionResult Get( int id, ODataQueryOptions options ) => - Ok( new Person() - { - Id = id, - FirstName = "Bill", - LastName = "Mei", + Ok( new Person() + { + Id = id, + FirstName = "Bill", + LastName = "Mei", Email = "bill.mei@somewhere.com", Phone = "555-555-5555", } ); @@ -50,11 +50,11 @@ public IHttpActionResult Patch( int id, Delta delta, ODataQueryOptions

{ - // reporting api versions will return the headers - // "api-supported-versions" and "api-deprecated-versions" - options.ReportApiVersions = true; + // reporting api versions will return the headers + // "api-supported-versions" and "api-deprecated-versions" + options.ReportApiVersions = true; - // apply api versions using conventions rather than attributes - options.Conventions.Controller() - .HasApiVersion( 1, 0 ); + // apply api versions using conventions rather than attributes + options.Conventions.Controller() + .HasApiVersion( 1, 0 ); options.Conventions.Controller() .HasApiVersion( 1, 0 ) diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/Models/Order.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/Models/Order.cs index e3f15cc4..24fecb81 100644 --- a/examples/AspNet/OData/OpenApiODataWebApiExample/Models/Order.cs +++ b/examples/AspNet/OData/OpenApiODataWebApiExample/Models/Order.cs @@ -48,5 +48,5 @@ public class Order /// /// The list of order line items. [Contained] - public virtual IList LineItems { get; } = new List(); + public virtual IList LineItems { get; } = []; } \ No newline at end of file diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/Startup.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/Startup.cs index 2577d01c..1138f8ff 100644 --- a/examples/AspNet/OData/OpenApiODataWebApiExample/Startup.cs +++ b/examples/AspNet/OData/OpenApiODataWebApiExample/Startup.cs @@ -37,10 +37,16 @@ public void Configuration( IAppBuilder builder ) // "api-supported-versions" and "api-deprecated-versions" options.ReportApiVersions = true; + options.Policies.Deprecate( 0.9 ) + .Effective( DateTimeOffset.Now ) + .Link( "policy.html" ) + .Title( "Version Deprecation Policy" ) + .Type( "text/html" ); + options.Policies.Sunset( 0.9 ) .Effective( DateTimeOffset.Now.AddDays( 60 ) ) .Link( "policy.html" ) - .Title( "Versioning Policy" ) + .Title( "Version Sunset Policy" ) .Type( "text/html" ); } ); @@ -107,70 +113,98 @@ public void Configuration( IAppBuilder builder ) { // build a swagger document and endpoint for each discovered API version swagger.MultipleApiVersions( - ( apiDescription, version ) => apiDescription.GetGroupName() == version, + ( apiDescription, version ) => apiDescription.GroupName == version, info => { foreach ( var group in apiExplorer.ApiDescriptions ) { var description = new StringBuilder( "A sample application with OData, OpenAPI, Swashbuckle, and API versioning." ); + var links = new List(); if ( group.IsDeprecated ) { - description.Append( " This API version has been deprecated." ); + description.Append( " The API " ); + + if ( group.DeprecationPolicy?.Date is { } when ) + { + description.Append( when < DateTimeOffset.Now ? "will be" : "was" ) + .Append( " deprecated on " ) + .Append( when.Date.ToShortDateString() ); + } + else + { + description.Append( "has been deprecated" ); + } + + description.Append( '.' ); + + if ( group.DeprecationPolicy is { } deprecation && deprecation.HasLinks ) + { + links.AddRange( deprecation.Links ); + } } - if ( group.SunsetPolicy is { } policy ) + if ( group.SunsetPolicy is { } sunset ) { - if ( policy.Date is { } when ) + if ( sunset.Date is { } when ) { - description.Append( " The API will be sunset on " ) + description.Append( " The API " ) + .Append( when < DateTimeOffset.Now ? "will be" : "was" ) + .Append( " sunset on " ) .Append( when.Date.ToShortDateString() ) .Append( '.' ); } - if ( policy.HasLinks ) + if ( sunset.HasLinks ) { - description.AppendLine(); + links.AddRange( sunset.Links ); + } + } - var rendered = false; + description.AppendLine(); - for ( var i = 0; i < policy.Links.Count; i++ ) + if ( links.Count > 0 ) + { + var rendered = false; + + for ( var i = 0; i < links.Count; i++ ) + { + var link = links[i]; + + if ( link.Type != "text/html" ) + { + continue; + } + + if ( !rendered ) { - var link = policy.Links[i]; + description.Append( "

" ); + description.Append( "**Links**" ); + description.AppendLine( "
" ); + rendered = true; + } - if ( link.Type == "text/html" ) + if ( StringSegment.IsNullOrEmpty( link.Title ) ) + { + if ( link.LinkTarget.IsAbsoluteUri ) + { + description.AppendLine( $"- {link.LinkTarget.OriginalString}" ); + } + else { - if ( !rendered ) - { - description.AppendLine(); - description.Append( "**Links**" ); - description.AppendLine(); - rendered = true; - } - - 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.AppendFormat( "- {0}", link.LinkTarget.OriginalString ); + description.AppendLine(); } } + else + { + description.AppendLine( $"- [{link.Title}]({link.LinkTarget.OriginalString})" ); + } } } - description.AppendLine(); - description.AppendLine( "**Additional Information**" ); + description.AppendLine().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/SwaggerDefaultValues.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/SwaggerDefaultValues.cs index 2ce56dcf..a26c8316 100644 --- a/examples/AspNet/OData/OpenApiODataWebApiExample/SwaggerDefaultValues.cs +++ b/examples/AspNet/OData/OpenApiODataWebApiExample/SwaggerDefaultValues.cs @@ -18,7 +18,7 @@ public class SwaggerDefaultValues : IOperationFilter /// The API description being filtered. public void Apply( Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription ) { - operation.deprecated |= apiDescription.IsDeprecated(); + operation.deprecated |= apiDescription.IsDeprecated; if ( operation.parameters == null ) { diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/V1/OrdersController.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/V1/OrdersController.cs index 8d42c15f..fe16d1ef 100644 --- a/examples/AspNet/OData/OpenApiODataWebApiExample/V1/OrdersController.cs +++ b/examples/AspNet/OData/OpenApiODataWebApiExample/V1/OrdersController.cs @@ -66,7 +66,7 @@ public IHttpActionResult Post( [FromBody] Order order ) [MapToApiVersion( "1.0" )] [ResponseType( typeof( Order ) )] [EnableQuery( AllowedQueryOptions = Select )] - public SingleResult MostExpensive() => + public SingleResult MostExpensive() => SingleResult.Create( new[] { new Order() { Id = 42, Customer = "Bill Mei" } }.AsQueryable() ); ///

diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/V1/PeopleController.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/V1/PeopleController.cs index 5d5993fa..f1a59f23 100644 --- a/examples/AspNet/OData/OpenApiODataWebApiExample/V1/PeopleController.cs +++ b/examples/AspNet/OData/OpenApiODataWebApiExample/V1/PeopleController.cs @@ -51,7 +51,7 @@ public IHttpActionResult Get( int key, ODataQueryOptions options ) [MapToApiVersion( "1.0" )] [ResponseType( typeof( Person ) )] [EnableQuery( AllowedQueryOptions = Select )] - public SingleResult MostExpensive( ODataQueryOptions options, CancellationToken ct ) => + public SingleResult MostExpensive( ODataQueryOptions options, CancellationToken ct ) => SingleResult.Create( new[] { new Person() { Id = 42, FirstName = "Elon", LastName = "Musk" } }.AsQueryable() ); /// diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/V2/OrdersController.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/V2/OrdersController.cs index ab55e697..e026a628 100644 --- a/examples/AspNet/OData/OpenApiODataWebApiExample/V2/OrdersController.cs +++ b/examples/AspNet/OData/OpenApiODataWebApiExample/V2/OrdersController.cs @@ -49,7 +49,7 @@ public IQueryable Get() [ODataRoute( "{key}" )] [ResponseType( typeof( Order ) )] [EnableQuery( AllowedQueryOptions = Select )] - public SingleResult Get( int key ) => + public SingleResult Get( int key ) => SingleResult.Create( new[] { new Order() { Id = key, Customer = "John Doe" } }.AsQueryable() ); /// diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/V3/AcmeController.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/V3/AcmeController.cs index b32facee..d588daca 100644 --- a/examples/AspNet/OData/OpenApiODataWebApiExample/V3/AcmeController.cs +++ b/examples/AspNet/OData/OpenApiODataWebApiExample/V3/AcmeController.cs @@ -58,8 +58,8 @@ private static Supplier NewSupplier() => { Id = 42, Name = "Acme", - Products = new List() - { + Products = + [ new() { Id = 42, @@ -68,6 +68,6 @@ private static Supplier NewSupplier() => Price = 42, SupplierId = 42, }, - }, + ], }; } \ No newline at end of file diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/V3/ProductsController.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/V3/ProductsController.cs index 4b438140..0f10e211 100644 --- a/examples/AspNet/OData/OpenApiODataWebApiExample/V3/ProductsController.cs +++ b/examples/AspNet/OData/OpenApiODataWebApiExample/V3/ProductsController.cs @@ -37,7 +37,7 @@ public class ProductsController : ODataController /// The product does not exist. [EnableQuery] [ResponseType( typeof( Product ) )] - public SingleResult Get( [FromODataUri] int key ) => + public SingleResult Get( [FromODataUri] int key ) => SingleResult.Create( products.Where( p => p.Id == key ) ); /// diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/V3/SuppliersController.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/V3/SuppliersController.cs index af837960..0a01a65f 100644 --- a/examples/AspNet/OData/OpenApiODataWebApiExample/V3/SuppliersController.cs +++ b/examples/AspNet/OData/OpenApiODataWebApiExample/V3/SuppliersController.cs @@ -156,8 +156,8 @@ private static Supplier NewSupplier( int id ) => { Id = id, Name = "Supplier " + id.ToString(), - Products = new List() - { + Products = + [ new() { Id = id, @@ -166,6 +166,6 @@ private static Supplier NewSupplier( int id ) => Price = id, SupplierId = id, }, - }, + ], }; } \ No newline at end of file diff --git a/examples/AspNet/OData/SomeOpenApiODataWebApiExample/Startup.cs b/examples/AspNet/OData/SomeOpenApiODataWebApiExample/Startup.cs index 1ac7209a..3de7899f 100644 --- a/examples/AspNet/OData/SomeOpenApiODataWebApiExample/Startup.cs +++ b/examples/AspNet/OData/SomeOpenApiODataWebApiExample/Startup.cs @@ -80,70 +80,17 @@ public void Configuration( IAppBuilder builder ) { // build a swagger document and endpoint for each discovered API version swagger.MultipleApiVersions( - ( apiDescription, version ) => apiDescription.GetGroupName() == version, + ( apiDescription, version ) => apiDescription.GroupName == version, info => { foreach ( var group in apiExplorer.ApiDescriptions ) { - var description = new StringBuilder( "A sample application with some OData, OpenAPI, Swashbuckle, and API versioning." ); - - if ( group.IsDeprecated ) - { - description.Append( " This API version has been deprecated." ); - } - - if ( group.SunsetPolicy is { } policy ) - { - if ( policy.Date is { } when ) - { - description.Append( " The API will be sunset on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - - if ( policy.HasLinks ) - { - description.AppendLine(); - - var rendered = false; - - for ( var i = 0; i < policy.Links.Count; i++ ) - { - var link = policy.Links[i]; - - if ( link.Type == "text/html" ) - { - if ( !rendered ) - { - description.AppendLine(); - description.Append( "**Links**" ); - description.AppendLine(); - rendered = true; - } - - 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**" ); + var description = new StringBuilder( "A sample application with some OData, OpenAPI, Swashbuckle, and API versioning." ) + .AppendLine() + .AppendLine( "

" ) + .AppendLine( "**Additional Information**" ) + .AppendLine( "
" ); + 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/SomeOpenApiODataWebApiExample/SwaggerDefaultValues.cs b/examples/AspNet/OData/SomeOpenApiODataWebApiExample/SwaggerDefaultValues.cs index 2ce56dcf..a26c8316 100644 --- a/examples/AspNet/OData/SomeOpenApiODataWebApiExample/SwaggerDefaultValues.cs +++ b/examples/AspNet/OData/SomeOpenApiODataWebApiExample/SwaggerDefaultValues.cs @@ -18,7 +18,7 @@ public class SwaggerDefaultValues : IOperationFilter /// The API description being filtered. public void Apply( Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription ) { - operation.deprecated |= apiDescription.IsDeprecated(); + operation.deprecated |= apiDescription.IsDeprecated; if ( operation.parameters == null ) { diff --git a/examples/AspNet/WebApi/BasicWebApiExample/Controllers/Values2Controller.cs b/examples/AspNet/WebApi/BasicWebApiExample/Controllers/Values2Controller.cs index ef7aa0ba..b390d4d8 100644 --- a/examples/AspNet/WebApi/BasicWebApiExample/Controllers/Values2Controller.cs +++ b/examples/AspNet/WebApi/BasicWebApiExample/Controllers/Values2Controller.cs @@ -13,6 +13,6 @@ public IHttpActionResult Get() => Ok( new { controller = GetType().Name, - version = Request.GetRequestedApiVersion().ToString(), + version = Request.RequestedApiVersion.ToString(), } ); } \ No newline at end of file diff --git a/examples/AspNet/WebApi/BasicWebApiExample/Controllers/ValuesController.cs b/examples/AspNet/WebApi/BasicWebApiExample/Controllers/ValuesController.cs index 3d46c489..2ca558b8 100644 --- a/examples/AspNet/WebApi/BasicWebApiExample/Controllers/ValuesController.cs +++ b/examples/AspNet/WebApi/BasicWebApiExample/Controllers/ValuesController.cs @@ -10,8 +10,8 @@ public class ValuesController : ApiController // GET api/values?api-version=1.0 public IHttpActionResult Get( ApiVersion apiVersion ) => Ok( new - { - controller = GetType().Name, + { + controller = GetType().Name, version = apiVersion.ToString(), } ); } \ No newline at end of file diff --git a/examples/AspNet/WebApi/BasicWebApiExample/Startup.cs b/examples/AspNet/WebApi/BasicWebApiExample/Startup.cs index 05130216..547e46a6 100644 --- a/examples/AspNet/WebApi/BasicWebApiExample/Startup.cs +++ b/examples/AspNet/WebApi/BasicWebApiExample/Startup.cs @@ -12,7 +12,7 @@ public void Configuration( IAppBuilder builder ) // we only need to change the default constraint resolver for services // that want urls with versioning like: ~/v{apiVersion}/{controller} var constraintResolver = new DefaultInlineConstraintResolver() - { + { ConstraintMap = { ["apiVersion"] = typeof( ApiVersionRouteConstraint ) }, }; var configuration = new HttpConfiguration(); diff --git a/examples/AspNet/WebApi/ByNamespaceWebApiExample/Startup.cs b/examples/AspNet/WebApi/ByNamespaceWebApiExample/Startup.cs index d5d67f53..6775b288 100644 --- a/examples/AspNet/WebApi/ByNamespaceWebApiExample/Startup.cs +++ b/examples/AspNet/WebApi/ByNamespaceWebApiExample/Startup.cs @@ -15,13 +15,13 @@ public void Configuration( IAppBuilder builder ) configuration.AddApiVersioning( options => { - // reporting api versions will return the headers - // "api-supported-versions" and "api-deprecated-versions" - options.ReportApiVersions = true; + // reporting api versions will return the headers + // "api-supported-versions" and "api-deprecated-versions" + options.ReportApiVersions = true; - // automatically applies an api version based on the name of - // the defining controller's namespace - options.Conventions.Add( new VersionByNamespaceConvention() ); + // automatically applies an api version based on the name of + // the defining controller's namespace + options.Conventions.Add( new VersionByNamespaceConvention() ); } ); // NOTE: you do NOT and should NOT use both the query string and url segment methods together. diff --git a/examples/AspNet/WebApi/ConventionsWebApiExample/Controllers/Values2Controller.cs b/examples/AspNet/WebApi/ConventionsWebApiExample/Controllers/Values2Controller.cs index 1d700c5c..f6fc6ad1 100644 --- a/examples/AspNet/WebApi/ConventionsWebApiExample/Controllers/Values2Controller.cs +++ b/examples/AspNet/WebApi/ConventionsWebApiExample/Controllers/Values2Controller.cs @@ -12,7 +12,7 @@ public IHttpActionResult Get() => Ok( new { controller = GetType().Name, - version = Request.GetRequestedApiVersion().ToString(), + version = Request.RequestedApiVersion.ToString(), } ); // GET api/values/{id}?api-version=2.0 @@ -22,7 +22,7 @@ public IHttpActionResult Get( int id ) => { controller = GetType().Name, id, - version = Request.GetRequestedApiVersion().ToString(), + version = Request.RequestedApiVersion.ToString(), } ); // GET api/values?api-version=3.0 @@ -31,7 +31,7 @@ public IHttpActionResult GetV3() => Ok( new { controller = GetType().Name, - version = Request.GetRequestedApiVersion().ToString(), + version = Request.RequestedApiVersion.ToString(), } ); // GET api/values/{id}?api-version=3.0 @@ -41,6 +41,6 @@ public IHttpActionResult GetV3( int id ) => { controller = GetType().Name, id, - version = Request.GetRequestedApiVersion().ToString(), + version = Request.RequestedApiVersion.ToString(), } ); } \ No newline at end of file diff --git a/examples/AspNet/WebApi/OpenApiWebApiExample/Startup.cs b/examples/AspNet/WebApi/OpenApiWebApiExample/Startup.cs index 2a060f11..fb06af3a 100644 --- a/examples/AspNet/WebApi/OpenApiWebApiExample/Startup.cs +++ b/examples/AspNet/WebApi/OpenApiWebApiExample/Startup.cs @@ -37,10 +37,16 @@ public void Configuration( IAppBuilder builder ) // "api-supported-versions" and "api-deprecated-versions" options.ReportApiVersions = true; + options.Policies.Deprecate( 0.9 ) + .Effective( DateTimeOffset.Now ) + .Link( "policy.html" ) + .Title( "Version Deprecation Policy" ) + .Type( "text/html" ); + options.Policies.Sunset( 0.9 ) .Effective( DateTimeOffset.Now.AddDays( 60 ) ) .Link( "policy.html" ) - .Title( "Versioning Policy" ) + .Title( "Version Sunset Policy" ) .Type( "text/html" ); } ); configuration.MapHttpAttributeRoutes( constraintResolver ); @@ -64,70 +70,98 @@ public void Configuration( IAppBuilder builder ) { // build a swagger document and endpoint for each discovered API version swagger.MultipleApiVersions( - ( apiDescription, version ) => apiDescription.GetGroupName() == version, + ( apiDescription, version ) => apiDescription.GroupName == version, info => { foreach ( var group in apiExplorer.ApiDescriptions ) { var description = new StringBuilder( "A sample application with OpenAPI, Swashbuckle, and API versioning." ); + var links = new List(); if ( group.IsDeprecated ) { - description.Append( " This API version has been deprecated." ); + description.Append( " The API " ); + + if ( group.DeprecationPolicy?.Date is { } when ) + { + description.Append( when < DateTimeOffset.Now ? "will be" : "was" ) + .Append( " deprecated on " ) + .Append( when.Date.ToShortDateString() ); + } + else + { + description.Append( "has been deprecated" ); + } + + description.Append( '.' ); + + if ( group.DeprecationPolicy is { } deprecation && deprecation.HasLinks ) + { + links.AddRange( deprecation.Links ); + } } - if ( group.SunsetPolicy is { } policy ) + if ( group.SunsetPolicy is { } sunset ) { - if ( policy.Date is { } when ) + if ( sunset.Date is { } when ) { - description.Append( " The API will be sunset on " ) + description.Append( " The API " ) + .Append( when < DateTimeOffset.Now ? "will be" : "was" ) + .Append( " sunset on " ) .Append( when.Date.ToShortDateString() ) .Append( '.' ); } - if ( policy.HasLinks ) + if ( sunset.HasLinks ) { - description.AppendLine(); + links.AddRange( sunset.Links ); + } + } - var rendered = false; + description.AppendLine(); - for ( var i = 0; i < policy.Links.Count; i++ ) + if ( links.Count > 0 ) + { + var rendered = false; + + for ( var i = 0; i < links.Count; i++ ) + { + var link = links[i]; + + if ( link.Type != "text/html" ) + { + continue; + } + + if ( !rendered ) { - var link = policy.Links[i]; + description.Append( "

" ); + description.Append( "**Links**" ); + description.AppendLine( "
" ); + rendered = true; + } - if ( link.Type == "text/html" ) + if ( StringSegment.IsNullOrEmpty( link.Title ) ) + { + if ( link.LinkTarget.IsAbsoluteUri ) + { + description.AppendLine( $"- {link.LinkTarget.OriginalString}" ); + } + else { - if ( !rendered ) - { - description.AppendLine(); - description.Append( "**Links**" ); - description.AppendLine(); - rendered = true; - } - - 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.AppendFormat( "- {0}", link.LinkTarget.OriginalString ); + description.AppendLine(); } } + else + { + description.AppendLine( $"- [{link.Title}]({link.LinkTarget.OriginalString})" ); + } } } - description.AppendLine(); - description.AppendLine( "**Additional Information**" ); + description.AppendLine().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/AspNet/WebApi/OpenApiWebApiExample/SwaggerDefaultValues.cs b/examples/AspNet/WebApi/OpenApiWebApiExample/SwaggerDefaultValues.cs index dc26ff66..3819f01b 100644 --- a/examples/AspNet/WebApi/OpenApiWebApiExample/SwaggerDefaultValues.cs +++ b/examples/AspNet/WebApi/OpenApiWebApiExample/SwaggerDefaultValues.cs @@ -18,7 +18,7 @@ public class SwaggerDefaultValues : IOperationFilter /// The API description being filtered. public void Apply( Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription ) { - operation.deprecated |= apiDescription.IsDeprecated(); + operation.deprecated |= apiDescription.IsDeprecated; if ( operation.parameters == null ) { diff --git a/examples/AspNetCore/OData/Directory.Build.props b/examples/AspNetCore/OData/Directory.Build.props index 0c6cf639..d75382b4 100644 --- a/examples/AspNetCore/OData/Directory.Build.props +++ b/examples/AspNetCore/OData/Directory.Build.props @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataAdvancedExample/Controllers/Orders3Controller.cs b/examples/AspNetCore/OData/ODataAdvancedExample/Controllers/Orders3Controller.cs index 0d4b0745..04cf3454 100644 --- a/examples/AspNetCore/OData/ODataAdvancedExample/Controllers/Orders3Controller.cs +++ b/examples/AspNetCore/OData/ODataAdvancedExample/Controllers/Orders3Controller.cs @@ -12,11 +12,11 @@ public class Orders3Controller : ControllerBase { // GET ~/api/orders?api-version=3.0 [HttpGet] - public IActionResult Get( ApiVersion version ) => + public IActionResult Get( ApiVersion version ) => Ok( new[] { new Order() { Id = 1, Customer = $"Customer v{version}" } } ); // GET ~/api/orders/{id}?api-version=3.0 [HttpGet( "{id}" )] - public IActionResult Get( int id, ApiVersion version ) => + public IActionResult Get( int id, ApiVersion version ) => Ok( new Order() { Id = id, Customer = $"Customer v{version}" } ); } \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataAdvancedExample/ODataAdvancedExample.csproj b/examples/AspNetCore/OData/ODataAdvancedExample/ODataAdvancedExample.csproj index ecccb3a9..45e10dc9 100644 --- a/examples/AspNetCore/OData/ODataAdvancedExample/ODataAdvancedExample.csproj +++ b/examples/AspNetCore/OData/ODataAdvancedExample/ODataAdvancedExample.csproj @@ -1,5 +1,9 @@  + + net10.0 + + diff --git a/examples/AspNetCore/OData/ODataBasicExample/ODataBasicExample.csproj b/examples/AspNetCore/OData/ODataBasicExample/ODataBasicExample.csproj index 6bdc8cef..45e10dc9 100644 --- a/examples/AspNetCore/OData/ODataBasicExample/ODataBasicExample.csproj +++ b/examples/AspNetCore/OData/ODataBasicExample/ODataBasicExample.csproj @@ -1,7 +1,11 @@  + + net10.0 + + - + \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataBasicExample/Program.cs b/examples/AspNetCore/OData/ODataBasicExample/Program.cs index a269f238..a21e6e19 100644 --- a/examples/AspNetCore/OData/ODataBasicExample/Program.cs +++ b/examples/AspNetCore/OData/ODataBasicExample/Program.cs @@ -15,10 +15,10 @@ // is merely illustrating that they can coexist and allows you // to easily experiment with either configuration. one of these // would be removed in a real application. - + // WHEN VERSIONING BY: query string, header, or media type options.AddRouteComponents( "api" ); - + // WHEN VERSIONING BY: url segment options.AddRouteComponents( "api/v{version:apiVersion}" ); } ); diff --git a/examples/AspNetCore/OData/ODataConventionsExample/Controllers/People2Controller.cs b/examples/AspNetCore/OData/ODataConventionsExample/Controllers/People2Controller.cs index 2cb9bc4f..ecb245e7 100644 --- a/examples/AspNetCore/OData/ODataConventionsExample/Controllers/People2Controller.cs +++ b/examples/AspNetCore/OData/ODataConventionsExample/Controllers/People2Controller.cs @@ -14,12 +14,12 @@ public class People2Controller : ODataController public IActionResult Get( ODataQueryOptions options ) => Ok( new Person[] { - new() - { - Id = 1, - FirstName = "Bill", - LastName = "Mei", - Email = "bill.mei@somewhere.com", + new() + { + Id = 1, + FirstName = "Bill", + LastName = "Mei", + Email = "bill.mei@somewhere.com", Phone = "555-555-5555", }, } ); @@ -29,10 +29,10 @@ public IActionResult Get( ODataQueryOptions options ) => public IActionResult Get( int key, ODataQueryOptions options ) => Ok( new Person() { - Id = key, - FirstName = "Bill", - LastName = "Mei", - Email = "bill.mei@somewhere.com", + Id = key, + FirstName = "Bill", + LastName = "Mei", + Email = "bill.mei@somewhere.com", Phone = "555-555-5555", } ); } \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataConventionsExample/ODataConventionsExample.csproj b/examples/AspNetCore/OData/ODataConventionsExample/ODataConventionsExample.csproj index ecccb3a9..45e10dc9 100644 --- a/examples/AspNetCore/OData/ODataConventionsExample/ODataConventionsExample.csproj +++ b/examples/AspNetCore/OData/ODataConventionsExample/ODataConventionsExample.csproj @@ -1,5 +1,9 @@  + + net10.0 + + diff --git a/examples/AspNetCore/OData/ODataConventionsExample/Program.cs b/examples/AspNetCore/OData/ODataConventionsExample/Program.cs index 9f937fda..2a615bde 100644 --- a/examples/AspNetCore/OData/ODataConventionsExample/Program.cs +++ b/examples/AspNetCore/OData/ODataConventionsExample/Program.cs @@ -21,12 +21,12 @@ // apply api versions using conventions rather than attributes options.Conventions.Controller() .HasApiVersion( 1.0 ); - + options.Conventions.Controller() .HasApiVersion( 1.0 ) .HasApiVersion( 2.0 ) .Action( c => c.Patch( default, default, default ) ).MapToApiVersion( 2.0 ); - + options.Conventions.Controller() .HasApiVersion( 3.0 ); } ) @@ -38,10 +38,10 @@ // is merely illustrating that they can coexist and allows you // to easily experiment with either configuration. one of these // would be removed in a real application. - + // WHEN VERSIONING BY: query string, header, or media type options.AddRouteComponents( "api" ); - + // WHEN VERSIONING BY: url segment options.AddRouteComponents( "api/v{version:apiVersion}" ); } ); diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/ProductConfiguration.cs b/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/ProductConfiguration.cs index 88f7d83a..ad96be11 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/ProductConfiguration.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/ProductConfiguration.cs @@ -19,7 +19,7 @@ public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string rout } var product = builder.EntitySet( "Products" ).EntityType; - + product.HasKey( p => p.Id ); product.Page( maxTopValue: 100, pageSizeValue: default ); } diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs deleted file mode 100644 index f3b2bf12..00000000 --- a/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs +++ /dev/null @@ -1,162 +0,0 @@ -namespace ApiVersioning.Examples; - -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; - -/// -/// Configures the Swagger generation options. -/// -/// This allows API versioning to define a Swagger document per API version after the -/// service has been resolved from the service container. -public class ConfigureSwaggerOptions : IConfigureOptions -{ - private readonly IApiVersionDescriptionProvider provider; - - /// - /// Initializes a new instance of the class. - /// - /// The provider used to generate Swagger documents. - public ConfigureSwaggerOptions( IApiVersionDescriptionProvider provider ) => this.provider = provider; - - /// - public void Configure( SwaggerGenOptions options ) - { - // add a swagger document for each discovered API version - // note: you might choose to skip or document deprecated API versions differently - foreach ( var description in provider.ApiVersionDescriptions ) - { - options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) ); - } - } - - private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription description ) - { - var text = new StringBuilder( "An example application with OData, OpenAPI, Swashbuckle, and API versioning." ); - var info = new OpenApiInfo() - { - Title = "Sample API", - Version = description.ApiVersion.ToString(), - Contact = new OpenApiContact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" }, - License = new OpenApiLicense() { Name = "MIT", Url = new Uri( "https://opensource.org/licenses/MIT" ) } - }; - - if ( description.IsDeprecated ) - { - text.Append( " This API version has been deprecated." ); - } - - if ( description.DeprecationPolicy is { } deprecationPolicy ) - { - if ( deprecationPolicy.Date is { } when ) - { - if ( when < DateTime.Now ) - { - text.Append( " The API has been deprecated on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - else - { - text.Append( " The API will be deprecated on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - } - - if ( deprecationPolicy.HasLinks ) - { - text.AppendLine(); - - var rendered = false; - - foreach ( var link in deprecationPolicy.Links ) - { - if ( link.Type == "text/html" ) - { - if ( !rendered ) - { - text.Append( "

Links

" ); - } - } - } - - if ( description.SunsetPolicy is { } sunsetPolicy ) - { - if ( sunsetPolicy.Date is { } when ) - { - if ( when < DateTime.Now ) - { - text.Append( " The API has been sunset on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - else - { - text.Append( " The API will be sunset on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - } - - if ( sunsetPolicy.HasLinks ) - { - text.AppendLine(); - - var rendered = false; - - foreach ( var link in sunsetPolicy.Links ) - { - if ( link.Type == "text/html" ) - { - if ( !rendered ) - { - text.Append( "

Links

" ); - } - } - } - - text.Append( "

Additional Information

" ); - info.Description = text.ToString(); - - return info; - } -} \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/EnumerableExtensions.cs b/examples/AspNetCore/OData/ODataOpenApiExample/EnumerableExtensions.cs index d8970bf9..6779ece7 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/EnumerableExtensions.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/EnumerableExtensions.cs @@ -7,63 +7,65 @@ ///
public static class EnumerableExtensions { - /// - /// Returns the first element from the specified sequence. - /// /// The sequence to take an element from. - /// The first element in the sequence or null. - public static object FirstOrDefault( this IEnumerable enumerable ) + extension( IEnumerable enumerable ) { - var iterator = enumerable.GetEnumerator(); - - try + /// + /// Returns the first element from the specified sequence. + /// + /// The first element in the sequence or null. + public object FirstOrDefault() { - if ( iterator.MoveNext() ) + var iterator = enumerable.GetEnumerator(); + + try { - return iterator.Current; + if ( iterator.MoveNext() ) + { + return iterator.Current; + } } - } - finally - { - if ( iterator is IDisposable disposable ) + finally { - disposable.Dispose(); + if ( iterator is IDisposable disposable ) + { + disposable.Dispose(); + } } - } - - return default; - } - /// - /// Returns a single element from the specified sequence. - /// - /// The sequence to take an element from. - /// The single element in the sequence or null. - public static object SingleOrDefault( this IEnumerable enumerable ) - { - var iterator = enumerable.GetEnumerator(); - var result = default( object ); + return default; + } - try + /// + /// Returns a single element from the specified sequence. + /// + /// The single element in the sequence or null. + public object SingleOrDefault() { - if ( iterator.MoveNext() ) - { - result = iterator.Current; + var iterator = enumerable.GetEnumerator(); + var result = default( object ); + try + { if ( iterator.MoveNext() ) { - throw new InvalidOperationException( "The sequence contains more than one element." ); + result = iterator.Current; + + if ( iterator.MoveNext() ) + { + throw new InvalidOperationException( "The sequence contains more than one element." ); + } } } - } - finally - { - if ( iterator is IDisposable disposable ) + finally { - disposable.Dispose(); + if ( iterator is IDisposable disposable ) + { + disposable.Dispose(); + } } - } - return result; + return result; + } } } \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/FunctionsController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/FunctionsController.cs index 10c3175a..9e78d7e8 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/FunctionsController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/FunctionsController.cs @@ -12,11 +12,15 @@ public class FunctionsController : ODataController { /// - /// Gets the sales tax for a postal code. + /// Get Sales Tax /// + /// Gets the sales tax for a postal code. /// The postal code to get the sales tax for. /// The sales tax rate for the postal code. + /// The sales tax was successfully retrieved. + /// The postal code was not found. [HttpGet( "api/GetSalesTaxRate(PostalCode={postalCode})" )] [ProducesResponseType( typeof( double ), Status200OK )] + [ProducesResponseType( Status404NotFound )] public IActionResult GetSalesTaxRate( int postalCode ) => Ok( 5.6 ); } \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/Models/Order.cs b/examples/AspNetCore/OData/ODataOpenApiExample/Models/Order.cs index 1df9452e..0e7c563b 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/Models/Order.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/Models/Order.cs @@ -47,5 +47,5 @@ public class Order ///
/// The list of order line items. [Contained] - public virtual IList LineItems { get; } = new List(); + public virtual IList LineItems { get; } = []; } \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/ODataExtensions.cs b/examples/AspNetCore/OData/ODataOpenApiExample/ODataExtensions.cs index 72085a56..76870237 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/ODataExtensions.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/ODataExtensions.cs @@ -7,28 +7,31 @@ internal static class ODataExtensions { - public static IReadOnlyDictionary GetRelatedKeys( this ControllerBase controller, Uri uri ) + extension( ControllerBase controller ) { - // REF: https://github.com/OData/AspNetCoreOData/blob/main/src/Microsoft.AspNetCore.OData/Routing/Parser/DefaultODataPathParser.cs - var feature = controller.HttpContext.ODataFeature(); - var model = feature.Model; - var serviceRoot = new Uri( new Uri( feature.BaseAddress ), feature.RoutePrefix ); - var requestProvider = feature.Services; - var parser = new ODataUriParser( model, serviceRoot, uri, requestProvider ); + public IReadOnlyDictionary GetRelatedKeys( Uri uri ) + { + // REF: https://github.com/OData/AspNetCoreOData/blob/main/src/Microsoft.AspNetCore.OData/Routing/Parser/DefaultODataPathParser.cs + var feature = controller.HttpContext.ODataFeature(); + var model = feature.Model; + var serviceRoot = new Uri( new Uri( feature.BaseAddress ), feature.RoutePrefix ); + var requestProvider = feature.Services; + var parser = new ODataUriParser( model, serviceRoot, uri, requestProvider ); - parser.Resolver ??= new UnqualifiedODataUriResolver() { EnableCaseInsensitive = true }; - parser.UrlKeyDelimiter = ODataUrlKeyDelimiter.Slash; + parser.Resolver ??= new UnqualifiedODataUriResolver() { EnableCaseInsensitive = true }; + parser.UrlKeyDelimiter = ODataUrlKeyDelimiter.Slash; - var path = parser.ParsePath(); - var segment = path.OfType().FirstOrDefault(); + var path = parser.ParsePath(); + var segment = path.OfType().FirstOrDefault(); - if ( segment is null ) - { - return new Dictionary( capacity: 0 ); + if ( segment is null ) + { + return new Dictionary( capacity: 0 ); + } + + return new Dictionary( segment.Keys, StringComparer.OrdinalIgnoreCase ); } - return new Dictionary( segment.Keys, StringComparer.OrdinalIgnoreCase ); + public object GetRelatedKey( Uri uri ) => controller.GetRelatedKeys( uri ).Values.SingleOrDefault(); } - - public static object GetRelatedKey( this ControllerBase controller, Uri uri ) => controller.GetRelatedKeys( uri ).Values.SingleOrDefault(); } \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/ODataOpenApiExample.csproj b/examples/AspNetCore/OData/ODataOpenApiExample/ODataOpenApiExample.csproj index 3bffcf5e..d6b8d5a0 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/ODataOpenApiExample.csproj +++ b/examples/AspNetCore/OData/ODataOpenApiExample/ODataOpenApiExample.csproj @@ -1,15 +1,23 @@  + net10.0 + Example API true - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - + + \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/Program.cs b/examples/AspNetCore/OData/ODataOpenApiExample/Program.cs index 4dd5e683..f2722e75 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/Program.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/Program.cs @@ -1,16 +1,17 @@ using ApiVersioning.Examples; using Asp.Versioning; using Asp.Versioning.Conventions; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.OData; -using Microsoft.Extensions.Options; -using Swashbuckle.AspNetCore.SwaggerGen; +using Scalar.AspNetCore; +using System.Reflection; using static Microsoft.AspNetCore.OData.Query.AllowedQueryOptions; using PeopleControllerV2 = ApiVersioning.Examples.V2.PeopleController; using PeopleControllerV3 = ApiVersioning.Examples.V3.PeopleController; -var builder = WebApplication.CreateBuilder( args ); +[assembly: AssemblyDescription( "An example API" )] -// Add services to the container. +var builder = WebApplication.CreateBuilder( args ); builder.Services.AddControllers() .AddOData( @@ -31,10 +32,16 @@ // "api-supported-versions" and "api-deprecated-versions" options.ReportApiVersions = true; + options.Policies.Deprecate( 0.9 ) + .Effective( DateTimeOffset.Now ) + .Link( "policy.html" ) + .Title( "Version Deprecation Policy" ) + .Type( "text/html" ); + options.Policies.Sunset( 0.9 ) .Effective( DateTimeOffset.Now.AddDays( 60 ) ) .Link( "policy.html" ) - .Title( "Versioning Policy" ) + .Title( "Version Sunset Policy" ) .Type( "text/html" ); } ) .AddOData( options => options.AddRouteComponents( "api" ) ) @@ -61,49 +68,29 @@ .Allow( Skip | Count ) .AllowTop( 100 ) .AllowOrderBy( "firstName", "lastName" ); - } ); - -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddTransient, ConfigureSwaggerOptions>(); -builder.Services.AddSwaggerGen( - options => - { - // add a custom operation filter which sets default values - options.OperationFilter(); - - var fileName = typeof( Program ).Assembly.GetName().Name + ".xml"; - var filePath = Path.Combine( AppContext.BaseDirectory, fileName ); - - // integrate xml comments - options.IncludeXmlComments( filePath ); - } ); + } ) + .AddOpenApi( options => options.Document.AddScalarTransformers() ); var app = builder.Build(); -// Configure HTTP request pipeline. - if ( app.Environment.IsDevelopment() ) { - // Access ~/$odata to identify OData endpoints that failed to match a route template. + // access ~/$odata to identify OData endpoints that failed to match a route template app.UseODataRouteDebug(); -} + app.MapOpenApi().WithDocumentPerVersion(); + app.MapScalarApiReference( + options => + { + var descriptions = app.DescribeApiVersions(); -app.UseSwagger(); -if ( app.Environment.IsDevelopment() ) -{ -app.UseSwaggerUI( - options => - { - var descriptions = app.DescribeApiVersions(); + for ( var i = 0; i < descriptions.Count; i++ ) + { + var description = descriptions[i]; + var isDefault = i == descriptions.Count - 1; - // 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 ); - } - } ); + options.AddDocument( description.GroupName, description.GroupName, isDefault: isDefault ); + } + } ); } app.UseHttpsRedirection(); diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/Properties/launchSettings.json b/examples/AspNetCore/OData/ODataOpenApiExample/Properties/launchSettings.json index 62e70eff..6ac4c4ce 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/Properties/launchSettings.json +++ b/examples/AspNetCore/OData/ODataOpenApiExample/Properties/launchSettings.json @@ -12,7 +12,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "scalar", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -21,7 +21,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "scalar", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/SwaggerDefaultValues.cs b/examples/AspNetCore/OData/ODataOpenApiExample/SwaggerDefaultValues.cs deleted file mode 100644 index fbf06c2f..00000000 --- a/examples/AspNetCore/OData/ODataOpenApiExample/SwaggerDefaultValues.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace ApiVersioning.Examples; - -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; -using System.Text.Json; - -/// -/// Represents the OpenAPI/Swashbuckle operation filter used to document the implicit API version parameter. -/// -/// This is only required due to bugs in the . -/// Once they are fixed and published, this class can be removed. -public class SwaggerDefaultValues : IOperationFilter -{ - /// - /// Applies the filter to the specified operation using the given context. - /// - /// The operation to apply the filter to. - /// The current operation filter context. - public void Apply( OpenApiOperation operation, OperationFilterContext context ) - { - var apiDescription = context.ApiDescription; - - operation.Deprecated |= apiDescription.IsDeprecated(); - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077 - foreach ( var responseType in context.ApiDescription.SupportedResponseTypes ) - { - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387 - var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); - var response = operation.Responses[responseKey]; - - foreach ( var contentType in response.Content.Keys ) - { - if ( !responseType.ApiResponseFormats.Any( x => x.MediaType == contentType ) ) - { - response.Content.Remove( contentType ); - } - } - } - - if ( operation.Parameters == null ) - { - return; - } - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 - foreach ( var parameter in operation.Parameters ) - { - var description = apiDescription.ParameterDescriptions.First( p => p.Name == parameter.Name ); - - parameter.Description ??= description.ModelMetadata?.Description; - - if ( parameter.Schema.Default == null && description.DefaultValue != null ) - { - // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330 - var json = JsonSerializer.Serialize( description.DefaultValue, description.ModelMetadata.ModelType ); - parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson( json ); - } - - parameter.Required |= description.IsRequired; - } - } -} \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V1/OrdersController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V1/OrdersController.cs index 6d5e2fbf..c1bfffba 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/V1/OrdersController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/V1/OrdersController.cs @@ -18,8 +18,9 @@ public class OrdersController : ODataController { /// - /// Gets a single order. + /// Get Order /// + /// Gets a single order. /// The requested order identifier. /// The requested order. /// The order was successfully retrieved. @@ -33,8 +34,9 @@ public SingleResult Get( int key ) => SingleResult.Create( new[] { new Order() { Id = key, Customer = "John Doe" } }.AsQueryable() ); /// - /// Places a new order. + /// Place Order /// + /// Places a new order. /// The order to place. /// The created order. /// The order was successfully placed. @@ -57,8 +59,9 @@ public IActionResult Post( [FromBody] Order order ) } /// - /// Gets the most expensive order. + /// Get Most Expensive Order /// + /// Gets the most expensive order. /// The most expensive order. /// The order was successfully retrieved. /// The no orders exist. @@ -72,8 +75,9 @@ public SingleResult MostExpensive() => SingleResult.Create( new[] { new Order() { Id = 42, Customer = "Bill Mei" } }.AsQueryable() ); /// - /// Gets the most expensive order. + /// Get Most Expensive Order By ID /// + /// Gets the most expensive order. /// The order identifier. /// The most expensive order. /// The order was successfully retrieved. @@ -88,8 +92,9 @@ public SingleResult MostExpensive( int key ) => SingleResult.Create( new[] { new Order() { Id = key, Customer = "Bill Mei" } }.AsQueryable() ); /// - /// Gets the line items for the specified order. + /// Get Line Items /// + /// Gets the line items for the specified order. /// The order identifier. /// The order line items. /// The line items were successfully retrieved. diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V1/PeopleController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V1/PeopleController.cs index fa6f9aff..b12e2606 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/V1/PeopleController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/V1/PeopleController.cs @@ -17,8 +17,9 @@ public class PeopleController : ODataController { /// - /// Gets a single person. + /// Get Person /// + /// Gets a single person. /// The requested person identifier. /// The current OData query options. /// The requested person. @@ -51,8 +52,9 @@ public IActionResult Get( int key, ODataQueryOptions options ) } /// - /// Gets the most expensive person. + /// Most Expensive Person /// + /// Gets the most expensive person. /// The most expensive person. /// The person was successfully retrieved. /// No people exist. @@ -75,8 +77,9 @@ public SingleResult MostExpensive( ODataQueryOptions options, Ca }.AsQueryable() ); /// - /// Gets the most expensive person. + /// Most Expensive Person By ID /// + /// Gets the most expensive person. /// The most expensive person. /// The person was successfully retrieved. /// The person does not exist. diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V2/OrdersController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V2/OrdersController.cs index c502c0bf..459440fd 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/V2/OrdersController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/V2/OrdersController.cs @@ -19,8 +19,9 @@ public class OrdersController : ODataController { /// - /// Retrieves all orders. + /// Get Orders /// + /// Retrieves all orders. /// All available orders. /// The successfully retrieved orders. [HttpGet] @@ -40,8 +41,9 @@ public IQueryable Get() } /// - /// Gets a single order. + /// Get Order /// + /// Gets a single order. /// The requested order identifier. /// The requested order. /// The order was successfully retrieved. @@ -55,8 +57,9 @@ public SingleResult Get( int key ) => SingleResult.Create( new[] { new Order() { Id = key, Customer = "John Doe" } }.AsQueryable() ); /// - /// Places a new order. + /// Place Order /// + /// Places a new order. /// The order to place. /// The created order. /// The order was successfully placed. @@ -78,8 +81,9 @@ public IActionResult Post( [FromBody] Order order ) } /// - /// Updates an existing order. + /// Update Order /// + /// Updates an existing order. /// The requested order identifier. /// The partial order to update. /// The created order. @@ -107,8 +111,9 @@ public IActionResult Patch( int key, [FromBody] Delta delta ) } /// - /// Gets the most expensive order. + /// Get Most Expensive Order /// + /// Gets the most expensive order. /// The most expensive order. /// The order was successfully retrieved. /// The no orders exist. @@ -121,8 +126,9 @@ public SingleResult MostExpensive() => SingleResult.Create( new[] { new Order() { Id = 42, Customer = "Bill Mei" } }.AsQueryable() ); /// - /// Rates an order. + /// Rate Order /// + /// Rates an order. /// The requested order identifier. /// The action parameters. /// None @@ -145,8 +151,9 @@ public IActionResult Rate( int key, [FromBody] ODataActionParameters parameters } /// - /// Gets the line items for the specified order. + /// Get Line Items /// + /// Gets the line items for the specified order. /// The order identifier. /// The order line items. /// The line items were successfully retrieved. diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V2/PeopleController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V2/PeopleController.cs index 08a2737d..42d845c0 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/V2/PeopleController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/V2/PeopleController.cs @@ -18,8 +18,9 @@ public class PeopleController : ODataController { /// - /// Gets all people. + /// Get People /// + /// Gets all people. /// The current OData query options. /// All available people. /// The successfully retrieved people. @@ -77,8 +78,9 @@ public IActionResult Get( ODataQueryOptions options ) } /// - /// Gets a single person. + /// Get Person /// + /// Gets a single person. /// The requested person identifier. /// The current OData query options. /// The requested person. @@ -112,8 +114,9 @@ public IActionResult Get( int key, ODataQueryOptions options ) } /// - /// Gets the new hires since the specified date. + /// Get New Hires /// + /// Gets the new hires since the specified date. /// The date and time since people were hired. /// The current OData query options. /// The matching new hires. @@ -124,8 +127,9 @@ public IActionResult Get( int key, ODataQueryOptions options ) public IActionResult NewHires( DateTime since, ODataQueryOptions options ) => Get( options ); /// - /// Gets the home address of a person. + /// Get Home Address /// + /// Gets the home address of a person. /// The person identifier. /// The person's home address. /// The home address was successfully retrieved. diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V3/AcmeController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V3/AcmeController.cs index 7ad02441..0b019f09 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/V3/AcmeController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/V3/AcmeController.cs @@ -15,10 +15,11 @@ public class AcmeController : ODataController { /// - /// Retrieves the ACME supplier. + /// Get ACME Supplier /// + /// Retrieves the ACME supplier. /// All available suppliers. - /// The supplier successfully retrieved. + /// The supplier was successfully retrieved. [HttpGet] [EnableQuery] [Produces( "application/json" )] @@ -26,58 +27,59 @@ public class AcmeController : ODataController public IActionResult Get() => Ok( NewSupplier() ); /// - /// Gets the products associated with the supplier. + /// Get Products /// + /// Gets the products associated with the supplier. /// The associated supplier products. + /// The products were successfully retrieved. [HttpGet] [EnableQuery] + [Produces( "application/json" )] + [ProducesResponseType( typeof( ODataValue> ), Status200OK )] public IQueryable GetProducts() => NewSupplier().Products.AsQueryable(); /// - /// Links a product to a supplier. + /// Link Product /// + /// Links a product to a supplier. /// The name of the related navigation property. /// The related entity identifier. /// None + /// The product was successfully linked. [HttpPut] [ProducesResponseType( Status204NoContent )] - [ProducesResponseType( Status404NotFound )] - public IActionResult CreateRef( - string navigationProperty, - [FromBody] Uri link ) + public IActionResult CreateRef( string navigationProperty, [FromBody] Uri link ) { var relatedKey = this.GetRelatedKey( link ); return NoContent(); } /// - /// Unlinks a product from a supplier. + /// Unlink Product /// + /// Unlinks a product from a supplier. /// The related entity identifier. /// The name of the related navigation property. /// None + /// The product was successfully unlinked. [HttpDelete] [ProducesResponseType( Status204NoContent )] - [ProducesResponseType( Status404NotFound )] - public IActionResult DeleteRef( - int relatedKey, - string navigationProperty ) => NoContent(); + public IActionResult DeleteRef( int relatedKey, string navigationProperty ) => NoContent(); - private static Supplier NewSupplier() => - new() - { - Id = 42, - Name = "Acme", - Products = new List() + private static Supplier NewSupplier() => new() + { + Id = 42, + Name = "Acme", + Products = + [ + new() { - new() - { - Id = 42, - Name = "Product 42", - Category = "Test", - Price = 42, - SupplierId = 42, - }, + Id = 42, + Name = "Product 42", + Category = "Test", + Price = 42, + SupplierId = 42, }, - }; + ], + }; } \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V3/OrdersController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V3/OrdersController.cs index 90eaffd6..fc102b9f 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/V3/OrdersController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/V3/OrdersController.cs @@ -19,8 +19,9 @@ public class OrdersController : ODataController { /// - /// Retrieves all orders. + /// Get Orders /// + /// Retrieves all orders. /// All available orders. /// Orders successfully retrieved. /// The order is invalid. @@ -41,8 +42,9 @@ public IQueryable Get() } /// - /// Gets a single order. + /// Get Order /// + /// Gets a single order. /// The requested order identifier. /// The requested order. /// The order was successfully retrieved. @@ -56,8 +58,9 @@ public SingleResult Get( int key ) => SingleResult.Create( new[] { new Order() { Id = key, Customer = "John Doe" } }.AsQueryable() ); /// - /// Places a new order. + /// Place Order /// + /// Places a new order. /// The order to place. /// The created order. /// The order was successfully placed. @@ -78,8 +81,9 @@ public IActionResult Post( [FromBody] Order order ) } /// - /// Updates an existing order. + /// Update Order /// + /// Updates an existing order. /// The requested order identifier. /// The partial order to update. /// The created order. @@ -107,8 +111,9 @@ public IActionResult Patch( int key, [FromBody] Delta delta ) } /// - /// Cancels an order. + /// Cancel Order /// + /// Cancels an order. /// The order to cancel. /// Indicates if the order should only be suspended. /// None @@ -120,8 +125,9 @@ public IActionResult Patch( int key, [FromBody] Delta delta ) public IActionResult Delete( int key, bool suspendOnly ) => NoContent(); /// - /// Gets the most expensive order. + /// Get Most Expensive Order /// + /// Gets the most expensive order. /// The most expensive order. /// The order was successfully retrieved. /// The no orders exist. @@ -134,8 +140,9 @@ public SingleResult MostExpensive() => SingleResult.Create( new[] { new Order() { Id = 42, Customer = "Bill Mei" } }.AsQueryable() ); /// - /// Rates an order. + /// Rate Order /// + /// Rates an order. /// The requested order identifier. /// The action parameters. /// None @@ -158,8 +165,9 @@ public IActionResult Rate( int key, [FromBody] ODataActionParameters parameters } /// - /// Gets the line items for the specified order. + /// Get Line Items /// + /// Gets the line items for the specified order. /// The order identifier. /// The order line items. /// The line items were successfully retrieved. diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V3/PeopleController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V3/PeopleController.cs index fee451f6..6fd1a887 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/V3/PeopleController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/V3/PeopleController.cs @@ -19,8 +19,9 @@ public class PeopleController : ODataController { /// - /// Gets all people. + /// Get People /// + /// Gets all people. /// The current OData query options. /// All available people. /// The successfully retrieved people. @@ -81,8 +82,9 @@ public IActionResult Get( ODataQueryOptions options ) } /// - /// Gets a single person. + /// Get Person /// + /// Gets a single person. /// The requested person identifier. /// The current OData query options. /// The requested person. @@ -117,8 +119,9 @@ public IActionResult Get( int key, ODataQueryOptions options ) } /// - /// Creates a new person. + /// Add Person /// + /// Adds a new person. /// The person to create. /// The created person. /// The person was successfully created. @@ -140,8 +143,9 @@ public IActionResult Post( [FromBody] Person person ) } /// - /// Gets the new hires since the specified date. + /// Get New Hires /// + /// Gets the new hires since the specified date. /// The date and time since people were hired. /// The current OData query options. /// The matching new hires. @@ -152,8 +156,9 @@ public IActionResult Post( [FromBody] Person person ) public IActionResult NewHires( DateTime since, ODataQueryOptions options ) => Get( options ); /// - /// Promotes a person. + /// Promote Person /// + /// Promotes a person. /// The identifier of the person to promote. /// The action parameters. /// None @@ -176,8 +181,9 @@ public IActionResult Promote( int key, [FromBody] ODataActionParameters paramete } /// - /// Gets the home address of a person. + /// Get Home Address /// + /// Gets the home address of a person. /// The person identifier. /// The person's home address. /// The home address was successfully retrieved. @@ -197,8 +203,9 @@ public IActionResult GetHomeAddress( int key ) => } ); /// - /// Gets the work address of a person. + /// Get Work Address /// + /// Gets the work address of a person. /// The person identifier. /// The person's work address. /// The work address was successfully retrieved. diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V3/ProductsController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V3/ProductsController.cs index f0b2c9a8..9ab51c0c 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/V3/ProductsController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/V3/ProductsController.cs @@ -26,8 +26,9 @@ public class ProductsController : ODataController }.AsQueryable(); /// - /// Retrieves all products. + /// Get Products /// + /// Retrieves all products. /// All available products. /// Products successfully retrieved. [HttpGet] @@ -37,8 +38,9 @@ public class ProductsController : ODataController public IQueryable Get() => products; /// - /// Gets a single product. + /// Get Product /// + /// Gets a single product. /// The requested product identifier. /// The requested product. /// The product was successfully retrieved. @@ -52,12 +54,13 @@ public SingleResult Get( int key ) => SingleResult.Create( products.Where( p => p.Id == key ) ); /// - /// Creates a new product. + /// Add Product /// - /// The product to create. - /// The created product. - /// The product was successfully created. - /// The product was successfully created. + /// Adds a new product. + /// The product to add. + /// The added product. + /// The product was successfully added. + /// The product was successfully added. /// The product is invalid. [HttpPost] [Produces( "application/json" )] @@ -77,8 +80,9 @@ public IActionResult Post( [FromBody] Product product ) } /// - /// Updates an existing product. + /// Update Product (Partial) /// + /// Updates an existing product. /// The requested product identifier. /// The partial product to update. /// The updated product. @@ -107,8 +111,9 @@ public IActionResult Patch( int key, [FromBody] Delta delta ) } /// - /// Updates an existing product. + /// Update Product /// + /// Updates an existing product. /// The requested product identifier. /// The product to update. /// The updated product. @@ -133,22 +138,24 @@ public IActionResult Put( int key, [FromBody] Product update ) } /// - /// Deletes a product. + /// Remove Product /// - /// The product to delete. + /// Removes a product. + /// The product to remove. /// None - /// The product was successfully deleted. + /// The product was successfully removed. [HttpDelete] [ProducesResponseType( Status204NoContent )] - [ProducesResponseType( Status404NotFound )] public IActionResult Delete( int key ) => NoContent(); /// - /// Gets the supplier associated with the product. + /// Get Supplier /// + /// Gets the supplier associated with the product. /// The product identifier. - /// The supplier /// The requested supplier. + /// The supplier was successfully retrieved. + /// The supplier was not found. [HttpGet] [EnableQuery] [Produces( "application/json" )] @@ -158,11 +165,14 @@ public SingleResult GetSupplier( int key ) => SingleResult.Create( products.Where( p => p.Id == key ).Select( p => p.Supplier ) ); /// - /// Gets the link to the associated supplier, if any. + /// Get Supplier Reference /// + /// Gets the reference link to the associated supplier, if any. /// The product identifier. /// The name of the related navigation property. /// The supplier link. + /// The supplier reference was successfully retrieved. + /// A supplier reference was not found. [HttpGet] [Produces( "application/json" )] [ProducesResponseType( typeof( ODataId ), Status200OK )] @@ -181,47 +191,46 @@ public IActionResult GetRef( int key, string navigationProperty ) } /// - /// Links a supplier to a product. + /// Link Supplier /// + /// Links a supplier to a product. /// The product identifier. /// The name of the related navigation property. /// The related entity identifier. /// None + /// The supplier was successfully linked. + /// The product or supplier was not found. [HttpPut] [ProducesResponseType( Status204NoContent )] [ProducesResponseType( Status404NotFound )] - public IActionResult CreateRef( - int key, - string navigationProperty, - [FromBody] Uri link ) + public IActionResult CreateRef( int key, string navigationProperty, [FromBody] Uri link ) { var relatedKey = this.GetRelatedKey( link ); return NoContent(); } /// - /// Unlinks a supplier from a product. + /// Unlink Supplier /// + /// Unlinks a supplier from a product. /// The product identifier. /// The name of the related navigation property. /// The related entity identifier. /// None + /// The supplier was successfully linked. + /// The product or supplier was not found. [HttpDelete] [ProducesResponseType( Status204NoContent )] [ProducesResponseType( Status404NotFound )] - public IActionResult DeleteRef( - int key, - string navigationProperty, - int relatedKey ) => NoContent(); + public IActionResult DeleteRef( int key, string navigationProperty, int relatedKey ) => NoContent(); - private static Product NewProduct( int id ) => - new() - { - Id = id, - Category = "Test", - Name = "Product " + id.ToString(), - Price = id, - Supplier = new Supplier() { Id = id, Name = "Supplier " + id.ToString() }, - SupplierId = id, - }; + private static Product NewProduct( int id ) => new() + { + Id = id, + Category = "Test", + Name = "Product " + id.ToString(), + Price = id, + Supplier = new Supplier() { Id = id, Name = "Supplier " + id.ToString() }, + SupplierId = id, + }; } \ No newline at end of file diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V3/SuppliersController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V3/SuppliersController.cs index 652b4d4e..4a1bd632 100644 --- a/examples/AspNetCore/OData/ODataOpenApiExample/V3/SuppliersController.cs +++ b/examples/AspNetCore/OData/ODataOpenApiExample/V3/SuppliersController.cs @@ -190,8 +190,8 @@ private static Supplier NewSupplier( int id ) => { Id = id, Name = "Supplier " + id.ToString(), - Products = new List() - { + Products = + [ new() { Id = id, @@ -200,6 +200,6 @@ private static Supplier NewSupplier( int id ) => Price = id, SupplierId = id, }, - }, + ], }; } \ No newline at end of file diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/BooksController.cs b/examples/AspNetCore/OData/SomeODataOpenApiExample/BooksController.cs index 68e97255..f08c8499 100644 --- a/examples/AspNetCore/OData/SomeODataOpenApiExample/BooksController.cs +++ b/examples/AspNetCore/OData/SomeODataOpenApiExample/BooksController.cs @@ -24,8 +24,9 @@ public class BooksController : ControllerBase ]; /// - /// Gets all books. + /// Get Books /// + /// Gets all books. /// The current OData query options. /// All available books. /// The successfully retrieved books. @@ -36,8 +37,9 @@ public IActionResult Get( ODataQueryOptions options ) => Ok( options.ApplyTo( books.AsQueryable() ) ); /// - /// Gets a single book. + /// Get Book /// + /// Gets a single book. /// The requested book identifier. /// The current OData query options. /// The requested book. diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs deleted file mode 100644 index 38a250af..00000000 --- a/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs +++ /dev/null @@ -1,162 +0,0 @@ -namespace ApiVersioning.Examples; - -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; - -/// -/// Configures the Swagger generation options. -/// -/// This allows API versioning to define a Swagger document per API version after the -/// service has been resolved from the service container. -public class ConfigureSwaggerOptions : IConfigureOptions -{ - private readonly IApiVersionDescriptionProvider provider; - - /// - /// Initializes a new instance of the class. - /// - /// The provider used to generate Swagger documents. - public ConfigureSwaggerOptions( IApiVersionDescriptionProvider provider ) => this.provider = provider; - - /// - public void Configure( SwaggerGenOptions options ) - { - // add a swagger document for each discovered API version - // note: you might choose to skip or document deprecated API versions differently - foreach ( var description in provider.ApiVersionDescriptions ) - { - options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) ); - } - } - - private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription description ) - { - var text = new StringBuilder( "An example application with some OData, OpenAPI, Swashbuckle, and API versioning." ); - var info = new OpenApiInfo() - { - Title = "Sample API", - Version = description.ApiVersion.ToString(), - Contact = new OpenApiContact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" }, - License = new OpenApiLicense() { Name = "MIT", Url = new Uri( "https://opensource.org/licenses/MIT" ) } - }; - - if ( description.IsDeprecated ) - { - text.Append( " This API version has been deprecated." ); - } - - if ( description.DeprecationPolicy is { } deprecationPolicy ) - { - if ( deprecationPolicy.Date is { } when ) - { - if ( when < DateTime.Now ) - { - text.Append( " The API has been deprecated on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - else - { - text.Append( " The API will be deprecated on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - } - - if ( deprecationPolicy.HasLinks ) - { - text.AppendLine(); - - var rendered = false; - - foreach ( var link in deprecationPolicy.Links ) - { - if ( link.Type == "text/html" ) - { - if ( !rendered ) - { - text.Append( "

Links

" ); - } - } - } - - if ( description.SunsetPolicy is { } sunsetPolicy ) - { - if ( sunsetPolicy.Date is { } when ) - { - if ( when < DateTime.Now ) - { - text.Append( " The API has been sunset on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - else - { - text.Append( " The API will be sunset on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - } - - if ( sunsetPolicy.HasLinks ) - { - text.AppendLine(); - - var rendered = false; - - foreach ( var link in sunsetPolicy.Links ) - { - if ( link.Type == "text/html" ) - { - if ( !rendered ) - { - text.Append( "

Links

" ); - } - } - } - - text.Append( "

Additional Information

" ); - info.Description = text.ToString(); - - return info; - } -} \ No newline at end of file diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/Program.cs b/examples/AspNetCore/OData/SomeODataOpenApiExample/Program.cs index 55e70b17..4f814f76 100644 --- a/examples/AspNetCore/OData/SomeODataOpenApiExample/Program.cs +++ b/examples/AspNetCore/OData/SomeODataOpenApiExample/Program.cs @@ -3,14 +3,14 @@ using Asp.Versioning.Conventions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.OData; -using Microsoft.Extensions.Options; -using Swashbuckle.AspNetCore.SwaggerGen; +using Scalar.AspNetCore; +using System.Reflection; using static Microsoft.AspNetCore.OData.Query.AllowedQueryOptions; using static System.Text.Json.JsonNamingPolicy; -var builder = WebApplication.CreateBuilder( args ); +[assembly: AssemblyDescription( "An example API" )] -// Add services to the container. +var builder = WebApplication.CreateBuilder( args ); // note: this example application intentionally only illustrates the // bare minimum configuration for OpenAPI with partial OData support. @@ -42,44 +42,29 @@ .Allow( Skip | Count ) .AllowTop( 100 ) .AllowOrderBy( "title", "published" ); - } ); - -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddTransient, ConfigureSwaggerOptions>(); -builder.Services.AddSwaggerGen( - options => - { - // add a custom operation filter which sets default values - options.OperationFilter(); - - var fileName = typeof( Program ).Assembly.GetName().Name + ".xml"; - var filePath = Path.Combine( AppContext.BaseDirectory, fileName ); - - // integrate xml comments - options.IncludeXmlComments( filePath ); - } ); + } ) + .AddOpenApi( options => options.Document.AddScalarTransformers() ); var app = builder.Build(); -// Configure the HTTP request pipeline. - -app.UseSwagger(); 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.MapOpenApi().WithDocumentPerVersion(); + app.MapScalarApiReference( + options => + { + var descriptions = app.DescribeApiVersions(); + + for ( var i = 0; i < descriptions.Count; i++ ) + { + var description = descriptions[i]; + var isDefault = i == descriptions.Count - 1; + + options.AddDocument( description.GroupName, description.GroupName, isDefault: isDefault ); + } + } ); } + app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/Properties/launchSettings.json b/examples/AspNetCore/OData/SomeODataOpenApiExample/Properties/launchSettings.json index 3c74a485..3c7843d2 100644 --- a/examples/AspNetCore/OData/SomeODataOpenApiExample/Properties/launchSettings.json +++ b/examples/AspNetCore/OData/SomeODataOpenApiExample/Properties/launchSettings.json @@ -3,7 +3,7 @@ "SomeODataOpenApiExample": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "scalar", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/SomeODataOpenApiExample.csproj b/examples/AspNetCore/OData/SomeODataOpenApiExample/SomeODataOpenApiExample.csproj index 3bffcf5e..d6b8d5a0 100644 --- a/examples/AspNetCore/OData/SomeODataOpenApiExample/SomeODataOpenApiExample.csproj +++ b/examples/AspNetCore/OData/SomeODataOpenApiExample/SomeODataOpenApiExample.csproj @@ -1,15 +1,23 @@  + net10.0 + Example API true - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - + + \ No newline at end of file diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/SwaggerDefaultValues.cs b/examples/AspNetCore/OData/SomeODataOpenApiExample/SwaggerDefaultValues.cs deleted file mode 100644 index fbf06c2f..00000000 --- a/examples/AspNetCore/OData/SomeODataOpenApiExample/SwaggerDefaultValues.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace ApiVersioning.Examples; - -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; -using System.Text.Json; - -/// -/// Represents the OpenAPI/Swashbuckle operation filter used to document the implicit API version parameter. -/// -/// This is only required due to bugs in the . -/// Once they are fixed and published, this class can be removed. -public class SwaggerDefaultValues : IOperationFilter -{ - /// - /// Applies the filter to the specified operation using the given context. - /// - /// The operation to apply the filter to. - /// The current operation filter context. - public void Apply( OpenApiOperation operation, OperationFilterContext context ) - { - var apiDescription = context.ApiDescription; - - operation.Deprecated |= apiDescription.IsDeprecated(); - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077 - foreach ( var responseType in context.ApiDescription.SupportedResponseTypes ) - { - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387 - var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); - var response = operation.Responses[responseKey]; - - foreach ( var contentType in response.Content.Keys ) - { - if ( !responseType.ApiResponseFormats.Any( x => x.MediaType == contentType ) ) - { - response.Content.Remove( contentType ); - } - } - } - - if ( operation.Parameters == null ) - { - return; - } - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 - foreach ( var parameter in operation.Parameters ) - { - var description = apiDescription.ParameterDescriptions.First( p => p.Name == parameter.Name ); - - parameter.Description ??= description.ModelMetadata?.Description; - - if ( parameter.Schema.Default == null && description.DefaultValue != null ) - { - // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330 - var json = JsonSerializer.Serialize( description.DefaultValue, description.ModelMetadata.ModelType ); - parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson( json ); - } - - parameter.Required |= description.IsRequired; - } - } -} \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/BasicExample/BasicExample.csproj b/examples/AspNetCore/WebApi/BasicExample/BasicExample.csproj index f283af35..8a8854b8 100644 --- a/examples/AspNetCore/WebApi/BasicExample/BasicExample.csproj +++ b/examples/AspNetCore/WebApi/BasicExample/BasicExample.csproj @@ -1,7 +1,11 @@  + + net10.0 + + - + \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/ByNamespaceExample/ByNamespaceExample.csproj b/examples/AspNetCore/WebApi/ByNamespaceExample/ByNamespaceExample.csproj index f283af35..8a8854b8 100644 --- a/examples/AspNetCore/WebApi/ByNamespaceExample/ByNamespaceExample.csproj +++ b/examples/AspNetCore/WebApi/ByNamespaceExample/ByNamespaceExample.csproj @@ -1,7 +1,11 @@  + + net10.0 + + - + \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/ConventionsExample/ConventionsExample.csproj b/examples/AspNetCore/WebApi/ConventionsExample/ConventionsExample.csproj index f283af35..8a8854b8 100644 --- a/examples/AspNetCore/WebApi/ConventionsExample/ConventionsExample.csproj +++ b/examples/AspNetCore/WebApi/ConventionsExample/ConventionsExample.csproj @@ -1,7 +1,11 @@  + + net10.0 + + - + \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/MinimalApiExample/MinimalApiExample.csproj b/examples/AspNetCore/WebApi/MinimalApiExample/MinimalApiExample.csproj index 3fc9fc03..55862198 100644 --- a/examples/AspNetCore/WebApi/MinimalApiExample/MinimalApiExample.csproj +++ b/examples/AspNetCore/WebApi/MinimalApiExample/MinimalApiExample.csproj @@ -1,11 +1,12 @@  + net10.0 enable - + diff --git a/examples/AspNetCore/WebApi/MinimalApiExample/Program.cs b/examples/AspNetCore/WebApi/MinimalApiExample/Program.cs index 4282c9f5..a37595fa 100644 --- a/examples/AspNetCore/WebApi/MinimalApiExample/Program.cs +++ b/examples/AspNetCore/WebApi/MinimalApiExample/Program.cs @@ -1,3 +1,5 @@ +using Asp.Versioning; + var builder = WebApplication.CreateBuilder( args ); // Add services to the container. @@ -36,7 +38,7 @@ var v2 = forecast.MapGroup( "/weatherforecast" ) .HasApiVersion( 2.0 ); -v2.MapGet( "/", () => +v2.MapGet( "/", ( ApiVersion version ) => { return Enumerable.Range( 0, summaries.Length ).Select( index => new WeatherForecast diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs deleted file mode 100644 index 5a8cbf51..00000000 --- a/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs +++ /dev/null @@ -1,162 +0,0 @@ -namespace ApiVersioning.Examples; - -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; - -/// -/// Configures the Swagger generation options. -/// -/// This allows API versioning to define a Swagger document per API version after the -/// service has been resolved from the service container. -public class ConfigureSwaggerOptions : IConfigureOptions -{ - private readonly IApiVersionDescriptionProvider provider; - - /// - /// Initializes a new instance of the class. - /// - /// The provider used to generate Swagger documents. - public ConfigureSwaggerOptions( IApiVersionDescriptionProvider provider ) => this.provider = provider; - - /// - public void Configure( SwaggerGenOptions options ) - { - // add a swagger document for each discovered API version - // note: you might choose to skip or document deprecated API versions differently - foreach ( var description in provider.ApiVersionDescriptions ) - { - options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) ); - } - } - - private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription description ) - { - var text = new StringBuilder( "An example application with OpenAPI, Swashbuckle, and API versioning." ); - var info = new OpenApiInfo() - { - Title = "Example API", - Version = description.ApiVersion.ToString(), - Contact = new OpenApiContact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" }, - License = new OpenApiLicense() { Name = "MIT", Url = new Uri( "https://opensource.org/licenses/MIT" ) } - }; - - if ( description.IsDeprecated ) - { - text.Append( " This API version has been deprecated." ); - } - - if ( description.DeprecationPolicy is { } deprecationPolicy ) - { - if ( deprecationPolicy.Date is { } when ) - { - if ( when < DateTime.Now ) - { - text.Append( " The API has been deprecated on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - else - { - text.Append( " The API will be deprecated on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - } - - if ( deprecationPolicy.HasLinks ) - { - text.AppendLine(); - - var rendered = false; - - foreach ( var link in deprecationPolicy.Links ) - { - if ( link.Type == "text/html" ) - { - if ( !rendered ) - { - text.Append( "

Links

" ); - } - } - } - - if ( description.SunsetPolicy is { } sunsetPolicy ) - { - if ( sunsetPolicy.Date is { } when ) - { - if ( when < DateTime.Now ) - { - text.Append( " The API has been sunset on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - else - { - text.Append( " The API will be sunset on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - } - - if ( sunsetPolicy.HasLinks ) - { - text.AppendLine(); - - var rendered = false; - - foreach ( var link in sunsetPolicy.Links ) - { - if ( link.Type == "text/html" ) - { - if ( !rendered ) - { - text.Append( "

Links

" ); - } - } - } - - text.Append( "

Additional Information

" ); - info.Description = text.ToString(); - - return info; - } -} \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/MinimalOpenApiExample.csproj b/examples/AspNetCore/WebApi/MinimalOpenApiExample/MinimalOpenApiExample.csproj index d2daf6ff..9b0fe311 100644 --- a/examples/AspNetCore/WebApi/MinimalOpenApiExample/MinimalOpenApiExample.csproj +++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/MinimalOpenApiExample.csproj @@ -1,11 +1,22 @@  + + net10.0 + Example API + true + + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - + \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs index 909d8261..dff69bda 100644 --- a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs +++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs @@ -1,18 +1,13 @@ -using ApiVersioning.Examples; using Asp.Versioning; -using Microsoft.Extensions.Options; -using Swashbuckle.AspNetCore.SwaggerGen; -using OrderV1 = ApiVersioning.Examples.Models.V1.Order; -using OrderV2 = ApiVersioning.Examples.Models.V2.Order; -using OrderV3 = ApiVersioning.Examples.Models.V3.Order; -using PersonV1 = ApiVersioning.Examples.Models.V1.Person; -using PersonV2 = ApiVersioning.Examples.Models.V2.Person; -using PersonV3 = ApiVersioning.Examples.Models.V3.Person; +using Scalar.AspNetCore; +using ApiVersioning.Examples.Services; +using System.Reflection; + +[assembly: AssemblyDescription( "An example API" )] var builder = WebApplication.CreateBuilder( args ); var services = builder.Services; -// Add services to the container. services.AddProblemDetails(); services.AddEndpointsApiExplorer(); services.AddApiVersioning( @@ -22,10 +17,16 @@ // "api-supported-versions" and "api-deprecated-versions" options.ReportApiVersions = true; + options.Policies.Deprecate( 0.9 ) + .Effective( DateTimeOffset.Now ) + .Link( "policy.html" ) + .Title( "Version Deprecation Policy" ) + .Type( "text/html" ); + options.Policies.Sunset( 0.9 ) .Effective( DateTimeOffset.Now.AddDays( 60 ) ) .Link( "policy.html" ) - .Title( "Versioning Policy" ) + .Title( "Version Sunset Policy" ) .Type( "text/html" ); } ) .AddApiExplorer( @@ -39,245 +40,29 @@ // can also be used to control the format of the API version in route templates options.SubstituteApiVersionInUrl = true; } ) - // this enables binding ApiVersion as a endpoint callback parameter. if you don't use it, then - // you should remove this configuration. - .EnableApiVersionBinding(); -services.AddTransient, ConfigureSwaggerOptions>(); -services.AddSwaggerGen( options => options.OperationFilter() ); + .AddOpenApi( options => options.Document.AddScalarTransformers() ); -// Configure the HTTP request pipeline. var app = builder.Build(); -var orders = app.NewVersionedApi( "Orders" ); -var people = app.NewVersionedApi( "People" ); - -// 1.0 -var ordersV1 = orders.MapGroup( "/api/orders" ) - .HasDeprecatedApiVersion( 0.9 ) - .HasApiVersion( 1.0 ); - -ordersV1.MapGet( "/{id:int}", ( int id ) => new OrderV1() { Id = id, Customer = "John Doe" } ) - .Produces() - .Produces( 404 ); - -ordersV1.MapPost( "/", ( HttpRequest request, OrderV1 order ) => - { - order.Id = 42; - var scheme = request.Scheme; - var host = request.Host; - var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/{order.Id}" ); - return Results.Created( location, order ); - } ) - .Accepts( "application/json" ) - .Produces( 201 ) - .Produces( 400 ) - .MapToApiVersion( 1.0 ); - -ordersV1.MapPatch( "/{id:int}", ( int id, OrderV1 order ) => Results.NoContent() ) - .Accepts( "application/json" ) - .Produces( 204 ) - .Produces( 400 ) - .Produces( 404 ) - .MapToApiVersion( 1.0 ); - -// 2.0 -var ordersV2 = orders.MapGroup( "/api/orders" ) - .HasApiVersion( 2.0 ); - -ordersV2.MapGet( "/", () => - new OrderV2[] - { - new(){ Id = 1, Customer = "John Doe" }, - new(){ Id = 2, Customer = "Bob Smith" }, - new(){ Id = 3, Customer = "Jane Doe", EffectiveDate = DateTimeOffset.UtcNow.AddDays( 7d ) }, - } ) - .Produces>() - .Produces( 404 ); - -ordersV2.MapGet( "/{id:int}", ( int id ) => new OrderV2() { Id = id, Customer = "John Doe" } ) - .Produces() - .Produces( 404 ); - -ordersV2.MapPost( "/", ( HttpRequest request, OrderV2 order ) => - { - order.Id = 42; - var scheme = request.Scheme; - var host = request.Host; - var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/{order.Id}" ); - return Results.Created( location, order ); - } ) - .Accepts( "application/json" ) - .Produces( 201 ) - .Produces( 400 ); - - -ordersV2.MapPatch( "/{id:int}", ( int id, OrderV2 order ) => Results.NoContent() ) - .Accepts( "application/json" ) - .Produces( 204 ) - .Produces( 400 ) - .Produces( 404 ); - -// 3.0 -var ordersV3 = orders.MapGroup( "/api/orders" ) - .HasApiVersion( 3.0 ); -ordersV3.MapGet( "/", () => - new OrderV3[] - { - new(){ Id = 1, Customer = "John Doe" }, - new(){ Id = 2, Customer = "Bob Smith" }, - new(){ Id = 3, Customer = "Jane Doe", EffectiveDate = DateTimeOffset.UtcNow.AddDays( 7d ) }, - } ) - .Produces>(); +app.MapOrders().ToV1().ToV2().ToV3(); +app.MapPeople().ToV1().ToV2().ToV3(); -ordersV3.MapGet( "/{id:int}", ( int id ) => new OrderV3() { Id = id, Customer = "John Doe" } ) - .Produces() - .Produces( 404 ); - -ordersV3.MapPost( "/", ( HttpRequest request, OrderV3 order ) => - { - order.Id = 42; - var scheme = request.Scheme; - var host = request.Host; - var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/{order.Id}" ); - return Results.Created( location, order ); - } ) - .Accepts( "application/json" ) - .Produces( 201 ) - .Produces( 400 ); - -ordersV3.MapDelete( "/{id:int}", ( int id ) => Results.NoContent() ) - .Produces( 204 ); - -// 1.0 -var peopleV1 = people.MapGroup( "/api/v{version:apiVersion}/people" ) - .HasDeprecatedApiVersion( 0.9 ) - .HasApiVersion( 1.0 ); - -peopleV1.MapGet( "/{id:int}", ( int id ) => - new PersonV1() - { - Id = id, - FirstName = "John", - LastName = "Doe", - } ) - .Produces() - .Produces( 404 ); - -// 2.0 -var peopleV2 = people.MapGroup( "/api/v{version:apiVersion}/people" ) - .HasApiVersion( 2.0 ); - -peopleV2.MapGet( "/", () => - new PersonV2[] - { - new() - { - Id = 1, - FirstName = "John", - LastName = "Doe", - Email = "john.doe@somewhere.com", - }, - new() - { - Id = 2, - FirstName = "Bob", - LastName = "Smith", - Email = "bob.smith@somewhere.com", - }, - new() - { - Id = 3, - FirstName = "Jane", - LastName = "Doe", - Email = "jane.doe@somewhere.com", - }, - } ) - .Produces>(); - -peopleV2.MapGet( "/{id:int}", ( int id ) => - new PersonV2() - { - Id = id, - FirstName = "John", - LastName = "Doe", - Email = "john.doe@somewhere.com", - } ) - .Produces() - .Produces( 404 ); - -// 3.0 -var peopleV3 = people.MapGroup( "/api/v{version:apiVersion}/people" ) - .HasApiVersion( 3.0 ); - -peopleV3.MapGet( "/", () => - new PersonV3[] - { - new() - { - Id = 1, - FirstName = "John", - LastName = "Doe", - Email = "john.doe@somewhere.com", - Phone = "555-987-1234", - }, - new() - { - Id = 2, - FirstName = "Bob", - LastName = "Smith", - Email = "bob.smith@somewhere.com", - Phone = "555-654-4321", - }, - new() - { - Id = 3, - FirstName = "Jane", - LastName = "Doe", - Email = "jane.doe@somewhere.com", - Phone = "555-789-3456", - }, - } ) - .Produces>(); - -peopleV3.MapGet( "/{id:int}", ( int id ) => - new PersonV3() - { - Id = id, - FirstName = "John", - LastName = "Doe", - Email = "john.doe@somewhere.com", - Phone = "555-987-1234", - } ) - .Produces() - .Produces( 404 ); - -peopleV3.MapPost( "/", ( HttpRequest request, ApiVersion version, PersonV3 person ) => - { - person.Id = 42; - var scheme = request.Scheme; - var host = request.Host; - var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/v{version}/api/people/{person.Id}" ); - return Results.Created( location, person ); - } ) - .Accepts( "application/json" ) - .Produces( 201 ) - .Produces( 400 ); - -app.UseSwagger(); if ( app.Environment.IsDevelopment() ) { - app.UseSwaggerUI( + app.MapOpenApi().WithDocumentPerVersion(); + app.MapScalarApiReference( options => { var descriptions = app.DescribeApiVersions(); - // build a swagger endpoint for each discovered API version - foreach ( var description in descriptions ) + for ( var i = 0; i < descriptions.Count; i++ ) { - var url = $"/swagger/{description.GroupName}/swagger.json"; - var name = description.GroupName.ToUpperInvariant(); - options.SwaggerEndpoint( url, name ); + var description = descriptions[i]; + var isDefault = i == descriptions.Count - 1; + + options.AddDocument( description.GroupName, description.GroupName, isDefault: isDefault ); } } ); } + app.Run(); \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Properties/launchSettings.json b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Properties/launchSettings.json index 1daa6c5a..31870801 100644 --- a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Properties/launchSettings.json +++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Properties/launchSettings.json @@ -12,7 +12,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "scalar", "applicationUrl": "https://localhost:5145;http://localhost:5144", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -21,7 +21,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "scalar", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/ApiBuilder.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/ApiBuilder.cs new file mode 100644 index 00000000..e8425d13 --- /dev/null +++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/ApiBuilder.cs @@ -0,0 +1,22 @@ +namespace ApiVersioning.Examples.Services; + +/// +/// Represents an API builder. +/// +public static class ApiBuilder +{ + extension( IEndpointRouteBuilder app ) + { + /// + /// Creates and returns an API builder for the Orders service. + /// + /// A new Orders API builder. + public OrdersApiBuilder MapOrders() => new( app.NewVersionedApi( "Orders" ) ); + + /// + /// Creates and returns an API builder for the Orders service. + /// + /// A new Orders API builder. + public PeopleApiBuilder MapPeople() => new( app.NewVersionedApi( "People" ) ); + } +} diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/Orders.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/Orders.cs new file mode 100644 index 00000000..9d30085a --- /dev/null +++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/Orders.cs @@ -0,0 +1,271 @@ +using Asp.Versioning; + +namespace ApiVersioning.Examples.Services; + +/// +/// Provides the endpoint extensions for the Orders service. +/// +public static class Orders +{ + extension( OrdersApiBuilder apiBuilder ) + { + /// + /// Maps the Orders APIs for 1.0. + /// + /// The next builder. + public VersionedApiBuilder ToV1() + { + var orders = new VersionedApiBuilder( apiBuilder.Endpoints ); + var builder = orders.Endpoints; + var api = builder.MapGroup( "/api/orders" ) + .HasDeprecatedApiVersion( 0.9 ) + .HasApiVersion( 1.0 ); + + api.MapGet( "/{id:int}", V1.Get ) + .Produces( 200, orders.ModelType ) + .Produces( 404 ); + + api.MapPost( "/", V1.Post ) + .Accepts( orders.ModelType, "application/json" ) + .Produces( 201, orders.ModelType ) + .Produces( 400 ) + .MapToApiVersion( 1.0 ); + + api.MapPatch( "/{id:int}", V1.Patch ) + .Accepts( orders.ModelType, "application/json" ) + .Produces( 204 ) + .Produces( 400 ) + .Produces( 404 ) + .MapToApiVersion( 1.0 ); + + return new( builder ); + } + } + + extension( VersionedApiBuilder orders ) + { + /// + /// Maps the Orders APIs for 2.0. + /// + /// The next builder. + public VersionedApiBuilder ToV2() + { + var builder = orders.Endpoints; + var api = builder.MapGroup( "/api/orders" ) + .HasApiVersion( 2.0 ); + + api.MapGet( "/", V2.GetAll ) + .Produces( 200, orders.EnumerableModelType ) + .Produces( 404 ); + + api.MapGet( "/{id:int}", V2.GetById ) + .Produces( 200, orders.ModelType ) + .Produces( 404 ); + + api.MapPost( "/", V2.Post ) + .Accepts( orders.ModelType, "application/json" ) + .Produces( 201, orders.ModelType ) + .Produces( 400 ); + + api.MapPatch( "/{id:int}", V2.Patch ) + .Accepts( orders.ModelType, "application/json" ) + .Produces( 204 ) + .Produces( 400 ) + .Produces( 404 ); + + return new( builder ); + } + } + + extension( VersionedApiBuilder orders ) + { + /// + /// Maps the Orders APIs for 3.0. + /// + public void ToV3() + { + var builder = orders.Endpoints; + var api = builder.MapGroup( "/api/orders" ) + .HasApiVersion( 3.0 ); + + api.MapGet( "/", V3.GetAll ) + .Produces( 200, orders.EnumerableModelType ); + + api.MapGet( "/{id:int}", V3.GetById ) + .Produces( 200, orders.ModelType ) + .Produces( 404 ); + + api.MapPost( "/", V3.Post ) + .Accepts( orders.ModelType, "application/json" ) + .Produces( 201, orders.ModelType ) + .Produces( 400 ); + + api.MapDelete( "/{id:int}", V3.Delete ) + .Produces( 204 ); + } + } + + /// + /// Represents the 1.0 Orders service. + /// + public static class V1 + { + /// + /// Get Order + /// + /// Gets a single order. + /// The requested order identifier. + /// The requested order. + /// The order was successfully retrieved. + /// The order does not exist. + public static Models.V1.Order Get( int id ) => new() { Id = id, Customer = "John Doe" }; + + /// + /// Place Order + /// + /// Places a new order. + /// + /// The order to place. + /// The created order. + /// The order was successfully placed. + /// The order is invalid. + public static IResult Post( HttpRequest request, Models.V1.Order order ) + { + order.Id = 42; + var scheme = request.Scheme; + var host = request.Host; + var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/{order.Id}" ); + return Results.Created( location, order ); + } + + /// + /// Update Order + /// + /// Updates an existing order. + /// The requested order identifier. + /// The order to update. + /// None. + /// The order was successfully updated. + /// The order is invalid. + /// The order does not exist. + public static IResult Patch( int id, Models.V1.Order order ) => Results.NoContent(); + } + + /// + /// Represents the 2.0 Orders service. + /// + public static class V2 + { + /// + /// Get Orders + /// + /// Retrieves all orders. + /// + /// All available orders. + /// The successfully retrieved orders. + public static Models.V2.Order[] GetAll( ApiVersion version ) => + [ + new (){ Id = 1, Customer = "John Doe" }, + new (){ Id = 2, Customer = "Bob Smith" }, + new (){ Id = 3, Customer = "Jane Doe", EffectiveDate = DateTimeOffset.UtcNow.AddDays( 7d ) }, + ]; + + /// + /// Get Order + /// + /// Gets a single order. + /// The requested order identifier. + /// + /// The requested order. + /// The order was successfully retrieved. + /// The order does not exist. + public static Models.V2.Order GetById( int id, ApiVersion version ) => new() { Id = id, Customer = "John Doe" }; + + /// + /// Place Order + /// + /// Places a new order. + /// + /// The order to place. + /// The created order. + /// The order was successfully placed. + /// The order is invalid. + public static IResult Post( HttpRequest request, Models.V2.Order order ) + { + order.Id = 42; + var scheme = request.Scheme; + var host = request.Host; + var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/{order.Id}" ); + return Results.Created( location, order ); + } + + /// + /// Update Order + /// + /// Updates an existing order. + /// The requested order identifier. + /// The order to update. + /// None. + /// The order was successfully updated. + /// The order is invalid. + /// The order does not exist. + public static IResult Patch( int id, Models.V2.Order order ) => Results.NoContent(); + } + + /// + /// Represents the 3.0 Orders service. + /// + public static class V3 + { + /// + /// Get Orders + /// + /// Retrieves all orders. + /// All available orders. + /// The successfully retrieved orders. + public static Models.V3.Order[] GetAll() => + [ + new (){ Id = 1, Customer = "John Doe" }, + new (){ Id = 2, Customer = "Bob Smith" }, + new (){ Id = 3, Customer = "Jane Doe", EffectiveDate = DateTimeOffset.UtcNow.AddDays( 7d ) }, + ]; + + /// + /// Get Order + /// + /// Gets a single order. + /// The requested order identifier. + /// The requested order. + /// The order was successfully retrieved. + /// The order does not exist. + public static Models.V3.Order GetById( int id ) => new() { Id = id, Customer = "John Doe" }; + + /// + /// Place Order + /// + /// Places a new order. + /// + /// The order to place. + /// The created order. + /// The order was successfully placed. + /// The order is invalid. + public static IResult Post( HttpRequest request, Models.V3.Order order ) + { + order.Id = 42; + var scheme = request.Scheme; + var host = request.Host; + var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/{order.Id}" ); + return Results.Created( location, order ); + } + + /// + /// Cancel Order + /// + /// Cancels an order. + /// The order to cancel. + /// None + /// The order was successfully canceled. + /// The order does not exist. + public static IResult Delete( int id ) => Results.NoContent(); + } +} \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/People.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/People.cs new file mode 100644 index 00000000..816bb5de --- /dev/null +++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/People.cs @@ -0,0 +1,231 @@ +namespace ApiVersioning.Examples.Services; + +using Asp.Versioning; + +/// +/// Provides the endpoint extensions for the People service. +/// +public static class People +{ + extension( PeopleApiBuilder apiBuilder ) + { + /// + /// Maps the People APIs for 1.0. + /// + /// The next builder. + public VersionedApiBuilder ToV1() + { + var people = new VersionedApiBuilder( apiBuilder.Endpoints ); + var builder = people.Endpoints; + var api = builder.MapGroup( "/api/v{version:apiVersion}/people" ) + .HasDeprecatedApiVersion( 0.9 ) + .HasApiVersion( 1.0 ); + + api.MapGet( "/{id:int}", V1.Get ) + .Produces( 200, people.ModelType ) + .Produces( 404 ); + + return new( builder ); + } + } + + extension( VersionedApiBuilder people ) + { + /// + /// Maps the People APIs for 2.0. + /// + /// The next builder. + public VersionedApiBuilder ToV2() + { + var builder = people.Endpoints; + var api = builder.MapGroup( "/api/v{version:apiVersion}/people" ) + .HasApiVersion( 2.0 ); + + api.MapGet( "/", V2.GetAll ) + .Produces( 200, people.EnumerableModelType ); + + api.MapGet( "/{id:int}", V2.GetById ) + .WithSummary( "Get Person" ) + .WithDescription( "Gets a single person." ) + .Produces( 200, people.ModelType ) + .Produces( 404 ); + + return new( builder ); + } + } + + extension( VersionedApiBuilder people ) + { + /// + /// Maps the Person APIs for 3.0. + /// + public void ToV3() + { + var builder = people.Endpoints; + var api = builder.MapGroup( "/api/v{version:apiVersion}/people" ) + .HasApiVersion( 3.0 ); + + api.MapGet( "/", V3.GetAll ) + .Produces( 200, people.EnumerableModelType ); + + api.MapGet( "/{id:int}", V3.GetById ) + .Produces( 200, people.ModelType ) + .Produces( 404 ); + + api.MapPost( "/", V3.Post ) + .Accepts( "application/json" ) + .Produces( 201 ) + .Produces( 400 ); + } + } + + /// + /// Represents the 1.0 People service. + /// + public static class V1 + { + /// + /// Get Person + /// + /// Gets a single person. + /// The requested person identifier. + /// The requested person. + /// The person was successfully retrieved. + /// The person does not exist. + public static Models.V1.Person Get( int id ) => new() + { + Id = id, + FirstName = "John", + LastName = "Doe", + }; + } + + /// + /// Represents the 2.0 People service. + /// + public static class V2 + { + /// + /// Get People + /// + /// Gets all people. + /// All available people. + /// The successfully retrieved people. + public static Models.V2.Person[] GetAll() => + [ + new() + { + Id = 1, + FirstName = "John", + LastName = "Doe", + Email = "john.doe@somewhere.com", + }, + new() + { + Id = 2, + FirstName = "Bob", + LastName = "Smith", + Email = "bob.smith@somewhere.com", + }, + new() + { + Id = 3, + FirstName = "Jane", + LastName = "Doe", + Email = "jane.doe@somewhere.com", + }, + ]; + + /// + /// Get Person + /// + /// Gets a single person. + /// The requested person identifier. + /// The requested person. + /// The person was successfully retrieved. + /// The person does not exist. + public static Models.V2.Person GetById( int id ) => new() + { + Id = id, + FirstName = "John", + LastName = "Doe", + Email = "john.doe@somewhere.com", + }; + } + + /// + /// Represents the 3.0 People service. + /// + public static class V3 + { + /// + /// Get People + /// + /// Gets all people. + /// All available people. + /// The successfully retrieved people. + public static Models.V3.Person[] GetAll() => + [ + new() + { + Id = 1, + FirstName = "John", + LastName = "Doe", + Email = "john.doe@somewhere.com", + Phone = "555-987-1234", + }, + new() + { + Id = 2, + FirstName = "Bob", + LastName = "Smith", + Email = "bob.smith@somewhere.com", + Phone = "555-654-4321", + }, + new() + { + Id = 3, + FirstName = "Jane", + LastName = "Doe", + Email = "jane.doe@somewhere.com", + Phone = "555-789-3456", + }, + ]; + + /// + /// Get Person + /// + /// Gets a single person. + /// The requested person identifier. + /// The requested person. + /// The person was successfully retrieved. + /// The person does not exist. + public static Models.V3.Person GetById( int id ) => new() + { + Id = id, + FirstName = "John", + LastName = "Doe", + Email = "john.doe@somewhere.com", + Phone = "555-987-1234", + }; + + /// + /// Add Person + /// + /// Adds a new person. + /// + /// + /// The person to create. + /// The created person. + /// The person was successfully created. + /// The person was invalid. + public static IResult Post( HttpRequest request, ApiVersion version, Models.V3.Person person ) + { + person.Id = 42; + var scheme = request.Scheme; + var host = request.Host; + var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/v{version}/api/people/{person.Id}" ); + return Results.Created( location, person ); + } + } +} \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/VersionedApiBuilder.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/VersionedApiBuilder.cs new file mode 100644 index 00000000..82cde3f0 --- /dev/null +++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Services/VersionedApiBuilder.cs @@ -0,0 +1,46 @@ +namespace ApiVersioning.Examples.Services; + +using Asp.Versioning.Builder; + +/// +/// Represents the base builder implementation for a verisoned API. +/// +/// The underlying versioned endpoint route builder. + +public abstract class VersionedApiBuilder( IVersionedEndpointRouteBuilder builder ) +{ + /// + /// Gets versioned endpoint route builder. + /// + public IVersionedEndpointRouteBuilder Endpoints => builder; +} + +/// +/// Represents a versioned API builder with a model type. +/// +/// The associated model type. +/// The underlying versioned endpoint route builder. +public sealed class VersionedApiBuilder( IVersionedEndpointRouteBuilder builder ) : VersionedApiBuilder( builder ) +{ + /// + /// Gets the associated model type. + /// + public Type ModelType => typeof( T ); + + /// + /// Gets the associated type for a sequence of model types. + /// + public Type EnumerableModelType = typeof( IEnumerable ); +} + +/// +/// Represents the Orders API builder. +/// +/// The underlying versioned endpoint route builder. +public sealed class OrdersApiBuilder( IVersionedEndpointRouteBuilder builder ) : VersionedApiBuilder( builder ); + +/// +/// Represents the People API builder. +/// +/// The underlying versioned endpoint route builder. +public sealed class PeopleApiBuilder( IVersionedEndpointRouteBuilder builder ) : VersionedApiBuilder( builder ); \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/SwaggerDefaultValues.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/SwaggerDefaultValues.cs deleted file mode 100644 index 0bcae0a8..00000000 --- a/examples/AspNetCore/WebApi/MinimalOpenApiExample/SwaggerDefaultValues.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace ApiVersioning.Examples; - -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; -using System.Text.Json; - -/// -/// Represents the OpenAPI/Swashbuckle operation filter used to document information provided, but not used. -/// -/// This is only required due to bugs in the . -/// Once they are fixed and published, this class can be removed. -public class SwaggerDefaultValues : IOperationFilter -{ - /// - public void Apply( OpenApiOperation operation, OperationFilterContext context ) - { - var apiDescription = context.ApiDescription; - - operation.Deprecated |= apiDescription.IsDeprecated(); - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077 - foreach ( var responseType in context.ApiDescription.SupportedResponseTypes ) - { - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387 - var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); - var response = operation.Responses[responseKey]; - - foreach ( var contentType in response.Content.Keys ) - { - if ( !responseType.ApiResponseFormats.Any( x => x.MediaType == contentType ) ) - { - response.Content.Remove( contentType ); - } - } - } - - if ( operation.Parameters == null ) - { - return; - } - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 - foreach ( var parameter in operation.Parameters ) - { - var description = apiDescription.ParameterDescriptions.First( p => p.Name == parameter.Name ); - - parameter.Description ??= description.ModelMetadata?.Description; - - if ( parameter.Schema.Default == null && - description.DefaultValue != null && - description.DefaultValue is not DBNull && - description.ModelMetadata is ModelMetadata modelMetadata ) - { - // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330 - var json = JsonSerializer.Serialize( description.DefaultValue, modelMetadata.ModelType ); - parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson( json ); - } - - parameter.Required |= description.IsRequired; - } - } -} \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs deleted file mode 100644 index 5a8cbf51..00000000 --- a/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs +++ /dev/null @@ -1,162 +0,0 @@ -namespace ApiVersioning.Examples; - -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; - -/// -/// Configures the Swagger generation options. -/// -/// This allows API versioning to define a Swagger document per API version after the -/// service has been resolved from the service container. -public class ConfigureSwaggerOptions : IConfigureOptions -{ - private readonly IApiVersionDescriptionProvider provider; - - /// - /// Initializes a new instance of the class. - /// - /// The provider used to generate Swagger documents. - public ConfigureSwaggerOptions( IApiVersionDescriptionProvider provider ) => this.provider = provider; - - /// - public void Configure( SwaggerGenOptions options ) - { - // add a swagger document for each discovered API version - // note: you might choose to skip or document deprecated API versions differently - foreach ( var description in provider.ApiVersionDescriptions ) - { - options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) ); - } - } - - private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription description ) - { - var text = new StringBuilder( "An example application with OpenAPI, Swashbuckle, and API versioning." ); - var info = new OpenApiInfo() - { - Title = "Example API", - Version = description.ApiVersion.ToString(), - Contact = new OpenApiContact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" }, - License = new OpenApiLicense() { Name = "MIT", Url = new Uri( "https://opensource.org/licenses/MIT" ) } - }; - - if ( description.IsDeprecated ) - { - text.Append( " This API version has been deprecated." ); - } - - if ( description.DeprecationPolicy is { } deprecationPolicy ) - { - if ( deprecationPolicy.Date is { } when ) - { - if ( when < DateTime.Now ) - { - text.Append( " The API has been deprecated on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - else - { - text.Append( " The API will be deprecated on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - } - - if ( deprecationPolicy.HasLinks ) - { - text.AppendLine(); - - var rendered = false; - - foreach ( var link in deprecationPolicy.Links ) - { - if ( link.Type == "text/html" ) - { - if ( !rendered ) - { - text.Append( "

Links

" ); - } - } - } - - if ( description.SunsetPolicy is { } sunsetPolicy ) - { - if ( sunsetPolicy.Date is { } when ) - { - if ( when < DateTime.Now ) - { - text.Append( " The API has been sunset on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - else - { - text.Append( " The API will be sunset on " ) - .Append( when.Date.ToShortDateString() ) - .Append( '.' ); - } - } - - if ( sunsetPolicy.HasLinks ) - { - text.AppendLine(); - - var rendered = false; - - foreach ( var link in sunsetPolicy.Links ) - { - if ( link.Type == "text/html" ) - { - if ( !rendered ) - { - text.Append( "

Links

" ); - } - } - } - - text.Append( "

Additional Information

" ); - info.Description = text.ToString(); - - return info; - } -} \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/OpenApiExample/OpenApiExample.csproj b/examples/AspNetCore/WebApi/OpenApiExample/OpenApiExample.csproj index 2be84679..9b0fe311 100644 --- a/examples/AspNetCore/WebApi/OpenApiExample/OpenApiExample.csproj +++ b/examples/AspNetCore/WebApi/OpenApiExample/OpenApiExample.csproj @@ -1,15 +1,22 @@  + net10.0 + Example API true - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - + \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/OpenApiExample/Program.cs b/examples/AspNetCore/WebApi/OpenApiExample/Program.cs index 454c60d7..31cc7d3f 100644 --- a/examples/AspNetCore/WebApi/OpenApiExample/Program.cs +++ b/examples/AspNetCore/WebApi/OpenApiExample/Program.cs @@ -1,14 +1,12 @@ -using ApiVersioning.Examples; -using Asp.Versioning; -using Microsoft.Extensions.Options; -using Swashbuckle.AspNetCore.SwaggerGen; +using Asp.Versioning; +using Scalar.AspNetCore; +using System.Reflection; [assembly: Microsoft.AspNetCore.Mvc.ApiController] +[assembly: AssemblyDescription( "An example API" )] var builder = WebApplication.CreateBuilder( args ); -// Add services to the container. - builder.Services.AddControllers(); builder.Services.AddProblemDetails(); builder.Services.AddApiVersioning( @@ -18,10 +16,16 @@ // "api-supported-versions" and "api-deprecated-versions" options.ReportApiVersions = true; + options.Policies.Deprecate( 0.9 ) + .Effective( DateTimeOffset.Now ) + .Link( "policy.html" ) + .Title( "Version Deprecation Policy" ) + .Type( "text/html" ); + options.Policies.Sunset( 0.9 ) .Effective( DateTimeOffset.Now.AddDays( 60 ) ) .Link( "policy.html" ) - .Title( "Versioning Policy" ) + .Title( "Version Sunset Policy" ) .Type( "text/html" ); } ) .AddMvc() @@ -35,43 +39,27 @@ // note: this option is only necessary when versioning by url segment. the SubstitutionFormat // can also be used to control the format of the API version in route templates options.SubstituteApiVersionInUrl = true; - } ); - -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddTransient, ConfigureSwaggerOptions>(); -builder.Services.AddSwaggerGen( - options => - { - // add a custom operation filter which sets default values - options.OperationFilter(); - - var fileName = typeof( Program ).Assembly.GetName().Name + ".xml"; - var filePath = Path.Combine( AppContext.BaseDirectory, fileName ); - - // integrate xml comments - options.IncludeXmlComments( filePath ); - } ); + } ) + .AddOpenApi( options => options.Document.AddScalarTransformers() ); var app = builder.Build(); -// Configure the HTTP request pipeline. - -app.UseSwagger(); 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.MapOpenApi().WithDocumentPerVersion(); + app.MapScalarApiReference( + options => + { + var descriptions = app.DescribeApiVersions(); + + for ( var i = 0; i < descriptions.Count; i++ ) + { + var description = descriptions[i]; + var isDefault = i == descriptions.Count - 1; + + options.AddDocument( description.GroupName, description.GroupName, isDefault: isDefault ); + } + } ); } app.UseHttpsRedirection(); diff --git a/examples/AspNetCore/WebApi/OpenApiExample/Properties/launchSettings.json b/examples/AspNetCore/WebApi/OpenApiExample/Properties/launchSettings.json index 07ffb91d..094861f9 100644 --- a/examples/AspNetCore/WebApi/OpenApiExample/Properties/launchSettings.json +++ b/examples/AspNetCore/WebApi/OpenApiExample/Properties/launchSettings.json @@ -12,7 +12,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "scalar", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -21,7 +21,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "scalar", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/examples/AspNetCore/WebApi/OpenApiExample/SwaggerDefaultValues.cs b/examples/AspNetCore/WebApi/OpenApiExample/SwaggerDefaultValues.cs deleted file mode 100644 index 0bcae0a8..00000000 --- a/examples/AspNetCore/WebApi/OpenApiExample/SwaggerDefaultValues.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace ApiVersioning.Examples; - -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; -using System.Text.Json; - -/// -/// Represents the OpenAPI/Swashbuckle operation filter used to document information provided, but not used. -/// -/// This is only required due to bugs in the . -/// Once they are fixed and published, this class can be removed. -public class SwaggerDefaultValues : IOperationFilter -{ - /// - public void Apply( OpenApiOperation operation, OperationFilterContext context ) - { - var apiDescription = context.ApiDescription; - - operation.Deprecated |= apiDescription.IsDeprecated(); - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077 - foreach ( var responseType in context.ApiDescription.SupportedResponseTypes ) - { - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387 - var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); - var response = operation.Responses[responseKey]; - - foreach ( var contentType in response.Content.Keys ) - { - if ( !responseType.ApiResponseFormats.Any( x => x.MediaType == contentType ) ) - { - response.Content.Remove( contentType ); - } - } - } - - if ( operation.Parameters == null ) - { - return; - } - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 - foreach ( var parameter in operation.Parameters ) - { - var description = apiDescription.ParameterDescriptions.First( p => p.Name == parameter.Name ); - - parameter.Description ??= description.ModelMetadata?.Description; - - if ( parameter.Schema.Default == null && - description.DefaultValue != null && - description.DefaultValue is not DBNull && - description.ModelMetadata is ModelMetadata modelMetadata ) - { - // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330 - var json = JsonSerializer.Serialize( description.DefaultValue, modelMetadata.ModelType ); - parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson( json ); - } - - parameter.Required |= description.IsRequired; - } - } -} \ No newline at end of file diff --git a/examples/AspNetCore/WebApi/OpenApiExample/V1/Controllers/OrdersController.cs b/examples/AspNetCore/WebApi/OpenApiExample/V1/Controllers/OrdersController.cs index 8cbb2328..8ef17829 100644 --- a/examples/AspNetCore/WebApi/OpenApiExample/V1/Controllers/OrdersController.cs +++ b/examples/AspNetCore/WebApi/OpenApiExample/V1/Controllers/OrdersController.cs @@ -13,8 +13,9 @@ public class OrdersController : ControllerBase { /// - /// Gets a single order. + /// Get Order /// + /// Gets a single order. /// The requested order identifier. /// The requested order. /// The order was successfully retrieved. @@ -26,8 +27,9 @@ public class OrdersController : ControllerBase public IActionResult Get( int id ) => Ok( new Order() { Id = id, Customer = "John Doe" } ); /// - /// Places a new order. + /// Place Order /// + /// Places a new order. /// The order to place. /// The created order. /// The order was successfully placed. @@ -45,8 +47,9 @@ public IActionResult Post( [FromBody] Order order ) } /// - /// Updates an existing order. + /// Update Order /// + /// Updates an existing order. /// The requested order identifier. /// The order to update. /// None. diff --git a/examples/AspNetCore/WebApi/OpenApiExample/V1/Controllers/PeopleController.cs b/examples/AspNetCore/WebApi/OpenApiExample/V1/Controllers/PeopleController.cs index f12c12ea..02688456 100644 --- a/examples/AspNetCore/WebApi/OpenApiExample/V1/Controllers/PeopleController.cs +++ b/examples/AspNetCore/WebApi/OpenApiExample/V1/Controllers/PeopleController.cs @@ -13,8 +13,9 @@ public class PeopleController : ControllerBase { /// - /// Gets a single person. + /// Get Person /// + /// Gets a single person. /// The requested person identifier. /// The requested person. /// The person was successfully retrieved. diff --git a/examples/AspNetCore/WebApi/OpenApiExample/V2/Controllers/OrdersController.cs b/examples/AspNetCore/WebApi/OpenApiExample/V2/Controllers/OrdersController.cs index 56f27ed7..06a512f7 100644 --- a/examples/AspNetCore/WebApi/OpenApiExample/V2/Controllers/OrdersController.cs +++ b/examples/AspNetCore/WebApi/OpenApiExample/V2/Controllers/OrdersController.cs @@ -13,8 +13,9 @@ public class OrdersController : ControllerBase { /// - /// Retrieves all orders. + /// Get Orders /// + /// Retrieves all orders. /// All available orders. /// The successfully retrieved orders. [HttpGet] @@ -33,8 +34,9 @@ public IActionResult Get() } /// - /// Gets a single order. + /// Get Order /// + /// Gets a single order. /// The requested order identifier. /// The requested order. /// The order was successfully retrieved. @@ -46,8 +48,9 @@ public IActionResult Get() public IActionResult Get( int id ) => Ok( new Order() { Id = id, Customer = "John Doe" } ); /// - /// Places a new order. + /// Place Order /// + /// Places a new order. /// The order to place. /// The created order. /// The order was successfully placed. @@ -64,8 +67,9 @@ public IActionResult Post( [FromBody] Order order ) } /// - /// Updates an existing order. + /// Update Order /// + /// Updates an existing order. /// The requested order identifier. /// The order to update. /// None. diff --git a/examples/AspNetCore/WebApi/OpenApiExample/V2/Controllers/PeopleController.cs b/examples/AspNetCore/WebApi/OpenApiExample/V2/Controllers/PeopleController.cs index 36ca9dae..326c1a31 100644 --- a/examples/AspNetCore/WebApi/OpenApiExample/V2/Controllers/PeopleController.cs +++ b/examples/AspNetCore/WebApi/OpenApiExample/V2/Controllers/PeopleController.cs @@ -12,8 +12,9 @@ public class PeopleController : ControllerBase { /// - /// Gets all people. + /// Get People /// + /// Gets all people. /// All available people. /// The successfully retrieved people. [HttpGet] @@ -50,8 +51,9 @@ public IActionResult Get() } /// - /// Gets a single person. + /// Get Person /// + /// Gets a single person. /// The requested person identifier. /// The requested person. /// The person was successfully retrieved. diff --git a/examples/AspNetCore/WebApi/OpenApiExample/V3/Controllers/OrdersController.cs b/examples/AspNetCore/WebApi/OpenApiExample/V3/Controllers/OrdersController.cs index 5170e12b..6d315181 100644 --- a/examples/AspNetCore/WebApi/OpenApiExample/V3/Controllers/OrdersController.cs +++ b/examples/AspNetCore/WebApi/OpenApiExample/V3/Controllers/OrdersController.cs @@ -12,8 +12,9 @@ public class OrdersController : ControllerBase { /// - /// Retrieves all orders. + /// Get Orders /// + /// Retrieves all orders. /// All available orders. /// Orders successfully retrieved. [HttpGet] @@ -32,8 +33,9 @@ public IActionResult Get() } /// - /// Gets a single order. + /// Get Order /// + /// Gets a single order. /// The requested order identifier. /// The requested order. /// The order was successfully retrieved. @@ -45,8 +47,9 @@ public IActionResult Get() public IActionResult Get( int id ) => Ok( new Order() { Id = id, Customer = "John Doe" } ); /// - /// Places a new order. + /// Place Order /// + /// Places a new order. /// The order to place. /// The created order. /// The order was successfully placed. @@ -63,10 +66,13 @@ public IActionResult Post( [FromBody] Order order ) } /// - /// Cancels an order. + /// Cancel Order /// + /// Cancels an order. /// The order to cancel. /// None + /// The order was successfully canceled. + /// The order does not exist. [HttpDelete( "{id:int}" )] [ProducesResponseType( 204 )] public IActionResult Delete( int id ) => NoContent(); diff --git a/examples/AspNetCore/WebApi/OpenApiExample/V3/Controllers/PeopleController.cs b/examples/AspNetCore/WebApi/OpenApiExample/V3/Controllers/PeopleController.cs index 05a86f2c..43f58788 100644 --- a/examples/AspNetCore/WebApi/OpenApiExample/V3/Controllers/PeopleController.cs +++ b/examples/AspNetCore/WebApi/OpenApiExample/V3/Controllers/PeopleController.cs @@ -1,8 +1,8 @@ namespace ApiVersioning.Examples.V3.Controllers; -using Microsoft.AspNetCore.Mvc; using ApiVersioning.Examples.V3.Models; using Asp.Versioning; +using Microsoft.AspNetCore.Mvc; /// /// Represents a RESTful people service. @@ -12,8 +12,9 @@ public class PeopleController : ControllerBase { /// - /// Gets all people. + /// Get People /// + /// Gets all people. /// All available people. /// The successfully retrieved people. [HttpGet] @@ -53,8 +54,9 @@ public IActionResult Get() } /// - /// Gets a single person. + /// Get Person /// + /// Gets a single person. /// The requested person identifier. /// The requested person. /// The person was successfully retrieved. @@ -74,8 +76,9 @@ public IActionResult Get( int id ) => } ); /// - /// Creates a new person. + /// Add Person /// + /// Adds a new person. /// The person to create. /// The requested API version. /// The created person. diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/AmbiguousApiVersionException.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/AmbiguousApiVersionException.cs index 4a780ad5..3a57766a 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/AmbiguousApiVersionException.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/AmbiguousApiVersionException.cs @@ -35,7 +35,7 @@ public AmbiguousApiVersionException( string message, Exception innerException ) /// The associated error message. /// The sequence of ambiguous API versions. public AmbiguousApiVersionException( string message, IEnumerable apiVersions ) - : base( message ) => this.apiVersions = apiVersions.ToArray(); + : base( message ) => this.apiVersions = [.. apiVersions]; /// /// Initializes a new instance of the class. @@ -44,7 +44,7 @@ public AmbiguousApiVersionException( string message, IEnumerable apiVers /// The sequence of ambiguous API versions. /// The inner exception that caused the current exception, if any. public AmbiguousApiVersionException( string message, IEnumerable apiVersions, Exception innerException ) - : base( message, innerException ) => this.apiVersions = apiVersions.ToArray(); + : base( message, innerException ) => this.apiVersions = [.. apiVersions]; /// /// Gets a read-only list of the ambiguous API versions. diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionMetadata.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionMetadata.cs index d7e49d28..20358ef1 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionMetadata.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionMetadata.cs @@ -104,7 +104,7 @@ public ApiVersionModel Map( ApiVersionMapping mapping ) implemented.Add( deprecated[i] ); } - mergedModel = new( apiModel, implemented.ToArray(), supported, deprecated ); + mergedModel = new( apiModel, [.. implemented], supported, deprecated ); } return mergedModel; diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModel.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModel.cs index 8dda1712..f44c9efe 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModel.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModel.cs @@ -14,8 +14,8 @@ public sealed class ApiVersionModel private const int DefaultModel = 0; private const int NeutralModel = 1; private const int EmptyModel = 2; - private static readonly IReadOnlyList emptyVersions = Array.Empty(); - private static readonly IReadOnlyList defaultVersions = new[] { ApiVersion.Default }; + private static readonly IReadOnlyList emptyVersions = []; + private static readonly IReadOnlyList defaultVersions = [ApiVersion.Default]; private static ApiVersionModel? defaultVersion; private static ApiVersionModel? neutralVersion; private static ApiVersionModel? emptyVersion; @@ -69,7 +69,7 @@ internal ApiVersionModel( ApiVersionModel original, IReadOnlyList im /// The declared version also represents the only implemented and supported API version. public ApiVersionModel( ApiVersion declaredVersion ) { - DeclaredApiVersions = new[] { declaredVersion }; + DeclaredApiVersions = [declaredVersion]; ImplementedApiVersions = DeclaredApiVersions; SupportedApiVersions = DeclaredApiVersions; DeprecatedApiVersions = emptyVersions; @@ -87,9 +87,9 @@ public ApiVersionModel( ApiVersion declaredVersion ) public ApiVersionModel( IEnumerable supportedVersions, IEnumerable deprecatedVersions ) { DeclaredApiVersions = emptyVersions; - SupportedApiVersions = supportedVersions.Distinct().OrderBy( v => v ).ToArray(); - DeprecatedApiVersions = deprecatedVersions.Distinct().OrderBy( v => v ).ToArray(); - ImplementedApiVersions = SupportedApiVersions.Union( DeprecatedApiVersions ).OrderBy( v => v ).ToArray(); + SupportedApiVersions = [.. supportedVersions.Distinct().OrderBy( v => v )]; + DeprecatedApiVersions = [.. deprecatedVersions.Distinct().OrderBy( v => v )]; + ImplementedApiVersions = [.. SupportedApiVersions.Union( DeprecatedApiVersions ).OrderBy( v => v )]; } /// @@ -121,10 +121,10 @@ public ApiVersionModel( IEnumerable advertisedVersions, IEnumerable deprecatedAdvertisedVersions ) { - DeclaredApiVersions = declaredVersions.Distinct().OrderBy( v => v ).ToArray(); - SupportedApiVersions = supportedVersions.Union( advertisedVersions ).OrderBy( v => v ).ToArray(); - DeprecatedApiVersions = deprecatedVersions.Union( deprecatedAdvertisedVersions ).OrderBy( v => v ).ToArray(); - ImplementedApiVersions = SupportedApiVersions.Union( DeprecatedApiVersions ).OrderBy( v => v ).ToArray(); + DeclaredApiVersions = [.. declaredVersions.Distinct().OrderBy( v => v )]; + SupportedApiVersions = [.. supportedVersions.Union( advertisedVersions ).OrderBy( v => v )]; + DeprecatedApiVersions = [.. deprecatedVersions.Union( deprecatedAdvertisedVersions ).OrderBy( v => v )]; + ImplementedApiVersions = [.. SupportedApiVersions.Union( DeprecatedApiVersions ).OrderBy( v => v )]; } private string DebuggerDisplayText => IsApiVersionNeutral ? "*.*" : string.Join( ", ", DeclaredApiVersions ); diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelExtensions.cs index efdc86f3..b821c1ff 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelExtensions.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelExtensions.cs @@ -7,112 +7,116 @@ namespace Asp.Versioning; /// public static class ApiVersionModelExtensions { - /// - /// Aggregates the current version information with other version information. - /// /// The API version information that is the basis /// of the aggregation. - /// The other API version information to aggregate. - /// A new that is the aggregated result of the - /// other version information and the current version information. - public static ApiVersionModel Aggregate( this ApiVersionModel version, ApiVersionModel otherVersion ) + extension( ApiVersionModel version ) { - ArgumentNullException.ThrowIfNull( version ); - ArgumentNullException.ThrowIfNull( otherVersion ); - - var implemented = new SortedSet( version.ImplementedApiVersions ); - var supported = new SortedSet( version.SupportedApiVersions ); - var deprecated = new SortedSet( version.DeprecatedApiVersions ); + /// + /// Aggregates the current version information with other version information. + /// + /// The other API version information to aggregate. + /// A new that is the aggregated result of the + /// other version information and the current version information. + public ApiVersionModel Aggregate( ApiVersionModel otherVersion ) + { + ArgumentNullException.ThrowIfNull( version ); + ArgumentNullException.ThrowIfNull( otherVersion ); - implemented.UnionWith( otherVersion.ImplementedApiVersions ); - supported.UnionWith( otherVersion.SupportedApiVersions ); - deprecated.UnionWith( otherVersion.DeprecatedApiVersions ); - deprecated.ExceptWith( supported ); + var implemented = new SortedSet( version.ImplementedApiVersions ); + var supported = new SortedSet( version.SupportedApiVersions ); + var deprecated = new SortedSet( version.DeprecatedApiVersions ); - return new( version, implemented.ToArray(), supported.ToArray(), deprecated.ToArray() ); - } + implemented.UnionWith( otherVersion.ImplementedApiVersions ); + supported.UnionWith( otherVersion.SupportedApiVersions ); + deprecated.UnionWith( otherVersion.DeprecatedApiVersions ); + deprecated.ExceptWith( supported ); - /// - /// Aggregates the current version information with other version information. - /// - /// The API version information that is the basis - /// of the aggregation. - /// A sequence of other - /// API version information to aggregate. - /// A new that is the aggregated result of the - /// other version information and the current version information. - public static ApiVersionModel Aggregate( this ApiVersionModel version, IEnumerable otherVersions ) - { - ArgumentNullException.ThrowIfNull( version ); - ArgumentNullException.ThrowIfNull( otherVersions ); + return new( version, [.. implemented], [.. supported], [.. deprecated] ); + } - if ( ( otherVersions is ICollection collection && collection.Count == 0 ) || - ( otherVersions is IReadOnlyCollection readOnlyCollection && readOnlyCollection.Count == 0 ) ) + /// + /// Aggregates the current version information with other version information. + /// + /// A sequence of other + /// API version information to aggregate. + /// A new that is the aggregated result of the + /// other version information and the current version information. + public ApiVersionModel Aggregate( IEnumerable otherVersions ) { - return version; - } + ArgumentNullException.ThrowIfNull( version ); + ArgumentNullException.ThrowIfNull( otherVersions ); - using var iterator = otherVersions.GetEnumerator(); + if ( ( otherVersions is ICollection collection && collection.Count == 0 ) || + ( otherVersions is IReadOnlyCollection readOnlyCollection && readOnlyCollection.Count == 0 ) ) + { + return version; + } - if ( !iterator.MoveNext() ) - { - return version; - } + using var iterator = otherVersions.GetEnumerator(); - var implemented = new SortedSet( version.ImplementedApiVersions ); - var supported = new SortedSet( version.SupportedApiVersions ); - var deprecated = new SortedSet( version.DeprecatedApiVersions ); + if ( !iterator.MoveNext() ) + { + return version; + } - do - { - var otherVersion = iterator.Current; + var implemented = new SortedSet( version.ImplementedApiVersions ); + var supported = new SortedSet( version.SupportedApiVersions ); + var deprecated = new SortedSet( version.DeprecatedApiVersions ); - implemented.UnionWith( otherVersion.ImplementedApiVersions ); - supported.UnionWith( otherVersion.SupportedApiVersions ); - deprecated.UnionWith( otherVersion.DeprecatedApiVersions ); - } - while ( iterator.MoveNext() ); + do + { + var otherVersion = iterator.Current; - deprecated.ExceptWith( supported ); + implemented.UnionWith( otherVersion.ImplementedApiVersions ); + supported.UnionWith( otherVersion.SupportedApiVersions ); + deprecated.UnionWith( otherVersion.DeprecatedApiVersions ); + } + while ( iterator.MoveNext() ); - return new( version, implemented.ToArray(), supported.ToArray(), deprecated.ToArray() ); + deprecated.ExceptWith( supported ); + + return new( version, [.. implemented], [.. supported], [.. deprecated] ); + } } - /// - /// Aggregates a sequence of version information. - /// /// The API version information to aggregate. - /// A new that is the aggregated result of the provided version information. - public static ApiVersionModel Aggregate( this IEnumerable versions ) + extension( IEnumerable versions ) { - ArgumentNullException.ThrowIfNull( versions ); - - if ( ( versions is ICollection collection && collection.Count == 0 ) || - ( versions is IReadOnlyCollection readOnlyCollection && readOnlyCollection.Count == 0 ) ) + /// + /// Aggregates a sequence of version information. + /// + /// A new that is the aggregated result of the provided version information. + public ApiVersionModel Aggregate() { - return ApiVersionModel.Empty; - } + ArgumentNullException.ThrowIfNull( versions ); - using var iterator = versions.GetEnumerator(); + if ( ( versions is ICollection collection && collection.Count == 0 ) || + ( versions is IReadOnlyCollection readOnlyCollection && readOnlyCollection.Count == 0 ) ) + { + return ApiVersionModel.Empty; + } - if ( !iterator.MoveNext() ) - { - return ApiVersionModel.Empty; - } + using var iterator = versions.GetEnumerator(); - var version = iterator.Current; - var supported = new SortedSet( version.SupportedApiVersions ); - var deprecated = new SortedSet( version.DeprecatedApiVersions ); + if ( !iterator.MoveNext() ) + { + return ApiVersionModel.Empty; + } - while ( iterator.MoveNext() ) - { - version = iterator.Current; - supported.UnionWith( version.SupportedApiVersions ); - deprecated.UnionWith( version.DeprecatedApiVersions ); - } + var version = iterator.Current; + var supported = new SortedSet( version.SupportedApiVersions ); + var deprecated = new SortedSet( version.DeprecatedApiVersions ); + + while ( iterator.MoveNext() ) + { + version = iterator.Current; + supported.UnionWith( version.SupportedApiVersions ); + deprecated.UnionWith( version.DeprecatedApiVersions ); + } - deprecated.ExceptWith( supported ); + deprecated.ExceptWith( supported ); - return new( supported, deprecated ); + return new( supported, deprecated ); + } } } \ No newline at end of file diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionsBaseAttribute.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionsBaseAttribute.cs index 4c966ae5..291feff3 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionsBaseAttribute.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionsBaseAttribute.cs @@ -13,7 +13,7 @@ public abstract partial class ApiVersionsBaseAttribute : Attribute /// Initializes a new instance of the class. /// /// The API version. - protected ApiVersionsBaseAttribute( ApiVersion version ) => Versions = new[] { version }; + protected ApiVersionsBaseAttribute( ApiVersion version ) => Versions = [version]; /// /// Initializes a new instance of the class. @@ -26,7 +26,7 @@ protected ApiVersionsBaseAttribute( ApiVersion version, params ApiVersion[] othe if ( otherVersions is null || ( count = otherVersions.Length ) == 0 ) { - Versions = new[] { version }; + Versions = [version]; } else { @@ -41,7 +41,7 @@ protected ApiVersionsBaseAttribute( ApiVersion version, params ApiVersion[] othe /// Initializes a new instance of the class. /// /// A numeric API version. - protected ApiVersionsBaseAttribute( double version ) => Versions = new ApiVersion[] { new( version ) }; + protected ApiVersionsBaseAttribute( double version ) => Versions = [new( version )]; /// /// Initializes a new instance of the class. @@ -54,7 +54,7 @@ protected ApiVersionsBaseAttribute( double version, params double[] otherVersion if ( otherVersions is null || ( count = otherVersions.Length ) == 0 ) { - Versions = new ApiVersion[] { new( version ) }; + Versions = [new( version )]; } else { @@ -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 System.ArgumentNullException( nameof( parser ) ) ).Parse( version ) }; + Versions = [( parser ?? throw new System.ArgumentNullException( nameof( parser ) ) ).Parse( version )]; /// /// Initializes a new instance of the class. @@ -112,7 +112,7 @@ protected ApiVersionsBaseAttribute( IApiVersionParser parser, string version, pa if ( otherVersions is null || ( count = otherVersions.Length ) == 0 ) { - Versions = new[] { parser.Parse( version ) }; + Versions = [parser.Parse( version )]; } else { 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 861b642c..b7e841cf 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/Asp.Versioning.Abstractions.csproj +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/Asp.Versioning.Abstractions.csproj @@ -1,13 +1,21 @@  - 8.1.0 - 8.1.0.0 + 10.0.0 + 10.0.0.0 $(DefaultTargetFramework);netstandard1.0;netstandard2.0 API Versioning Abstractions The abstractions library for API versioning. Asp.Versioning Asp;AspNet;AspNetCore;Versioning + + + $(NoWarn);NU1903 diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/CollectionExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/CollectionExtensions.cs index f8b89a46..d123f18c 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/CollectionExtensions.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/CollectionExtensions.cs @@ -1,60 +1,65 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Collections.Generic; internal static class CollectionExtensions { - internal static void UnionWith( this ICollection collection, IEnumerable other ) + extension( ICollection collection ) { - if ( collection is ISet set ) - { - set.UnionWith( other ); - } - else + internal void UnionWith( IEnumerable other ) { - switch ( other ) + if ( collection is ISet set ) { - case IList list: - for ( var i = 0; i < list.Count; i++ ) - { - if ( !collection.Contains( list[i] ) ) + set.UnionWith( other ); + } + else + { + switch ( other ) + { + case IList list: + for ( var i = 0; i < list.Count; i++ ) { - collection.Add( list[i] ); + if ( !collection.Contains( list[i] ) ) + { + collection.Add( list[i] ); + } } - } - break; - case IReadOnlyList list: - for ( var i = 0; i < list.Count; i++ ) - { - if ( !collection.Contains( list[i] ) ) + break; + case IReadOnlyList list: + for ( var i = 0; i < list.Count; i++ ) { - collection.Add( list[i] ); + if ( !collection.Contains( list[i] ) ) + { + collection.Add( list[i] ); + } } - } - break; - case ICollection otherCollection: - var array = new T[otherCollection.Count]; + break; + case ICollection otherCollection: + var array = new T[otherCollection.Count]; - otherCollection.CopyTo( array, 0 ); + otherCollection.CopyTo( array, 0 ); - for ( var i = 0; i < array.Length; i++ ) - { - collection.Add( array[i] ); - } + for ( var i = 0; i < array.Length; i++ ) + { + collection.Add( array[i] ); + } - break; - default: - foreach ( var item in other ) - { - if ( !collection.Contains( item ) ) + break; + default: + foreach ( var item in other ) { - collection.Add( item ); + if ( !collection.Contains( item ) ) + { + collection.Add( item ); + } } - } - break; + break; + } } } } diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderBase.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderBase.cs index 4ebe62bd..cc77e733 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderBase.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderBase.cs @@ -64,7 +64,7 @@ protected ApiVersionConventionBuilderBase() { } /// /// The sequence of attributes to merge. protected virtual void MergeAttributesWithConventions( IEnumerable attributes ) => - MergeAttributesWithConventions( ( attributes as IReadOnlyList ) ?? attributes.ToArray() ); + MergeAttributesWithConventions( ( attributes as IReadOnlyList ) ?? [.. attributes] ); /// /// Merges API version information from the specified attributes with the current conventions. diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderExtensions.cs index 9d528546..af66109c 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderExtensions.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderExtensions.cs @@ -11,418 +11,331 @@ namespace Asp.Versioning.Conventions; /// public static class ApiVersionConventionBuilderExtensions { - /// - /// Indicates that the specified API version is supported by the configured controller. - /// /// The type of . /// The extended convention builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. /// The original . - public static T HasApiVersion( this T builder, int majorVersion, int? minorVersion = default, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder + extension( T builder ) where T : notnull, IDeclareApiVersionConventionBuilder { - builder.HasApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - return builder; - } - - /// - /// Indicates that the specified API version is supported by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The version number. - /// The optional version status. - /// The original . - public static T HasApiVersion( this T builder, double version, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.HasApiVersion( new ApiVersion( version, status ) ); - return builder; - } - - /// - /// Indicates that the specified API version is supported by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// The original . - public static T HasApiVersion( this T builder, int year, int month, int day, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.HasApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - return builder; - } + /// + /// Indicates that the specified API version is supported by the configured controller. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public T HasApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) + { + builder.HasApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + return builder; + } - /// - /// Indicates that the specified API version is supported by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The group version. - /// The optional version status. - /// The original . - public static T HasApiVersion( this T builder, DateOnly groupVersion, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.HasApiVersion( new ApiVersion( groupVersion, status ) ); - return builder; - } + /// + /// Indicates that the specified API version is supported by the configured controller. + /// + /// The version number. + /// The optional version status. + public T HasApiVersion( double version, string? status = default ) + { + builder.HasApiVersion( new ApiVersion( version, status ) ); + return builder; + } - /// - /// Indicates that the specified API versions are supported by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The sequence of API versions supported by the controller. - /// The original . - public static T HasApiVersions( this T builder, IEnumerable apiVersions ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - ArgumentNullException.ThrowIfNull( apiVersions ); + /// + /// Indicates that the specified API version is supported by the configured controller. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + public T HasApiVersion( int year, int month, int day, string? status = default ) + { + builder.HasApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + return builder; + } - foreach ( var apiVersion in apiVersions ) + /// + /// Indicates that the specified API version is supported by the configured controller. + /// + /// The group version. + /// The optional version status. + public T HasApiVersion( DateOnly groupVersion, string? status = default ) { - builder.HasApiVersion( apiVersion ); + builder.HasApiVersion( new ApiVersion( groupVersion, status ) ); + return builder; } - return builder; - } + /// + /// Indicates that the specified API versions are supported by the configured controller. + /// + /// The sequence of API versions supported by the controller. + public T HasApiVersions( IEnumerable apiVersions ) + { + ArgumentNullException.ThrowIfNull( apiVersions ); - /// - /// Indicates that the specified API version is deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The original . - public static T HasDeprecatedApiVersion( this T builder, int majorVersion, int? minorVersion = default, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.HasDeprecatedApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - return builder; - } + foreach ( var apiVersion in apiVersions ) + { + builder.HasApiVersion( apiVersion ); + } - /// - /// Indicates that the specified API version is deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The version number. - /// The optional version status. - /// The original . - public static T HasDeprecatedApiVersion( this T builder, double version, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.HasDeprecatedApiVersion( new ApiVersion( version, status ) ); - return builder; - } + return builder; + } - /// - /// Indicates that the specified API version is deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// The original . - public static T HasDeprecatedApiVersion( this T builder, int year, int month, int day, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.HasDeprecatedApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - return builder; - } + /// + /// Indicates that the specified API version is deprecated by the configured controller. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public T HasDeprecatedApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) + { + builder.HasDeprecatedApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + return builder; + } - /// - /// Indicates that the specified API version is deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The group version. - /// The optional version status. - /// The original . - public static T HasDeprecatedApiVersion( this T builder, DateOnly groupVersion, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.HasDeprecatedApiVersion( new ApiVersion( groupVersion, status ) ); - return builder; - } + /// + /// Indicates that the specified API version is deprecated by the configured controller. + /// + /// The version number. + /// The optional version status. + public T HasDeprecatedApiVersion( double version, string? status = default ) + { + builder.HasDeprecatedApiVersion( new ApiVersion( version, status ) ); + return builder; + } - /// - /// Indicates that the specified API versions are deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The sequence of API versions deprecated by the controller. - /// The original . - public static T HasDeprecatedApiVersions( this T builder, IEnumerable apiVersions ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - ArgumentNullException.ThrowIfNull( apiVersions ); + /// + /// Indicates that the specified API version is deprecated by the configured controller. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + public T HasDeprecatedApiVersion( int year, int month, int day, string? status = default ) + { + builder.HasDeprecatedApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + return builder; + } - foreach ( var apiVersion in apiVersions ) + /// + /// Indicates that the specified API version is deprecated by the configured controller. + /// + /// The group version. + /// The optional version status. + public T HasDeprecatedApiVersion( DateOnly groupVersion, string? status = default ) { - builder.HasDeprecatedApiVersion( apiVersion ); + builder.HasDeprecatedApiVersion( new ApiVersion( groupVersion, status ) ); + return builder; } - return builder; - } + /// + /// Indicates that the specified API versions are deprecated by the configured controller. + /// + /// The sequence of API versions deprecated by the controller. + public T HasDeprecatedApiVersions( IEnumerable apiVersions ) + { + ArgumentNullException.ThrowIfNull( apiVersions ); - /// - /// Indicates that the specified API version is advertised by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The original . - public static T AdvertisesApiVersion( this T builder, int majorVersion, int? minorVersion = default, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.AdvertisesApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - return builder; - } + foreach ( var apiVersion in apiVersions ) + { + builder.HasDeprecatedApiVersion( apiVersion ); + } - /// - /// Indicates that the specified API version is advertised by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The version number. - /// The optional version status. - /// The original . - public static T AdvertisesApiVersion( this T builder, double version, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.AdvertisesApiVersion( new ApiVersion( version, status ) ); - return builder; - } + return builder; + } - /// - /// Indicates that the specified API version is advertised by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// The original . - public static T AdvertisesApiVersion( this T builder, int year, int month, int day, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.AdvertisesApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - return builder; - } + /// + /// Indicates that the specified API version is advertised by the configured controller. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public T AdvertisesApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) + { + builder.AdvertisesApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + return builder; + } - /// - /// Indicates that the specified API version is advertised by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The group version. - /// The optional version status. - /// The original . - public static T AdvertisesApiVersion( this T builder, DateOnly groupVersion, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.AdvertisesApiVersion( new ApiVersion( groupVersion, status ) ); - return builder; - } + /// + /// Indicates that the specified API version is advertised by the configured controller. + /// + /// The version number. + /// The optional version status. + public T AdvertisesApiVersion( double version, string? status = default ) + { + builder.AdvertisesApiVersion( new ApiVersion( version, status ) ); + return builder; + } - /// - /// Indicates that the specified API versions are advertised by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The sequence of API versions advertised by the controller. - /// The original . - public static T AdvertisesApiVersions( this T builder, IEnumerable apiVersions ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - ArgumentNullException.ThrowIfNull( apiVersions ); + /// + /// Indicates that the specified API version is advertised by the configured controller. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + public T AdvertisesApiVersion( int year, int month, int day, string? status = default ) + { + builder.AdvertisesApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + return builder; + } - foreach ( var apiVersion in apiVersions ) + /// + /// Indicates that the specified API version is advertised by the configured controller. + /// + /// The group version. + /// The optional version status. + public T AdvertisesApiVersion( DateOnly groupVersion, string? status = default ) { - builder.AdvertisesApiVersion( apiVersion ); + builder.AdvertisesApiVersion( new ApiVersion( groupVersion, status ) ); + return builder; } - return builder; - } + /// + /// Indicates that the specified API versions are advertised by the configured controller. + /// + /// The sequence of API versions advertised by the controller. + public T AdvertisesApiVersions( IEnumerable apiVersions ) + { + ArgumentNullException.ThrowIfNull( apiVersions ); - /// - /// Indicates that the specified API version is advertised and deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The original . - public static T AdvertisesDeprecatedApiVersion( this T builder, int majorVersion, int? minorVersion = default, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.AdvertisesDeprecatedApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - return builder; - } + foreach ( var apiVersion in apiVersions ) + { + builder.AdvertisesApiVersion( apiVersion ); + } - /// - /// Indicates that the specified API version is advertised and deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The version number. - /// The optional version status. - /// The original . - public static T AdvertisesDeprecatedApiVersion( this T builder, double version, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.AdvertisesDeprecatedApiVersion( new ApiVersion( version, status ) ); - return builder; - } + return builder; + } - /// - /// Indicates that the specified API version is advertised and deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The version year. - /// The version month. - /// The version day. - /// The version status. - /// The original . - public static T AdvertisesDeprecatedApiVersion( this T builder, int year, int month, int day, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.AdvertisesDeprecatedApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - return builder; - } + /// + /// Indicates that the specified API version is advertised and deprecated by the configured controller. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public T AdvertisesDeprecatedApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) + { + builder.AdvertisesDeprecatedApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + return builder; + } - /// - /// Indicates that the specified API version is advertised and deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The group version. - /// The optional version status. - /// The original . - public static T AdvertisesDeprecatedApiVersion( this T builder, DateOnly groupVersion, string? status = default ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - builder.AdvertisesDeprecatedApiVersion( new ApiVersion( groupVersion, status ) ); - return builder; - } + /// + /// Indicates that the specified API version is advertised and deprecated by the configured controller. + /// + /// The version number. + /// The optional version status. + public T AdvertisesDeprecatedApiVersion( double version, string? status = default ) + { + builder.AdvertisesDeprecatedApiVersion( new ApiVersion( version, status ) ); + return builder; + } - /// - /// Indicates that the specified API versions are advertised and deprecated by the configured controller. - /// - /// The type of . - /// The extended convention builder. - /// The sequence of deprecated API versions advertised by the controller. - /// The original . - public static T AdvertisesDeprecatedApiVersions( this T builder, IEnumerable apiVersions ) - where T : notnull, IDeclareApiVersionConventionBuilder - { - ArgumentNullException.ThrowIfNull( apiVersions ); + /// + /// Indicates that the specified API version is advertised and deprecated by the configured controller. + /// + /// The version year. + /// The version month. + /// The version day. + /// The version status. + public T AdvertisesDeprecatedApiVersion( int year, int month, int day, string? status = default ) + { + builder.AdvertisesDeprecatedApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + return builder; + } - foreach ( var apiVersion in apiVersions ) + /// + /// Indicates that the specified API version is advertised and deprecated by the configured controller. + /// + /// The group version. + /// The optional version status. + public T AdvertisesDeprecatedApiVersion( DateOnly groupVersion, string? status = default ) { - builder.AdvertisesDeprecatedApiVersion( apiVersion ); + builder.AdvertisesDeprecatedApiVersion( new ApiVersion( groupVersion, status ) ); + return builder; } - return builder; - } + /// + /// Indicates that the specified API versions are advertised and deprecated by the configured controller. + /// + /// The sequence of deprecated API versions advertised by the controller. + public T AdvertisesDeprecatedApiVersions( IEnumerable apiVersions ) + { + ArgumentNullException.ThrowIfNull( apiVersions ); - /// - /// Indicates that the specified API version is mapped to the configured controller action. - /// - /// The type of . - /// The extended . - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The original . - public static T MapToApiVersion( this T builder, int majorVersion, int? minorVersion = default, string? status = default ) - where T : notnull, IMapToApiVersionConventionBuilder - { - builder.MapToApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - return builder; - } + foreach ( var apiVersion in apiVersions ) + { + builder.AdvertisesDeprecatedApiVersion( apiVersion ); + } - /// - /// Indicates that the specified API version is mapped to the configured controller action. - /// - /// The type of . - /// The extended . - /// The version number. - /// The optional version status. - /// The original . - public static T MapToApiVersion( this T builder, double version, string? status = default ) - where T : notnull, IMapToApiVersionConventionBuilder - { - builder.MapToApiVersion( new ApiVersion( version, status ) ); - return builder; + return builder; + } } - /// - /// Indicates that the specified API version is mapped to the configured controller action. - /// /// The type of . /// The extended . - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. /// The original . - public static T MapToApiVersion( this T builder, int year, int month, int day, string? status = default ) + extension( T builder ) where T : notnull, IMapToApiVersionConventionBuilder { - builder.MapToApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - return builder; - } + /// + /// Indicates that the specified API version is mapped to the configured controller action. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public T MapToApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) + { + builder.MapToApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + return builder; + } - /// - /// Indicates that the specified API version is mapped to the configured controller action. - /// - /// The type of . - /// The extended . - /// The group version. - /// The optional version status. - /// The original . - public static T MapToApiVersion( this T builder, DateOnly groupVersion, string? status = default ) - where T : notnull, IMapToApiVersionConventionBuilder - { - builder.MapToApiVersion( new ApiVersion( groupVersion, status ) ); - return builder; - } + /// + /// Indicates that the specified API version is mapped to the configured controller action. + /// + /// The version number. + /// The optional version status. + public T MapToApiVersion( double version, string? status = default ) + { + builder.MapToApiVersion( new ApiVersion( version, status ) ); + return builder; + } - /// - /// Indicates that the specified API versions are mapped to the configured controller action. - /// - /// The type of . - /// The extended . - /// The sequence of API versions supported by the controller. - /// The original . - public static T MapToApiVersions( this T builder, IEnumerable apiVersions ) - where T : notnull, IMapToApiVersionConventionBuilder - { - ArgumentNullException.ThrowIfNull( apiVersions ); + /// + /// Indicates that the specified API version is mapped to the configured controller action. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + public T MapToApiVersion( int year, int month, int day, string? status = default ) + { + builder.MapToApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + return builder; + } - foreach ( var apiVersion in apiVersions ) + /// + /// Indicates that the specified API version is mapped to the configured controller action. + /// + /// The group version. + /// The optional version status. + public T MapToApiVersion( DateOnly groupVersion, string? status = default ) { - builder.MapToApiVersion( apiVersion ); + builder.MapToApiVersion( new ApiVersion( groupVersion, status ) ); + return builder; } - return builder; + /// + /// Indicates that the specified API versions are mapped to the configured controller action. + /// + /// The sequence of API versions supported by the controller. + public T MapToApiVersions( IEnumerable apiVersions ) + { + ArgumentNullException.ThrowIfNull( apiVersions ); + + foreach ( var apiVersion in apiVersions ) + { + builder.MapToApiVersion( apiVersion ); + } + + return builder; + } } } \ No newline at end of file diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/DeprecationPolicy.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/DeprecationPolicy.cs index 8c454289..9a355a76 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/DeprecationPolicy.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/DeprecationPolicy.cs @@ -57,22 +57,9 @@ public DeprecationPolicy() { } public DeprecationPolicy( LinkHeaderValue link ) => Links.Add( link ); /// - /// Returns a boolean to indicate if this policy is effective at the given . + /// Returns a value indicating if this policy is effective for the specified date and time. /// - /// The point in time to serve as a reference. - /// A boolean which indicates if this policy is effective. - public bool IsEffective( DateTimeOffset? dateTimeOffset ) - { - if ( dateTimeOffset is not { } when ) - { - return true; - } - - if ( Date is not { } date ) - { - return true; - } - - return date <= when; - } + /// The date and time to evaluate. + /// True if the policy is effective; otherwise, false. + public bool IsEffective( DateTimeOffset dateTime ) => Date is null || Date <= dateTime; } \ No newline at end of file diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionParameterSourceExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionParameterSourceExtensions.cs index 55022bce..e7518e5e 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionParameterSourceExtensions.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionParameterSourceExtensions.cs @@ -9,111 +9,109 @@ namespace Asp.Versioning; /// public static class IApiVersionParameterSourceExtensions { - /// - /// Determines whether the specified parameter source versions by query string. - /// /// The extended parameter source. - /// True if multiple API version locations are allowed. - /// False if the API version can only appear in a query string parameter. The default value is true. - /// True if the parameter source versions by query string; otherwise, false. - public static bool VersionsByQueryString( this IApiVersionParameterSource source, bool allowMultipleLocations = true ) + extension( IApiVersionParameterSource source ) { - ArgumentNullException.ThrowIfNull( source ); + /// + /// Determines whether the specified parameter source versions by query string. + /// + /// True if multiple API version locations are allowed. + /// False if the API version can only appear in a query string parameter. The default value is true. + /// True if the parameter source versions by query string; otherwise, false. + public bool VersionsByQueryString( bool allowMultipleLocations = true ) + { + ArgumentNullException.ThrowIfNull( source ); - var context = new DescriptionContext( Query ); + var context = new DescriptionContext( Query ); - source.AddParameters( context ); + source.AddParameters( context ); - return context.IsMatch && ( allowMultipleLocations || context.Locations == 1 ); - } + return context.IsMatch && ( allowMultipleLocations || context.Locations == 1 ); + } - /// - /// Determines whether the specified parameter source versions by HTTP header. - /// - /// The extended parameter source. - /// True if multiple API version locations are allowed. - /// False if the API version can only appear in a HTTP header. The default value is true. - /// True if the parameter source versions by HTTP header; otherwise, false. - public static bool VersionsByHeader( this IApiVersionParameterSource source, bool allowMultipleLocations = true ) - { - ArgumentNullException.ThrowIfNull( source ); + /// + /// Determines whether the specified parameter source versions by HTTP header. + /// + /// True if multiple API version locations are allowed. + /// False if the API version can only appear in a HTTP header. The default value is true. + /// True if the parameter source versions by HTTP header; otherwise, false. + public bool VersionsByHeader( bool allowMultipleLocations = true ) + { + ArgumentNullException.ThrowIfNull( source ); - var context = new DescriptionContext( Header ); + var context = new DescriptionContext( Header ); - source.AddParameters( context ); + source.AddParameters( context ); - return context.IsMatch && ( allowMultipleLocations || context.Locations == 1 ); - } + return context.IsMatch && ( allowMultipleLocations || context.Locations == 1 ); + } - /// - /// Determines whether the specified parameter source versions by URL path segment. - /// - /// The extended parameter source. - /// True if multiple API version locations are allowed. - /// False if the API version can only appear in a URL path segment. The default value is true. - /// True if the parameter source versions by URL path segment; otherwise, false. - public static bool VersionsByUrl( this IApiVersionParameterSource source, bool allowMultipleLocations = true ) - { - ArgumentNullException.ThrowIfNull( source ); + /// + /// Determines whether the specified parameter source versions by URL path segment. + /// + /// True if multiple API version locations are allowed. + /// False if the API version can only appear in a URL path segment. The default value is true. + /// True if the parameter source versions by URL path segment; otherwise, false. + public bool VersionsByUrl( bool allowMultipleLocations = true ) + { + ArgumentNullException.ThrowIfNull( source ); - var context = new DescriptionContext( Path ); + var context = new DescriptionContext( Path ); - source.AddParameters( context ); + source.AddParameters( context ); - return context.IsMatch && ( allowMultipleLocations || context.Locations == 1 ); - } + return context.IsMatch && ( allowMultipleLocations || context.Locations == 1 ); + } - /// - /// Determines whether the specified parameter source versions by media type. - /// - /// The extended parameter source. - /// True if multiple API version locations are allowed. - /// False if the API version can only appear as a media type. The default value is true. - /// True if the parameter source versions by media type; otherwise, false. - public static bool VersionsByMediaType( this IApiVersionParameterSource source, bool allowMultipleLocations = true ) - { - ArgumentNullException.ThrowIfNull( source ); + /// + /// Determines whether the specified parameter source versions by media type. + /// + /// True if multiple API version locations are allowed. + /// False if the API version can only appear as a media type. The default value is true. + /// True if the parameter source versions by media type; otherwise, false. + public bool VersionsByMediaType( bool allowMultipleLocations = true ) + { + ArgumentNullException.ThrowIfNull( source ); - var context = new DescriptionContext( MediaTypeParameter ); + var context = new DescriptionContext( MediaTypeParameter ); - source.AddParameters( context ); + source.AddParameters( context ); - return context.IsMatch && ( allowMultipleLocations || context.Locations == 1 ); - } + return context.IsMatch && ( allowMultipleLocations || context.Locations == 1 ); + } - /// - /// Gets the name of the parameter associated with the parameter source, if any. - /// - /// The extended parameter source. - /// The location to get the parameter name for. - /// The name of the first parameter defined by the parameter source for the specified - /// or null. - public static string GetParameterName( this IApiVersionParameterSource source, ApiVersionParameterLocation location ) - { - ArgumentNullException.ThrowIfNull( source ); + /// + /// Gets the name of the parameter associated with the parameter source, if any. + /// + /// The location to get the parameter name for. + /// The name of the first parameter defined by the parameter source for the specified + /// or null. + public string GetParameterName( ApiVersionParameterLocation location ) + { + ArgumentNullException.ThrowIfNull( source ); - var context = new DescriptionContext( location ); + var context = new DescriptionContext( location ); - source.AddParameters( context ); + source.AddParameters( context ); - return context.ParameterName; - } + return context.ParameterName; + } - /// - /// Gets the name of the parameters associated with the parameter source. - /// - /// The extended parameter source. - /// The location to get the parameter names for. - /// The names of the parameters defined by the parameter source for the specified . - public static IReadOnlyList GetParameterNames( this IApiVersionParameterSource source, ApiVersionParameterLocation location ) - { - ArgumentNullException.ThrowIfNull( source ); + /// + /// Gets the name of the parameters associated with the parameter source. + /// + /// The location to get the parameter names for. + /// The names of the parameters defined by the parameter source for the specified . + public IReadOnlyList GetParameterNames( ApiVersionParameterLocation location ) + { + ArgumentNullException.ThrowIfNull( source ); - var context = new DescriptionContext( location ); + var context = new DescriptionContext( location ); - source.AddParameters( context ); + source.AddParameters( context ); - return context.ParameterNames; + return context.ParameterNames; + } } private sealed class DescriptionContext : IApiVersionParameterDescriptionContext @@ -136,7 +134,7 @@ internal IReadOnlyList ParameterNames { if ( parameterNames == null ) { - return Array.Empty(); + return []; } return parameterNames; diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs index afdfd5b9..74e36b3b 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs @@ -11,151 +11,139 @@ namespace Asp.Versioning; /// public static class IApiVersioningPolicyBuilderExtensions { - /// - /// Creates and returns a new sunset policy builder. - /// /// The extended API versioning policy builder. - /// The name of the API the policy is for. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, string name ) + extension( IApiVersioningPolicyBuilder builder ) { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( name, default ); - } + /// + /// Creates and returns a new sunset policy builder. + /// + /// The name of the API the policy is for. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( string name ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( name, default ); + } - /// - /// Creates and returns a new sunset policy builder. - /// - /// The extended API versioning policy builder. - /// The name of the API the policy is for. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( - this IApiVersioningPolicyBuilder builder, - string name, - int majorVersion, - int? minorVersion = default, - string? status = default ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( name, new ApiVersion( majorVersion, minorVersion, status ) ); - } + /// + /// Creates and returns a new sunset policy builder. + /// + /// The name of the API the policy is for. + /// The major version number. + /// The optional minor version number. + /// The optional version status. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( string name, int majorVersion, int? minorVersion = default, string? status = default ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( name, new ApiVersion( majorVersion, minorVersion, status ) ); + } - /// - /// Creates and returns a new sunset policy builder. - /// - /// The extended API versioning policy builder. - /// The name of the API the policy is for. - /// The version number. - /// The optional version status. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, string name, double version, string? status = default ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( name, new ApiVersion( version, status ) ); - } + /// + /// Creates and returns a new sunset policy builder. + /// + /// The name of the API the policy is for. + /// The version number. + /// The optional version status. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( string name, double version, string? status = default ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( name, new ApiVersion( version, status ) ); + } - /// - /// Creates and returns a new sunset policy builder. - /// - /// The extended API versioning policy builder. - /// The name of the API the policy is for. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, string name, int year, int month, int day, string? status = default ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( name, new ApiVersion( new DateOnly( year, month, day ), status ) ); - } + /// + /// Creates and returns a new sunset policy builder. + /// + /// The name of the API the policy is for. + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( string name, int year, int month, int day, string? status = default ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( name, new ApiVersion( new DateOnly( year, month, day ), status ) ); + } - /// - /// Creates and returns a new sunset policy builder. - /// - /// The extended API versioning policy builder. - /// The name of the API the policy is for. - /// The group version. - /// The optional version status. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, string name, DateOnly groupVersion, string? status = default ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( name, new ApiVersion( groupVersion, status ) ); - } + /// + /// Creates and returns a new sunset policy builder. + /// + /// The name of the API the policy is for. + /// The group version. + /// The optional version status. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( string name, DateOnly groupVersion, string? status = default ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( name, new ApiVersion( groupVersion, status ) ); + } - /// - /// Creates and returns a new sunset policy builder. - /// - /// The extended API versioning policy builder. - /// The API version the policy is for. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, ApiVersion apiVersion ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( default, apiVersion ); - } + /// + /// Creates and returns a new sunset policy builder. + /// + /// The API version the policy is for. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( ApiVersion apiVersion ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( default, apiVersion ); + } - /// - /// Creates and returns a new sunset policy builder. - /// - /// The extended API versioning policy builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( - this IApiVersioningPolicyBuilder builder, - int majorVersion, - int? minorVersion = default, - string? status = default ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( default, new ApiVersion( majorVersion, minorVersion, status ) ); - } + /// + /// Creates and returns a new sunset policy builder. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( + int majorVersion, + int? minorVersion = default, + string? status = default ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( default, new ApiVersion( majorVersion, minorVersion, status ) ); + } - /// - /// Creates and returns a new sunset policy builder. - /// - /// The extended API versioning policy builder. - /// The version number. - /// The optional version status. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, double version, string? status = default ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( default, new ApiVersion( version, status ) ); - } + /// + /// Creates and returns a new sunset policy builder. + /// + /// The version number. + /// The optional version status. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( double version, string? status = default ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( default, new ApiVersion( version, status ) ); + } - /// - /// Creates and returns a new sunset policy builder. - /// - /// The extended API versioning policy builder. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, int year, int month, int day, string? status = default ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( default, new ApiVersion( new DateOnly( year, month, day ), status ) ); - } + /// + /// Creates and returns a new sunset policy builder. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( int year, int month, int day, string? status = default ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( default, new ApiVersion( new DateOnly( year, month, day ), status ) ); + } - /// - /// Creates and returns a new sunset policy builder. - /// - /// The extended API versioning policy builder. - /// The group version. - /// The optional version status. - /// A new sunset policy builder. - public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, DateOnly groupVersion, string? status = default ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Sunset( default, new ApiVersion( groupVersion, status ) ); + /// + /// Creates and returns a new sunset policy builder. + /// + /// The group version. + /// The optional version status. + /// A new sunset policy builder. + public ISunsetPolicyBuilder Sunset( DateOnly groupVersion, string? status = default ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Sunset( default, new ApiVersion( groupVersion, status ) ); + } } /// diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IDeprecationPolicyBuilder.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IDeprecationPolicyBuilder.cs index 8f95e51a..e9f1aff7 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/IDeprecationPolicyBuilder.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IDeprecationPolicyBuilder.cs @@ -5,4 +5,6 @@ namespace Asp.Versioning; /// /// Defines the behavior of a deprecation policy builder. /// -public interface IDeprecationPolicyBuilder : IPolicyBuilder, IPolicyWithLink, IPolicyWithEffectiveDate { } \ No newline at end of file +public interface IDeprecationPolicyBuilder : IPolicyBuilder, IPolicyWithLink, IPolicyWithEffectiveDate +{ +} \ 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 acf444f9..f1f93d82 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/ILinkBuilderExtensions.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ILinkBuilderExtensions.cs @@ -7,15 +7,18 @@ namespace Asp.Versioning; /// public static class ILinkBuilderExtensions { - /// - /// Creates and returns a new link builder. - /// /// The extended link builder. - /// The link target URL. - /// A new link builder. - public static ILinkBuilder Link( this ILinkBuilder builder, string linkTarget ) + extension( ILinkBuilder builder ) { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Link( new Uri( linkTarget, UriKind.RelativeOrAbsolute ) ); + /// + /// Creates and returns a new link builder. + /// + /// The link target URL. + /// A new link builder. + public ILinkBuilder Link( string linkTarget ) + { + 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/IPolicyBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyBuilderExtensions.cs index 08142139..134a9d9d 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyBuilderExtensions.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyBuilderExtensions.cs @@ -7,46 +7,48 @@ namespace Asp.Versioning; /// public static class IPolicyBuilderExtensions { - /// - /// Creates and returns a new link builder. - /// /// The extended policy builder. - /// The link target URL. - /// A new link builder. - public static ILinkBuilder Link( this IPolicyWithLink builder, string linkTarget ) + extension( IPolicyWithLink builder ) { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Link( new Uri( linkTarget, UriKind.RelativeOrAbsolute ) ); + /// + /// Creates and returns a new link builder. + /// + /// The link target URL. + /// A new link builder. + public ILinkBuilder Link( string linkTarget ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Link( new Uri( linkTarget, UriKind.RelativeOrAbsolute ) ); + } } - /// - /// Indicates when a policy is applied. - /// /// The type of policy builder. /// The extended policy builder. - /// The time when the policy is applied. - /// The current policy builder. - public static TBuilder Effective( this TBuilder builder, DateTimeOffset effectiveDate ) - where TBuilder : notnull, IPolicyWithEffectiveDate + extension( TBuilder builder ) where TBuilder : notnull, IPolicyWithEffectiveDate { - ArgumentNullException.ThrowIfNull( builder ); - builder.SetEffectiveDate( effectiveDate ); - return builder; - } + /// + /// Indicates when a policy is applied. + /// + /// The time when the policy is applied. + /// The current policy builder. + public TBuilder Effective( DateTimeOffset effectiveDate ) + { + ArgumentNullException.ThrowIfNull( builder ); + builder.SetEffectiveDate( effectiveDate ); + return builder; + } - /// - /// Indicates when a policy is applied. - /// - /// The type of policy builder. - /// The extended policy builder. - /// The year when the policy is applied. - /// The month when the policy is applied. - /// The day when the policy is applied. - /// The current policy builder. - public static TBuilder Effective( this TBuilder builder, int year, int month, int day ) - where TBuilder : notnull, IPolicyWithEffectiveDate - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.Effective( new DateTimeOffset( new DateTime( year, month, day ) ) ); + /// + /// Indicates when a policy is applied. + /// + /// The year when the policy is applied. + /// The month when the policy is applied. + /// The day when the policy is applied. + /// The current policy builder. + public TBuilder Effective( int year, int month, int day ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.Effective( new DateTimeOffset( new DateTime( year, month, day ) ) ); + } } } \ No newline at end of file diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManagerExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManagerExtensions.cs index 0c468f71..1b1173a8 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManagerExtensions.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManagerExtensions.cs @@ -7,112 +7,96 @@ namespace Asp.Versioning; /// public static class IPolicyManagerExtensions { - /// - /// Returns the policy for the specified API and version. - /// + /// The type of policy. /// The extended policy manager. - /// The API version to get the policy for. - /// The applicable policy, if any. - /// The type of policy. - /// True if the policy was retrieved; otherwise, false. - public static bool TryGetPolicy( - this IPolicyManager policyManager, - ApiVersion apiVersion, - [MaybeNullWhen( false )] out TPolicy policy ) + extension( IPolicyManager policyManager ) { - ArgumentNullException.ThrowIfNull( policyManager ); - return policyManager.TryGetPolicy( default, apiVersion, out policy ); - } - - /// - /// Returns the policy for the specified API and version. - /// - /// The extended policy manager. - /// The name of the API. - /// The applicable policy, if any. - /// The type of policy. - /// True if the policy was retrieved; otherwise, false. - public static bool TryGetPolicy( - this IPolicyManager policyManager, - string name, - [MaybeNullWhen( false )] out TPolicy policy ) - { - ArgumentNullException.ThrowIfNull( policyManager ); - return policyManager.TryGetPolicy( name, default, out policy ); - } - - /// - /// Attempts to resolve a policy for the specified name and API version combination. - /// - /// The extended policy manager. - /// The name of the API. - /// The API version to get the policy for. - /// The type of policy. - /// The applicable policy, if any. - /// The resolution order is as follows: - /// - /// and - /// only - /// only - /// - /// - public static TPolicy? ResolvePolicyOrDefault( - this IPolicyManager policyManager, - string? name, - ApiVersion? apiVersion ) - { - ArgumentNullException.ThrowIfNull( policyManager ); + /// + /// Returns the policy for the specified API and version. + /// + /// The API version to get the policy for. + /// The applicable policy, if any. + /// True if the policy was retrieved; otherwise, false. + public bool TryGetPolicy( ApiVersion apiVersion, [MaybeNullWhen( false )] out T policy ) + { + ArgumentNullException.ThrowIfNull( policyManager ); + return policyManager.TryGetPolicy( default, apiVersion, out policy ); + } - if ( policyManager.TryResolvePolicy( name, apiVersion, out var policy ) ) + /// + /// Returns the policy for the specified API and version. + /// + /// The name of the API. + /// The applicable policy, if any. + /// True if the policy was retrieved; otherwise, false. + public bool TryGetPolicy( string name, [MaybeNullWhen( false )] out T policy ) { - return policy; + ArgumentNullException.ThrowIfNull( policyManager ); + return policyManager.TryGetPolicy( name, default, out policy ); } - return default; - } + /// + /// Attempts to resolve a policy for the specified name and API version combination. + /// + /// The name of the API. + /// The API version to get the policy for. + /// The applicable policy, if any. + /// The resolution order is as follows: + /// + /// and + /// only + /// only + /// + /// + public T? ResolvePolicyOrDefault( string? name, ApiVersion? apiVersion ) + { + ArgumentNullException.ThrowIfNull( policyManager ); - /// - /// Attempts to resolve a policy for the specified name and API version combination. - /// - /// The extended policy manager. - /// The name of the API. - /// The API version to get the policy for. - /// The applicable policy, if any. - /// The type of policy. - /// True if the policy was retrieved; otherwise, false. - /// The resolution order is as follows: - /// - /// and - /// only - /// only - /// - /// - public static bool TryResolvePolicy( - this IPolicyManager policyManager, - string? name, - ApiVersion? apiVersion, - [MaybeNullWhen( false )] out TPolicy policy ) - { - ArgumentNullException.ThrowIfNull( policyManager ); + if ( policyManager.TryResolvePolicy( name, apiVersion, out var policy ) ) + { + return policy; + } + + return default; + } - if ( !string.IsNullOrEmpty( name ) ) + /// + /// Attempts to resolve a policy for the specified name and API version combination. + /// + /// The name of the API. + /// The API version to get the policy for. + /// The applicable policy, if any. + /// True if the policy was retrieved; otherwise, false. + /// The resolution order is as follows: + /// + /// and + /// only + /// only + /// + /// + public bool TryResolvePolicy( string? name, ApiVersion? apiVersion, [MaybeNullWhen( false )] out T policy ) { - if ( apiVersion != null && policyManager.TryGetPolicy( name, apiVersion, out policy ) ) + ArgumentNullException.ThrowIfNull( policyManager ); + + if ( !string.IsNullOrEmpty( name ) ) { - return true; + if ( apiVersion != null && policyManager.TryGetPolicy( name, apiVersion, out policy ) ) + { + return true; + } + else if ( policyManager.TryGetPolicy( name!, out policy ) ) + { + return true; + } } - else if ( policyManager.TryGetPolicy( name!, out policy ) ) + + if ( apiVersion != null && policyManager.TryGetPolicy( apiVersion, out policy ) ) { return true; } - } - if ( apiVersion != null && policyManager.TryGetPolicy( apiVersion, out policy ) ) - { - return true; + policy = default!; + return false; } - - policy = default!; - return false; } } \ No newline at end of file diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithEffectiveDate.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithEffectiveDate.cs index f148a0c3..bfef317c 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithEffectiveDate.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithEffectiveDate.cs @@ -3,15 +3,13 @@ namespace Asp.Versioning; /// -/// A policy which can be configured to only be effective after a particular date. +/// Defines the behavior of a policy which can be configured to only be effective after a particular date. /// public interface IPolicyWithEffectiveDate { /// - /// Indicates when a policy is applied. + /// Sets the effective date when a policy is applied. /// - /// - /// The date and time when a policy is applied. - /// + /// The date and time when a policy is applied. void SetEffectiveDate( DateTimeOffset effectiveDate ); } \ No newline at end of file diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilder.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilder.cs index 58a45e70..d31ad2fb 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilder.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilder.cs @@ -5,4 +5,6 @@ namespace Asp.Versioning; /// /// Defines the behavior of a sunset policy builder. /// -public interface ISunsetPolicyBuilder : IPolicyBuilder, IPolicyWithLink, IPolicyWithEffectiveDate { } \ No newline at end of file +public interface ISunsetPolicyBuilder : IPolicyBuilder, IPolicyWithLink, IPolicyWithEffectiveDate +{ +} \ No newline at end of file diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/LinkHeaderValue.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/LinkHeaderValue.cs index 94088bc9..fca732bb 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/LinkHeaderValue.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/LinkHeaderValue.cs @@ -276,7 +276,7 @@ public static bool TryParse( { if ( input == null ) { - return new List(); + return []; } var list = new List( capacity: input.Count ); diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs index a4246749..ec664f86 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs @@ -57,7 +57,7 @@ public IReadOnlyList Parse( Type type ) if ( string.IsNullOrEmpty( type.Namespace ) ) { - return Array.Empty(); + return []; } #if NETSTANDARD @@ -114,11 +114,11 @@ public IReadOnlyList Parse( Type type ) { if ( version is null ) { - return new[] { result }; + return [result]; } else if ( versions is null ) { - return new[] { version, result }; + return [version, result]; } else { @@ -129,11 +129,11 @@ public IReadOnlyList Parse( Type type ) if ( version is null ) { - return Array.Empty(); + return []; } else if ( versions is null ) { - return new[] { version }; + return [version]; } return versions; diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/Str.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/Str.cs index 0c899a4b..a4eb70b1 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/Str.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/Str.cs @@ -100,10 +100,10 @@ internal static #if NETSTANDARD2_0 [MethodImpl( MethodImplOptions.AggressiveInlining )] - internal static ReadOnlySpan AsSpan( string text ) => text.AsSpan(); + internal static Text AsSpan( string text ) => text.AsSpan(); #elif !NETSTANDARD [MethodImpl( MethodImplOptions.AggressiveInlining )] - internal static ReadOnlySpan AsSpan( string text ) => text; + internal static Text AsSpan( string text ) => text; #endif #if NETSTANDARD2_0 @@ -111,7 +111,7 @@ internal static bool TryFormat( T value, Span destination, out int charsWritten, - ReadOnlySpan format = default, + Text format = default, IFormatProvider? provider = default ) where T : IFormattable { @@ -135,7 +135,7 @@ internal static bool TryFormat( T value, Span destination, out int charsWritten, - ReadOnlySpan format = default, + Text format = default, IFormatProvider? provider = default ) where T : ISpanFormattable => value.TryFormat( destination, out charsWritten, format, provider ); diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/SunsetPolicy.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/SunsetPolicy.cs index 28a41062..cc6d4376 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/SunsetPolicy.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/SunsetPolicy.cs @@ -41,7 +41,6 @@ public SunsetPolicy() { } /// The date and time when the API version will be sunset. /// The optional link which provides information about the sunset policy. public SunsetPolicy( DateTimeOffset date, LinkHeaderValue? link = default ) - : this() { Date = date; 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 3d23a71e..121d3440 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParserExtensions.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParserExtensions.cs @@ -7,31 +7,30 @@ namespace Asp.Versioning; /// public static class IApiVersionParserExtensions { - /// - /// Parses the specified text. - /// /// The extended parser. - /// The text to parse as an API version. - /// The parsed API version. - public static ApiVersion Parse( this IApiVersionParser parser, string? text ) + extension( IApiVersionParser parser ) { - ArgumentNullException.ThrowIfNull( parser ); - return parser.Parse( text == null ? default : text.AsSpan() ); - } + /// + /// Parses the specified text. + /// + /// The text to parse as an API version. + /// The parsed API version. + public ApiVersion Parse( string? text ) + { + ArgumentNullException.ThrowIfNull( parser ); + return parser.Parse( text == null ? default : text.AsSpan() ); + } - /// - /// Attempts to parse the specified text. - /// - /// The extended parser. - /// The text to parse as an API version. - /// The parsed API version or null. - /// True if the parsing was successful; otherwise false. - public static bool TryParse( - this IApiVersionParser parser, - string? text, - [MaybeNullWhen( false )] out ApiVersion apiVersion ) - { - ArgumentNullException.ThrowIfNull( parser ); - return parser.TryParse( text == null ? default : text.AsSpan(), out apiVersion ); + /// + /// Attempts to parse the specified text. + /// + /// The text to parse as an API version. + /// The parsed API version or null. + /// True if the parsing was successful; otherwise false. + public bool TryParse( string? text, [MaybeNullWhen( false )] out ApiVersion apiVersion ) + { + 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/ApiVersionFormatProviderTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionFormatProviderTest.cs index 7b8d2245..abd3684e 100644 --- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionFormatProviderTest.cs +++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionFormatProviderTest.cs @@ -8,7 +8,6 @@ namespace Asp.Versioning; using DateOnly = System.DateTime; #endif using static System.Globalization.CultureInfo; -using static System.String; public class ApiVersionFormatProviderTest { @@ -43,14 +42,19 @@ public void get_format_should_return_expected_format_provider() [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_allow_null_or_empty_format_string( ApiVersionFormatProvider provider ) + public void format_should_allow_null_or_empty_format_string( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var apiVersion = new ApiVersion( 1, 0 ); var expected = new[] { apiVersion.ToString(), apiVersion.ToString() }; // act - var actual = new[] { provider.Format( null, apiVersion, CurrentCulture ), provider.Format( Empty, apiVersion, CurrentCulture ) }; + var actual = new[] + { + provider.Format( null, apiVersion, CurrentCulture ), + provider.Format( string.Empty, apiVersion, CurrentCulture ), + }; // assert actual.Should().Equal( expected ); @@ -59,9 +63,10 @@ public void format_should_allow_null_or_empty_format_string( ApiVersionFormatPro [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_return_full_formatted_string_without_optional_components( ApiVersionFormatProvider provider ) + public void format_should_return_full_formatted_string_without_optional_components( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var apiVersion = ApiVersionParser.Default.Parse( "2017-05-01.1-Beta" ); // act @@ -74,9 +79,10 @@ public void format_should_return_full_formatted_string_without_optional_componen [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_return_full_formatted_string_with_optional_components( ApiVersionFormatProvider provider ) + public void format_should_return_full_formatted_string_with_optional_components( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var apiVersion = ApiVersionParser.Default.Parse( "2017-05-01.1-Beta" ); // act @@ -89,9 +95,10 @@ public void format_should_return_full_formatted_string_with_optional_components( [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_return_original_string_format_when_argument_cannot_be_formatted( ApiVersionFormatProvider provider ) + public void format_should_return_original_string_format_when_argument_cannot_be_formatted( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var value = new object(); var expected = new string[] { "d", value.ToString() }; @@ -104,9 +111,10 @@ public void format_should_return_original_string_format_when_argument_cannot_be_ [Theory] [MemberData( nameof( MalformedLiteralStringsData ) )] - public void format_should_not_allow_malformed_literal_string( ApiVersionFormatProvider provider, string malformedFormat ) + public void format_should_not_allow_malformed_literal_string( FormatProviderKind kind, string malformedFormat ) { // arrange + var provider = GetProvider( kind ); var apiVersion = new ApiVersion( new DateOnly( 2017, 5, 1 ) ); // act @@ -119,9 +127,10 @@ public void format_should_not_allow_malformed_literal_string( ApiVersionFormatPr [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( GroupVersionFormatData ) )] - public void format_should_return_formatted_group_version_string( ApiVersionFormatProvider provider, string format ) + public void format_should_return_formatted_group_version_string( FormatProviderKind kind, string format ) { // arrange + var provider = GetProvider( kind ); var groupVersion = new DateOnly( 2017, 5, 1 ); var apiVersion = new ApiVersion( groupVersion ); var expected = groupVersion.ToString( format, CurrentCulture ); @@ -136,9 +145,10 @@ public void format_should_return_formatted_group_version_string( ApiVersionForma [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_return_formatted_minor_version_string( ApiVersionFormatProvider provider ) + public void format_should_return_formatted_minor_version_string( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var apiVersion = new ApiVersion( 2, 5 ); // act @@ -151,9 +161,10 @@ public void format_should_return_formatted_minor_version_string( ApiVersionForma [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_return_formatted_major_version_string( ApiVersionFormatProvider provider ) + public void format_should_return_formatted_major_version_string( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var apiVersion = new ApiVersion( 2, 5 ); // act @@ -166,9 +177,10 @@ public void format_should_return_formatted_major_version_string( ApiVersionForma [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_return_formatted_major_and_minor_version_string( ApiVersionFormatProvider provider ) + public void format_should_return_formatted_major_and_minor_version_string( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var apiVersion = new ApiVersion( 2, 0 ); // act @@ -181,9 +193,10 @@ public void format_should_return_formatted_major_and_minor_version_string( ApiVe [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_return_formatted_short_version_string( ApiVersionFormatProvider provider ) + public void format_should_return_formatted_short_version_string( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var apiVersion = new ApiVersion( 2, 0 ); // act @@ -196,9 +209,10 @@ public void format_should_return_formatted_short_version_string( ApiVersionForma [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_return_formatted_long_version_string( ApiVersionFormatProvider provider ) + public void format_should_return_formatted_long_version_string( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var apiVersion = ApiVersionParser.Default.Parse( "1-RC" ); // act @@ -211,9 +225,10 @@ public void format_should_return_formatted_long_version_string( ApiVersionFormat [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( FormatProvidersData ) )] - public void format_should_return_formatted_status_string( ApiVersionFormatProvider provider ) + public void format_should_return_formatted_status_string( FormatProviderKind kind ) { // arrange + var provider = GetProvider( kind ); var apiVersion = new ApiVersion( 2, 5, "Beta" ); // act @@ -226,9 +241,10 @@ public void format_should_return_formatted_status_string( ApiVersionFormatProvid [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( PaddedMinorVersionFormatData ) )] - public void format_should_return_formatted_minor_version_with_padding_string( ApiVersionFormatProvider provider, string format ) + public void format_should_return_formatted_minor_version_with_padding_string( FormatProviderKind kind, string format ) { // arrange + var provider = GetProvider( kind ); var numberFormat = format.Replace( "p", "D" ); var apiVersion = new ApiVersion( 2, 5 ); @@ -247,9 +263,10 @@ public void format_should_return_formatted_minor_version_with_padding_string( Ap [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( PaddedMajorVersionFormatData ) )] - public void format_should_return_formatted_major_version_with_padding_string( ApiVersionFormatProvider provider, string format ) + public void format_should_return_formatted_major_version_with_padding_string( FormatProviderKind kind, string format ) { // arrange + var provider = GetProvider( kind ); var numberFormat = format.Replace( "P", "D" ); var apiVersion = new ApiVersion( 2, 5 ); @@ -268,36 +285,66 @@ public void format_should_return_formatted_major_version_with_padding_string( Ap [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( CustomFormatData ) )] - public void format_should_return_custom_format_string( Func format, string expected ) + public void format_should_return_custom_format_string( FormatProviderKind kind, string format, string expected ) + { + // arrange + var provider = GetProvider( kind ); + var groupVersion = new DateOnly( 2017, 5, 1 ); + var apiVersion = new ApiVersion( groupVersion, 1, 0, "Beta" ); + + // act + var actual = provider.Format( format, apiVersion, CurrentCulture ); + + // assert + actual.Should().Be( expected ); + } + + [Theory] + [AssumeCulture( "en-us" )] + [MemberData( nameof( StringCustomFormatData ) )] + public void string_format_should_return_custom_format_string( + FormatProviderKind kind, + string format, + string expected ) { // arrange + var provider = GetProvider( kind ); var groupVersion = new DateOnly( 2017, 5, 1 ); var apiVersion = new ApiVersion( groupVersion, 1, 0, "Beta" ); // act - var actual = format( apiVersion ); + var actual = string.Format( provider, format, apiVersion ); // assert actual.Should().Be( expected ); } +#pragma warning disable xUnit1045 + [Theory] [AssumeCulture( "en-us" )] [MemberData( nameof( MultipleFormatParameterData ) )] - public void format_should_return_formatted_string_with_multiple_parameters( ApiVersionFormatProvider provider, string format, object secondArgument, string expected ) + public void format_should_return_formatted_string_with_multiple_parameters( + FormatProviderKind kind, + string format, + object secondArgument, + string expected ) { // arrange + var provider = GetProvider( kind ); var groupVersion = new DateOnly( 2017, 5, 1 ); var apiVersion = new ApiVersion( groupVersion, 1, 0, "Beta" ); var args = new object[] { apiVersion, secondArgument }; // act - var status = Format( provider, format, args ); + var status = string.Format( provider, format, args ); // assert status.Should().Be( expected ); } +#pragma warning restore xUnit1045 + [Fact] [AssumeCulture( "en-us" )] public void format_should_return_formatted_string_with_escape_sequence() @@ -314,116 +361,183 @@ public void format_should_return_formatted_string_with_escape_sequence() result.Should().Be( "1.0 ('17)" ); } - public static IEnumerable FormatProvidersData + /// + /// Represents the supported test format providers. + /// + public enum FormatProviderKind { - get - { - yield return new object[] { new ApiVersionFormatProvider() }; - yield return new object[] { new ApiVersionFormatProvider( DateTimeFormatInfo.CurrentInfo ) }; - yield return new object[] { new ApiVersionFormatProvider( new GregorianCalendar() ) }; - yield return new object[] { new ApiVersionFormatProvider( DateTimeFormatInfo.CurrentInfo, new GregorianCalendar() ) }; - } + /// + /// . + /// + Default, + + /// + /// . + /// + DateTime, + + /// + /// . + /// + Calendar, + + /// + /// . + /// + DateTimeAndCalendar, } - public static IEnumerable MalformedLiteralStringsData + private static ApiVersionFormatProvider GetProvider( FormatProviderKind provider ) => + provider switch + { + FormatProviderKind.DateTime => new( DateTimeFormatInfo.CurrentInfo ), + FormatProviderKind.Calendar => new( new GregorianCalendar() ), + FormatProviderKind.DateTimeAndCalendar => new( DateTimeFormatInfo.CurrentInfo, new GregorianCalendar() ), + _ => new ApiVersionFormatProvider(), + }; + + public static TheoryData FormatProvidersData => + [ + FormatProviderKind.Default, + FormatProviderKind.DateTime, + FormatProviderKind.Calendar, + FormatProviderKind.DateTimeAndCalendar, + ]; + + public static TheoryData MalformedLiteralStringsData { get { - foreach ( var provider in FormatProvidersData.Select( d => d[0] ).Cast() ) + var data = new TheoryData(); + + foreach ( var provider in FormatProvidersData ) { - yield return new object[] { provider, "'MM-dd-yyyy" }; - yield return new object[] { provider, "MM-dd-yyyy'" }; - yield return new object[] { provider, "\"MM-dd-yyyy" }; - yield return new object[] { provider, "MM-dd-yyyy\"" }; + data.Add( provider, "'MM-dd-yyyy" ); + data.Add( provider, "MM-dd-yyyy'" ); + data.Add( provider, "\"MM-dd-yyyy" ); + data.Add( provider, "MM-dd-yyyy\"" ); } + + return data; } } - public static IEnumerable GroupVersionFormatData + public static TheoryData GroupVersionFormatData { get { + var data = new TheoryData(); var formats = new[] { "%d", "dd", "ddd", "dddd", "%M", "MM", "MMM", "MMMM", "%y", "yy", "yyy", "yyyy" }; - foreach ( var provider in FormatProvidersData.Select( d => d[0] ).Cast() ) + foreach ( var provider in FormatProvidersData ) { foreach ( var format in formats ) { - yield return new object[] { provider, format }; + data.Add( provider, format ); } } + + return data; } } - public static IEnumerable PaddedMinorVersionFormatData + public static TheoryData PaddedMinorVersionFormatData { get { + var data = new TheoryData(); var formats = new[] { "p", "p0", "p1", "p2", "p3" }; - foreach ( var provider in FormatProvidersData.Select( d => d[0] ).Cast() ) + foreach ( var provider in FormatProvidersData ) { foreach ( var format in formats ) { - yield return new object[] { provider, format }; + data.Add( provider, format ); } } + + return data; } } - public static IEnumerable PaddedMajorVersionFormatData + public static TheoryData PaddedMajorVersionFormatData { get { + var data = new TheoryData(); var formats = new[] { "P", "P0", "P1", "P2", "P3" }; - foreach ( var provider in FormatProvidersData.Select( d => d[0] ).Cast() ) + foreach ( var provider in FormatProvidersData ) { foreach ( var format in formats ) { - yield return new object[] { provider, format }; + data.Add( provider, format ); } } + + return data; + } + } + + public static TheoryData CustomFormatData + { + get + { + var data = new TheoryData(); + + foreach ( var provider in FormatProvidersData ) + { + data.Add( provider, "'v'F", "v2017-05-01.1.0-Beta" ); + data.Add( provider, "'v'FF", "v2017-05-01.1.0-Beta" ); + data.Add( provider, "'v'V", "v1" ); + data.Add( provider, "'v'VV", "v1.0" ); + data.Add( provider, "V'.'v", "1.0" ); + data.Add( provider, "P.p", "01.00" ); + data.Add( provider, "'Group:' G, 'Version:' V.v, 'Status:' S", "Group: 2017-05-01, Version: 1.0, Status: Beta" ); + data.Add( provider, "'Group:' yyyy-MM-dd, 'Version:' V.v, 'Status:' S", "Group: 2017-05-01, Version: 1.0, Status: Beta" ); + } + + return data; } } - public static IEnumerable CustomFormatData + public static TheoryData StringCustomFormatData { get { - foreach ( var provider in FormatProvidersData.Select( d => d[0] ).Cast() ) + var data = new TheoryData(); + + foreach ( var provider in FormatProvidersData ) { - yield return new object[] { new Func( v => provider.Format( "'v'F", v, CurrentCulture ) ), "v2017-05-01.1.0-Beta" }; - yield return new object[] { new Func( v => provider.Format( "'v'FF", v, CurrentCulture ) ), "v2017-05-01.1.0-Beta" }; - yield return new object[] { new Func( v => Format( provider, "v{0:F}", v ) ), "v2017-05-01.1.0-Beta" }; - yield return new object[] { new Func( v => Format( provider, "v{0:FF}", v ) ), "v2017-05-01.1.0-Beta" }; - yield return new object[] { new Func( v => provider.Format( "'v'V", v, CurrentCulture ) ), "v1" }; - yield return new object[] { new Func( v => provider.Format( "'v'VV", v, CurrentCulture ) ), "v1.0" }; - yield return new object[] { new Func( v => Format( provider, "v{0:V}", v ) ), "v1" }; - yield return new object[] { new Func( v => Format( provider, "v{0:VV}", v ) ), "v1.0" }; - yield return new object[] { new Func( v => provider.Format( "V'.'v", v, CurrentCulture ) ), "1.0" }; - yield return new object[] { new Func( v => Format( provider, "{0:V}.{0:v}", v ) ), "1.0" }; - yield return new object[] { new Func( v => provider.Format( "P.p", v, CurrentCulture ) ), "01.00" }; - yield return new object[] { new Func( v => Format( provider, "{0:P3}.{0:p3}", v ) ), "001.000" }; - yield return new object[] { new Func( v => provider.Format( "'Group:' G, 'Version:' V.v, 'Status:' S", v, CurrentCulture ) ), "Group: 2017-05-01, Version: 1.0, Status: Beta" }; - yield return new object[] { new Func( v => provider.Format( "'Group:' yyyy-MM-dd, 'Version:' V.v, 'Status:' S", v, CurrentCulture ) ), "Group: 2017-05-01, Version: 1.0, Status: Beta" }; - yield return new object[] { new Func( v => Format( provider, "{0:\"Group:\" G, \"Version:\" V.v, \"Status:\" S}", v ) ), "Group: 2017-05-01, Version: 1.0, Status: Beta" }; - yield return new object[] { new Func( v => Format( provider, "{0:\"Group:\" yyyy-MM-dd, \"Version:\" V.v, \"Status:\" S}", v ) ), "Group: 2017-05-01, Version: 1.0, Status: Beta" }; + data.Add( provider, "v{0:F}", "v2017-05-01.1.0-Beta" ); + data.Add( provider, "v{0:FF}", "v2017-05-01.1.0-Beta" ); + data.Add( provider, "v{0:V}", "v1" ); + data.Add( provider, "v{0:VV}", "v1.0" ); + data.Add( provider, "{0:V}.{0:v}", "1.0" ); + data.Add( provider, "{0:P3}.{0:p3}", "001.000" ); + data.Add( provider, "{0:\"Group:\" G, \"Version:\" V.v, \"Status:\" S}", "Group: 2017-05-01, Version: 1.0, Status: Beta" ); + data.Add( provider, "{0:\"Group:\" yyyy-MM-dd, \"Version:\" V.v, \"Status:\" S}", "Group: 2017-05-01, Version: 1.0, Status: Beta" ); } + + return data; } } - public static IEnumerable MultipleFormatParameterData + public static TheoryData MultipleFormatParameterData { get { - foreach ( var provider in FormatProvidersData.Select( d => d[0] ).Cast() ) + var data = new TheoryData(); + + foreach ( var provider in FormatProvidersData ) { - yield return new object[] { provider, "{0:yyyy}->{0:MM}->{0:dd} ({1})", "Group", "2017->05->01 (Group)" }; - yield return new object[] { provider, "{0:'v'VV}, Deprecated = {1}", false, "v1.0, Deprecated = False" }; - yield return new object[] { provider, "Major = {0:V}, Minor = {0:v}, Iteration = {1:N1}", 1, "Major = 1, Minor = 0, Iteration = 1.0" }; - yield return new object[] { provider, "Major\t| Minor\t| Iteration\n{0:P}\t\t| {0:p}\t| {1:N1}", 1, "Major\t| Minor\t| Iteration\n01\t\t| 00\t| 1.0" }; + data.Add( provider, "{0:yyyy}->{0:MM}->{0:dd} ({1})", "Group", "2017->05->01 (Group)" ); + data.Add( provider, "{0:'v'VV}, Deprecated = {1}", false, "v1.0, Deprecated = False" ); + data.Add( provider, "Major = {0:V}, Minor = {0:v}, Iteration = {1:N1}", 1, "Major = 1, Minor = 0, Iteration = 1.0" ); + data.Add( provider, "Major\t| Minor\t| Iteration\n{0:P}\t\t| {0:p}\t| {1:N1}", 1, "Major\t| Minor\t| Iteration\n01\t\t| 00\t| 1.0" ); } + + return data; } } } \ No newline at end of file diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionMetadataTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionMetadataTest.cs index 75b5da64..eb6d220c 100644 --- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionMetadataTest.cs +++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionMetadataTest.cs @@ -43,14 +43,14 @@ public void map_should_return_merge_models() { // arrange var apiModel = new ApiVersionModel( new ApiVersion( 1.0 ) ); - var endpointModel = new ApiVersionModel( new ApiVersion[] { new( 1.0 ) }, new ApiVersion[] { new( 0.9 ) } ); + var endpointModel = new ApiVersionModel( [new( 1.0 )], [new( 0.9 )] ); var metadata = new ApiVersionMetadata( apiModel, endpointModel ); var expected = new ApiVersionModel( - new ApiVersion[] { new( 1.0 ) }, - new ApiVersion[] { new( 1.0 ) }, - new ApiVersion[] { new( 0.9 ) }, - Enumerable.Empty(), - Enumerable.Empty() ); + [new( 1.0 )], + [new( 1.0 )], + [new( 0.9 )], + [], + [] ); // act var result = metadata.Map( Explicit | Implicit ); diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionModelExtensionsTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionModelExtensionsTest.cs index 880ed7ef..2ae78ca2 100644 --- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionModelExtensionsTest.cs +++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionModelExtensionsTest.cs @@ -8,9 +8,9 @@ public class ApiVersionModelExtensionsTest public void aggregate_should_merge_api_version_models() { // arrange - var model1 = new ApiVersionModel( new[] { new ApiVersion( 1, 0 ) }, new[] { new ApiVersion( 0, 9 ) } ); - var model2 = new ApiVersionModel( new[] { new ApiVersion( 2, 0 ) }, Enumerable.Empty() ); - var expected = new ApiVersionModel( new[] { new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ) }, new[] { new ApiVersion( 0, 9 ) } ); + var model1 = new ApiVersionModel( [new ApiVersion( 1, 0 )], [new ApiVersion( 0, 9 )] ); + var model2 = new ApiVersionModel( [new ApiVersion( 2, 0 )], [] ); + var expected = new ApiVersionModel( [new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 )], [new ApiVersion( 0, 9 )] ); // act var aggregatedModel = model1.Aggregate( model2 ); @@ -31,15 +31,15 @@ public void aggregate_should_merge_api_version_models() public void aggregate_should_merge_api_version_model_sequence() { // arrange - var model = new ApiVersionModel( new[] { new ApiVersion( 1, 0 ) }, new[] { new ApiVersion( 0, 9 ) } ); + var model = new ApiVersionModel( [new ApiVersion( 1, 0 )], [new ApiVersion( 0, 9 )] ); var otherModels = new[] { - new ApiVersionModel( new[] { new ApiVersion( 2, 0 ) }, Enumerable.Empty() ), - new ApiVersionModel( new[] { new ApiVersion( 3, 0 ) }, new[] { new ApiVersion( 3, 0, "Alpha" ) } ), + new ApiVersionModel( [new ApiVersion( 2, 0 )], [] ), + new ApiVersionModel( [new ApiVersion( 3, 0 )], [new ApiVersion( 3, 0, "Alpha" )] ), }; var expected = new ApiVersionModel( - new[] { new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0 ) }, - new[] { new ApiVersion( 0, 9 ), new ApiVersion( 3, 0, "Alpha" ) } ); + [new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0 )], + [new ApiVersion( 0, 9 ), new ApiVersion( 3, 0, "Alpha" )] ); // act var aggregatedModel = model.Aggregate( otherModels ); @@ -60,9 +60,9 @@ public void aggregate_should_merge_api_version_model_sequence() public void aggregate_should_not_merge_deprecated_api_version_when_also_supported() { // arrange - var model1 = new ApiVersionModel( new[] { new ApiVersion( 1, 0 ) }, Enumerable.Empty() ); - var model2 = new ApiVersionModel( new[] { new ApiVersion( 2, 0 ) }, new[] { new ApiVersion( 1, 0 ) } ); - var expected = new ApiVersionModel( new[] { new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ) }, Enumerable.Empty() ); + var model1 = new ApiVersionModel( [new ApiVersion( 1, 0 )], [] ); + var model2 = new ApiVersionModel( [new ApiVersion( 2, 0 )], [new ApiVersion( 1, 0 )] ); + var expected = new ApiVersionModel( [new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 )], [] ); // act var aggregatedModel = model1.Aggregate( model2 ); diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionTest.cs index 11c896a5..7d0a9899 100644 --- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionTest.cs +++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionTest.cs @@ -596,29 +596,28 @@ public void api_version_1_ge_api_version_2_should_return_expected_result( string result.Should().Be( expected ); } - public static IEnumerable FormatData => - new object[][] - { - [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)"], - }; + public static TheoryData FormatData => new() + { + { 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 private static DateTime Today => DateTime.Today; 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 e2fcdd62..b01159c3 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,6 +1,6 @@  - $(DefaultTargetFramework);net452;net472 + $(DefaultTargetFramework);net472 Asp.Versioning @@ -9,8 +9,8 @@ - - + + diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/AssumeCultureAttribute.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/AssumeCultureAttribute.cs index 9c956d87..395a9550 100644 --- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/AssumeCultureAttribute.cs +++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/AssumeCultureAttribute.cs @@ -4,7 +4,6 @@ namespace Asp.Versioning; using System.Globalization; using System.Reflection; -using Xunit.Sdk; using static System.AttributeTargets; using static System.Threading.Thread; @@ -12,7 +11,7 @@ namespace Asp.Versioning; /// Allows a test method to assume that it is running in a specific locale. /// [AttributeUsage( Class | Method, AllowMultiple = false, Inherited = true )] -public sealed class AssumeCultureAttribute : BeforeAfterTestAttribute +internal sealed class AssumeCultureAttribute : BeforeAfterTestAttribute { private CultureInfo originalCulture; private CultureInfo originalUICulture; @@ -21,7 +20,7 @@ public sealed class AssumeCultureAttribute : BeforeAfterTestAttribute public string Name { get; } - public override void Before( MethodInfo methodUnderTest ) + public override void Before( MethodInfo methodUnderTest, IXunitTest test ) { originalCulture = CurrentThread.CurrentCulture; originalUICulture = CurrentThread.CurrentUICulture; @@ -32,7 +31,7 @@ public override void Before( MethodInfo methodUnderTest ) CurrentThread.CurrentUICulture = culture; } - public override void After( MethodInfo methodUnderTest ) + public override void After( MethodInfo methodUnderTest, IXunitTest test ) { CurrentThread.CurrentCulture = originalCulture; CurrentThread.CurrentUICulture = originalUICulture; diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/Conventions/ApiVersionConventionBuilderBaseTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/Conventions/ApiVersionConventionBuilderBaseTest.cs index fecf03a0..e414329b 100644 --- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/Conventions/ApiVersionConventionBuilderBaseTest.cs +++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/Conventions/ApiVersionConventionBuilderBaseTest.cs @@ -51,10 +51,10 @@ public void merge_should_add_api_versions_from_attributes() new AdvertiseApiVersionsAttribute( "2.0-Beta" ) { Deprecated = true }, }; var expected = new ApiVersionModel( - new ApiVersion[] { new( 1.0 ) }, - new ApiVersion[] { new( 0.9 ) }, - new ApiVersion[] { new( 2.0 ) }, - new ApiVersion[] { new( 2.0, "Beta" ) } ); + [new( 1.0 )], + [new( 0.9 )], + [new( 2.0 )], + [new( 2.0, "Beta" )] ); var builder = new TestApiVersionConventionBuilder(); // act diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/NamespaceParserTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/NamespaceParserTest.cs index 9ee20823..186743b1 100644 --- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/NamespaceParserTest.cs +++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/NamespaceParserTest.cs @@ -2,6 +2,7 @@ namespace Asp.Versioning; +using System; using System.Reflection; #if NETFRAMEWORK using DateOnly = System.DateTime; @@ -11,10 +12,11 @@ public class NamespaceParserTest { [Theory] [MemberData( nameof( NamespaceWithOneVersion ) )] - public void parse_should_return_single_version( Type type, ApiVersion expected ) + public void parse_should_return_single_version( string @namespace, string version ) { // arrange - + var type = new TestType( @namespace ); + var expected = ApiVersionParser.Default.Parse( version ); // act var result = NamespaceParser.Default.Parse( type ); @@ -25,10 +27,11 @@ public void parse_should_return_single_version( Type type, ApiVersion expected ) [Theory] [MemberData( nameof( NamespaceWithMultipleVersions ) )] - public void parse_should_return_multiple_versions( Type type, ApiVersion[] expected ) + public void parse_should_return_multiple_versions( string @namespace, string[] versions ) { // arrange - + var type = new TestType( @namespace ); + var expected = versions.Select( static v => ApiVersionParser.Default.Parse( v ) ).ToArray(); // act var result = NamespaceParser.Default.Parse( type ); @@ -50,42 +53,39 @@ public void parse_should_return_no_versions() result.Should().BeEmpty(); } - public static IEnumerable NamespaceWithOneVersion + public static TheoryData NamespaceWithOneVersion => new() { - get - { - yield return new object[] { new TestType( "v1" ), new ApiVersion( 1 ) }; - yield return new object[] { new TestType( "v1RC" ), new ApiVersion( 1, 0, "RC" ) }; - yield return new object[] { new TestType( "v20180401" ), new ApiVersion( new DateOnly( 2018, 4, 1 ) ) }; - yield return new object[] { new TestType( "v20180401_Beta" ), new ApiVersion( new DateOnly( 2018, 4, 1 ), "Beta" ) }; - yield return new object[] { new TestType( "v20180401Beta" ), new ApiVersion( new DateOnly( 2018, 4, 1 ), "Beta" ) }; - yield return new object[] { new TestType( "Contoso.Api.v1.Controllers" ), new ApiVersion( 1 ) }; - yield return new object[] { new TestType( "Contoso.Api.v1_1.Controllers" ), new ApiVersion( 1, 1 ) }; - yield return new object[] { new TestType( "Contoso.Api.v0_9_Beta.Controllers" ), new ApiVersion( 0, 9, "Beta" ) }; - yield return new object[] { new TestType( "Contoso.Api.v20180401.Controllers" ), new ApiVersion( new DateOnly( 2018, 4, 1 ) ) }; - yield return new object[] { new TestType( "Contoso.Api.v2018_04_01.Controllers" ), new ApiVersion( new DateOnly( 2018, 4, 1 ) ) }; - yield return new object[] { new TestType( "Contoso.Api.v20180401_Beta.Controllers" ), new ApiVersion( new DateOnly( 2018, 4, 1 ), "Beta" ) }; - yield return new object[] { new TestType( "Contoso.Api.v2018_04_01_Beta.Controllers" ), new ApiVersion( new DateOnly( 2018, 4, 1 ), "Beta" ) }; - yield return new object[] { new TestType( "Contoso.Api.v2018_04_01_1_0_Beta.Controllers" ), new ApiVersion( new DateOnly( 2018, 4, 1 ), 1, 0, "Beta" ) }; - yield return new object[] { new TestType( "MyRestaurant.Vegetarian.Food.v1_1.Controllers" ), new ApiVersion( 1, 1 ) }; - yield return new object[] { new TestType( "VersioningSample.V5.Controllers" ), new ApiVersion( 5, 0 ) }; - } - } - - public static IEnumerable NamespaceWithMultipleVersions + { "v1", "1" }, + { "v1RC", "1.0-RC" }, + { "v20180401", "2018-04-01" }, + { "v20180401_Beta", "2018-04-01-Beta" }, + { "v20180401Beta", "2018-04-01-Beta" }, + { "Contoso.Api.v1.Controllers", "1" }, + { "Contoso.Api.v1_1.Controllers", "1.1" }, + { "Contoso.Api.v0_9_Beta.Controllers", "0.9-Beta" }, + { "Contoso.Api.v20180401.Controllers", "2018-04-01" }, + { "Contoso.Api.v2018_04_01.Controllers", "2018-04-01" }, + { "Contoso.Api.v20180401_Beta.Controllers", "2018-04-01-Beta" }, + { "Contoso.Api.v2018_04_01_Beta.Controllers", "2018-04-01-Beta" }, + { "Contoso.Api.v2018_04_01_1_0_Beta.Controllers", "2018-04-01.1.0-Beta" }, + { "MyRestaurant.Vegetarian.Food.v1_1.Controllers", "1.1" }, + { "VersioningSample.V5.Controllers", "5.0" }, + }; + + public static TheoryData NamespaceWithMultipleVersions => new() { - get - { - yield return new object[] { new TestType( "Contoso.Api.v1.Controllers.v1" ), new ApiVersion[] { new( 1 ), new( 1 ) } }; - yield return new object[] { new TestType( "Contoso.Api.v1_1.Controllers.v1" ), new ApiVersion[] { new( 1, 1 ), new( 1 ) } }; - yield return new object[] { new TestType( "Contoso.Api.v2_0.Controllers.v2" ), new ApiVersion[] { new( 2, 0 ), new( 2 ) } }; - yield return new object[] { new TestType( "Contoso.Api.v20180401.Controllers.v1" ), new ApiVersion[] { new( new DateOnly( 2018, 4, 1 ) ), new( 1 ) } }; - yield return new object[] { new TestType( "Contoso.Api.v2018_04_01.Controllers.v2_0_Beta" ), new ApiVersion[] { new( new DateOnly( 2018, 4, 1 ) ), new( 2, 0, "Beta" ) } }; - yield return new object[] { new TestType( "v2018_04_01.Controllers.v2_0_RC" ), new ApiVersion[] { new( new DateOnly( 2018, 4, 1 ) ), new( 2, 0, "RC" ) } }; - } - } - - private sealed class TestType : TypeDelegator + { "Contoso.Api.v1.Controllers.v1", ["1", "1"] }, + { "Contoso.Api.v1_1.Controllers.v1", ["1.1", "1"] }, + { "Contoso.Api.v2_0.Controllers.v2", ["2.0", "2"] }, + { "Contoso.Api.v20180401.Controllers.v1", ["2018-04-01", "1"] }, + { "Contoso.Api.v2018_04_01.Controllers.v2_0_Beta", ["2018-04-01", "2.0-Beta"] }, + { "v2018_04_01.Controllers.v2_0_RC", ["2018-04-01", "2.0-RC"] }, + }; + +#pragma warning disable IDE0079 +#pragma warning disable CA1034 + + public sealed class TestType : TypeDelegator { public TestType( string @namespace ) : base( typeof( object ) ) => Namespace = @namespace; diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Asp.Versioning.WebApi.Acceptance.Tests.csproj b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Asp.Versioning.WebApi.Acceptance.Tests.csproj index 29f5fbf5..9161f592 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Asp.Versioning.WebApi.Acceptance.Tests.csproj +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Asp.Versioning.WebApi.Acceptance.Tests.csproj @@ -1,13 +1,13 @@  - net452;net472 + net472 Asp.Versioning - - + + diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/HelloWorldController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/HelloWorldController.cs index 835d8bb4..9dd276ec 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/HelloWorldController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/HelloWorldController.cs @@ -9,10 +9,10 @@ namespace Asp.Versioning.Http.Basic.Controllers; public class HelloWorldController : ApiController { [Route] - public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id:int}", Name = "GetMessageById" )] - public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.RequestedApiVersion.ToString() } ); [Route] public IHttpActionResult Post() => CreatedAtRoute( "GetMessageById", new { id = 42 }, default( object ) ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/Values2Controller.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/Values2Controller.cs index 72b73dc8..0d009353 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/Values2Controller.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/Values2Controller.cs @@ -8,5 +8,5 @@ namespace Asp.Versioning.Http.Basic.Controllers; [Route( "api/values" )] public class Values2Controller : ApiController { - public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/ValuesController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/ValuesController.cs index 7a6b7959..be5b9844 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/ValuesController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/Controllers/ValuesController.cs @@ -8,5 +8,5 @@ namespace Asp.Versioning.Http.Basic.Controllers; [Route( "api/values" )] public class ValuesController : ApiController { - public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a version-neutral ApiController/when no version is specified.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a version-neutral ApiController/when no version is specified.cs index 95dbb7c1..54aa5857 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a version-neutral ApiController/when no version is specified.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a version-neutral ApiController/when no version is specified.cs @@ -30,7 +30,7 @@ public async Task then_post_should_return_405() // act var response = await PostAsync( "api/ping", entity ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( TestContext.Current.CancellationToken ); var traceId = problem.Extensions["traceId"]; // assert 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 index b40bfa14..2382bc4f 100644 --- 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 @@ -27,7 +27,7 @@ public async Task then_the_response_should_not_be_problem_details() // act var response = await GetAsync( "api/values?api-version=3.0" ); - var error = await response.Content.ReadAsExampleAsync( example ); + var error = await response.Content.ReadAsExampleAsync( example, TestContext.Current.CancellationToken ); // assert response.Content.Headers.ContentType.MediaType.Should().Be( "application/json" ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when using a query string and split into two types.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when using a query string and split into two types.cs index cffde314..24166651 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when using a query string and split into two types.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when using a query string and split into two types.cs @@ -21,7 +21,9 @@ public async Task then_get_should_return_200( string controller, string apiVersi // act var response = await GetAsync( $"api/values?api-version={apiVersion}" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsAsync>(); + var content = await response.EnsureSuccessStatusCode() + .Content + .ReadAsAsync>( CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); @@ -41,7 +43,7 @@ public async Task then_get_should_return_400_for_an_unsupported_version() // act var response = await GetAsync( "api/values?api-version=3.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -56,7 +58,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/values" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when using a url segment.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when using a url segment.cs index 06db743d..868f5ddc 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when using a url segment.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when using a url segment.cs @@ -30,7 +30,9 @@ public async Task then_get_should_return_200( string requestUrl, string id ) // act var response = await GetAsync( requestUrl ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsAsync>(); + var content = await response.EnsureSuccessStatusCode() + .Content + .ReadAsAsync>( CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0" ); @@ -59,7 +61,7 @@ public async Task then_get_should_return_404_for_an_unsupported_version() // act var response = await GetAsync( "api/v2/helloworld" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/HelloWorld2Controller.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/HelloWorld2Controller.cs index 54ad07b4..99ffbd74 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/HelloWorld2Controller.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/HelloWorld2Controller.cs @@ -8,14 +8,14 @@ namespace Asp.Versioning.Http.UsingConventions.Controllers; public class HelloWorld2Controller : ApiController { [Route] - public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id:int}" )] - public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.RequestedApiVersion.ToString() } ); [Route] - public IHttpActionResult GetV3() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult GetV3() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id:int}" )] - public IHttpActionResult GetV3( int id ) => Ok( new { controller = GetType().Name, id, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult GetV3( int id ) => Ok( new { controller = GetType().Name, id, version = Request.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/HelloWorldController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/HelloWorldController.cs index 15c01971..c48e8c98 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/HelloWorldController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/HelloWorldController.cs @@ -8,8 +8,8 @@ namespace Asp.Versioning.Http.UsingConventions.Controllers; public class HelloWorldController : ApiController { [Route] - public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id:int}" )] - public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/Values2Controller.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/Values2Controller.cs index f960c756..5d6c3766 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/Values2Controller.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/Values2Controller.cs @@ -8,14 +8,14 @@ namespace Asp.Versioning.Http.UsingConventions.Controllers; public class Values2Controller : ApiController { [Route] - public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id:int}" )] - public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.RequestedApiVersion.ToString() } ); [Route] - public IHttpActionResult GetV3() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult GetV3() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id:int}" )] - public IHttpActionResult GetV3( int id ) => Ok( new { controller = GetType().Name, id, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult GetV3( int id ) => Ok( new { controller = GetType().Name, id, version = Request.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/ValuesController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/ValuesController.cs index 9a64718a..b54359b2 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/ValuesController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/Controllers/ValuesController.cs @@ -8,8 +8,8 @@ namespace Asp.Versioning.Http.UsingConventions.Controllers; public class ValuesController : ApiController { [Route] - public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id:int}" )] - public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/given a versioned ApiController using conventions/when using a query string and split into two types.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/given a versioned ApiController using conventions/when using a query string and split into two types.cs index 1080b60e..b56e3945 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/given a versioned ApiController using conventions/when using a query string and split into two types.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/given a versioned ApiController using conventions/when using a query string and split into two types.cs @@ -22,7 +22,9 @@ public async Task then_get_should_return_200( string controller, string apiVersi // act var response = await GetAsync( $"api/values?api-version={apiVersion}" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsAsync>(); + var content = await response.EnsureSuccessStatusCode() + .Content + .ReadAsAsync>( CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0, 3.0" ); @@ -42,7 +44,7 @@ public async Task then_get_should_return_400_for_an_unsupported_version() // act var response = await GetAsync( "api/values?api-version=4.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -57,7 +59,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/values" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/given a versioned ApiController using conventions/when using a url segment.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/given a versioned ApiController using conventions/when using a url segment.cs index 49e0b020..16d41e64 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/given a versioned ApiController using conventions/when using a url segment.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingConventions/given a versioned ApiController using conventions/when using a url segment.cs @@ -21,7 +21,7 @@ public async Task then_get_should_return_200( string requestUrl, string controll // act var response = await GetAsync( requestUrl ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "2.0, 3.0, 4.0" ); @@ -40,7 +40,7 @@ public async Task then_get_with_id_should_return_200( string requestUrl, string // act var response = await GetAsync( requestUrl ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "2.0, 3.0, 4.0" ); @@ -56,7 +56,7 @@ public async Task then_get_should_return_404_for_an_unsupported_version() // act var response = await GetAsync( "api/v4/helloworld" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/HelloWorldController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/HelloWorldController.cs index 6854840f..e8bbbbb7 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/HelloWorldController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/HelloWorldController.cs @@ -10,10 +10,10 @@ namespace Asp.Versioning.Http.UsingMediaType.Controllers; public class HelloWorldController : ApiController { [Route] - public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id:int}", Name = "GetMessageById" )] - public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.GetRequestedApiVersion().ToString() } ); + public IHttpActionResult Get( int id ) => Ok( new { controller = GetType().Name, id, version = Request.RequestedApiVersion.ToString() } ); [Route] public IHttpActionResult Post( Message message ) => CreatedAtRoute( "GetMessageById", new { id = 42 }, message ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/Values2Controller.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/Values2Controller.cs index 0d13c326..2568a638 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/Values2Controller.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/Values2Controller.cs @@ -11,11 +11,11 @@ public class Values2Controller : ApiController { [Route] public IHttpActionResult Get() => - Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id}", Name = "GetByIdV2" )] public IHttpActionResult Get( string id ) => - Ok( new { controller = GetType().Name, Id = id, version = Request.GetRequestedApiVersion().ToString() } ); + Ok( new { controller = GetType().Name, Id = id, version = Request.RequestedApiVersion.ToString() } ); public IHttpActionResult Post( [FromBody] JToken json ) => CreatedAtRoute( "GetByIdV2", new { id = "42" }, json ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/ValuesController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/ValuesController.cs index 785904d7..3e7a0bb6 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/ValuesController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/ValuesController.cs @@ -10,9 +10,9 @@ public class ValuesController : ApiController { [Route] public IHttpActionResult Get() => - Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } ); + Ok( new { controller = GetType().Name, version = Request.RequestedApiVersion.ToString() } ); [Route( "{id}" )] public IHttpActionResult Get( string id ) => - Ok( new { controller = GetType().Name, Id = id, version = Request.GetRequestedApiVersion().ToString() } ); + Ok( new { controller = GetType().Name, Id = id, version = Request.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/given a versioned ApiController/when using media type negotiation.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/given a versioned ApiController/when using media type negotiation.cs index 7cbe560c..8d14e085 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/given a versioned ApiController/when using media type negotiation.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/given a versioned ApiController/when using media type negotiation.cs @@ -26,9 +26,9 @@ public async Task then_get_should_return_200( string controller, string apiVersi }; // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); var body = response.EnsureSuccessStatusCode().Content; - var content = await body.ReadAsExampleAsync( example ); + var content = await body.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); @@ -46,8 +46,8 @@ public async Task then_get_should_return_406_for_an_unsupported_version() }; // act - var response = await Client.SendAsync( request ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var response = await Client.SendAsync( request, CancellationToken ); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotAcceptable ); @@ -63,8 +63,8 @@ public async Task then_post_should_return_415_for_an_unsupported_version() using var content = new ObjectContent( entity.GetType(), entity, new JsonMediaTypeFormatter(), mediaType ); // act - var response = await Client.PostAsync( "api/values", content ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var response = await Client.PostAsync( "api/values", content, CancellationToken ); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( UnsupportedMediaType ); @@ -82,7 +82,7 @@ public async Task then_get_should_allow_an_unspecified_version( string requestUr // act var response = await GetAsync( requestUrl ); var body = response.EnsureSuccessStatusCode().Content; - var content = await body.ReadAsExampleAsync( example ); + var content = await body.ReadAsExampleAsync( example, CancellationToken ); // assert body.Headers.ContentType.Parameters.Single( p => p.Name == "v" ).Value.Should().Be( apiVersion ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V1/AgreementsController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V1/AgreementsController.cs index b845a185..a32d1ac5 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V1/AgreementsController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V1/AgreementsController.cs @@ -7,5 +7,5 @@ namespace Asp.Versioning.Http.UsingNamespace.Controllers.V1; public class AgreementsController : ApiController { - public IHttpActionResult Get( string accountId ) => Ok( new Agreement( GetType().FullName, accountId, Request.GetRequestedApiVersion().ToString() ) ); + public IHttpActionResult Get( string accountId ) => Ok( new Agreement( GetType().FullName, accountId, Request.RequestedApiVersion.ToString() ) ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V2/AgreementsController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V2/AgreementsController.cs index 8dc0eb17..399aaec5 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V2/AgreementsController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V2/AgreementsController.cs @@ -7,5 +7,5 @@ namespace Asp.Versioning.Http.UsingNamespace.Controllers.V2; public class AgreementsController : ApiController { - public IHttpActionResult Get( string accountId ) => Ok( new Agreement( GetType().FullName, accountId, Request.GetRequestedApiVersion().ToString() ) ); + public IHttpActionResult Get( string accountId ) => Ok( new Agreement( GetType().FullName, accountId, Request.RequestedApiVersion.ToString() ) ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V3/AgreementsController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V3/AgreementsController.cs index 7487fe5a..40e384a8 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V3/AgreementsController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/Controllers/V3/AgreementsController.cs @@ -7,5 +7,5 @@ namespace Asp.Versioning.Http.UsingNamespace.Controllers.V3; public class AgreementsController : ApiController { - public IHttpActionResult Get( string accountId ) => Ok( new Agreement( GetType().FullName, accountId, Request.GetRequestedApiVersion().ToString() ) ); + public IHttpActionResult Get( string accountId ) => Ok( new Agreement( GetType().FullName, accountId, Request.RequestedApiVersion.ToString() ) ); } \ 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 query string.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a query string.cs index 06c84e76..d2445bc7 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a query string.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a query string.cs @@ -24,7 +24,7 @@ public async Task then_get_should_return_200( Type controllerType, string apiVer // act var response = await GetAsync( $"api/agreements/42?api-version={apiVersion}" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1, 2, 3" ); @@ -39,7 +39,7 @@ public async Task then_get_should_return_400_for_an_unsupported_version() // act var response = await GetAsync( "api/agreements/42?api-version=4.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -54,7 +54,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/agreements/42" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); 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 88f11bf8..3115d57a 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 @@ -28,7 +28,7 @@ public async Task then_get_should_return_200( Type controllerType, string apiVer // act var response = await GetAsync( $"v{apiVersion}/agreements/42" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1, 2, 3" ); @@ -43,7 +43,7 @@ public async Task then_get_should_return_404_for_an_unsupported_version() // act var response = await GetAsync( "v4/agreements/42" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/Orders2Controller.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/Orders2Controller.cs index 9809b540..0e3f55c5 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/Orders2Controller.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/Orders2Controller.cs @@ -17,9 +17,9 @@ public class Orders2Controller : ODataController { [ODataRoute] public IHttpActionResult Get( ODataQueryOptions options ) => - Ok( new[] { new Order() { Id = 1, Customer = $"Customer v{Request.GetRequestedApiVersion()}" } } ); + Ok( new[] { new Order() { Id = 1, Customer = $"Customer v{Request.RequestedApiVersion}" } } ); [ODataRoute( "{key}" )] public IHttpActionResult Get( int key, ODataQueryOptions options ) => - Ok( new Order() { Id = key, Customer = $"Customer v{Request.GetRequestedApiVersion()}" } ); + Ok( new Order() { Id = key, Customer = $"Customer v{Request.RequestedApiVersion}" } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/Orders3Controller.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/Orders3Controller.cs index 46736d97..6f92f8e7 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/Orders3Controller.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/Orders3Controller.cs @@ -9,7 +9,7 @@ namespace Asp.Versioning.OData.Advanced.Controllers; [ControllerName( "Orders" )] public class Orders3Controller : ApiController { - public IHttpActionResult Get() => Ok( new[] { new Order() { Id = 1, Customer = $"Customer v{Request.GetRequestedApiVersion()}" } } ); + public IHttpActionResult Get() => Ok( new[] { new Order() { Id = 1, Customer = $"Customer v{Request.RequestedApiVersion}" } } ); - public IHttpActionResult Get( int key ) => Ok( new Order() { Id = key, Customer = $"Customer v{Request.GetRequestedApiVersion()}" } ); + public IHttpActionResult Get( int key ) => Ok( new Order() { Id = key, Customer = $"Customer v{Request.RequestedApiVersion}" } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/OrdersController.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/OrdersController.cs index 0ae253e5..1ce13e3d 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/OrdersController.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/Controllers/OrdersController.cs @@ -7,7 +7,7 @@ namespace Asp.Versioning.OData.Advanced.Controllers; public class OrdersController : ApiController { - public IHttpActionResult Get() => Ok( new[] { new Order() { Id = 1, Customer = $"Customer v{Request.GetRequestedApiVersion()}" } } ); + public IHttpActionResult Get() => Ok( new[] { new Order() { Id = 1, Customer = $"Customer v{Request.RequestedApiVersion}" } } ); - public IHttpActionResult Get( int key ) => Ok( new Order() { Id = key, Customer = $"Customer v{Request.GetRequestedApiVersion()}" } ); + public IHttpActionResult Get( int key ) => Ok( new Order() { Id = key, Customer = $"Customer v{Request.RequestedApiVersion}" } ); } \ No newline at end of file diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ApiController mixed with OData controllers/when orders is v1.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ApiController mixed with OData controllers/when orders is v1.cs index a5b975cf..17769ace 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ApiController mixed with OData controllers/when orders is v1.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ApiController mixed with OData controllers/when orders is v1.cs @@ -13,13 +13,12 @@ public async Task then_get_should_return_200_for_an_unspecified_version() // arrange var example = new[] { new { Id = 0, Customer = "" } }; - // act var response = await GetAsync( "api/orders" ); - var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert - orders.Should().BeEquivalentTo( new[] { new { Id = 1, Customer = "Customer v1.0" } } ); + orders.Should().BeEquivalentTo( [new { Id = 1, Customer = "Customer v1.0" }] ); } [Fact] @@ -28,13 +27,12 @@ public async Task then_get_should_return_200() // arrange var example = new[] { new { Id = 0, Customer = "" } }; - // act var response = await GetAsync( "api/orders?api-version=1.0" ); - var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert - orders.Should().BeEquivalentTo( new[] { new { Id = 1, Customer = "Customer v1.0" } } ); + orders.Should().BeEquivalentTo( [new { Id = 1, Customer = "Customer v1.0" }] ); } [Fact] @@ -43,10 +41,9 @@ public async Task then_get_with_key_should_return_200_for_an_unspecified_version // arrange var example = new { Id = 0, Customer = "" }; - // act var response = await GetAsync( "api/orders/42" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( new { Id = 42, Customer = "Customer v1.0" } ); @@ -56,11 +53,11 @@ public async Task then_get_with_key_should_return_200_for_an_unspecified_version public async Task then_get_with_key_should_return_200() { // arrange - + var example = new { Id = 0, Customer = "" }; // act var response = await GetAsync( "api/orders/42?api-version=1.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( new { Id = 0, Customer = "" } ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( new { Id = 42, Customer = "Customer v1.0" } ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ApiController mixed with OData controllers/when orders is v3.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ApiController mixed with OData controllers/when orders is v3.cs index 30ee4b30..ce1a3a7e 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ApiController mixed with OData controllers/when orders is v3.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ApiController mixed with OData controllers/when orders is v3.cs @@ -16,10 +16,10 @@ public async Task then_get_should_return_200() // act var response = await GetAsync( "api/orders?api-version=3.0" ); - var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert - orders.Should().BeEquivalentTo( new[] { new { Id = 1, Customer = "Customer v3.0" } } ); + orders.Should().BeEquivalentTo( [new { Id = 1, Customer = "Customer v3.0" }] ); } [Fact] @@ -31,7 +31,7 @@ public async Task then_get_with_key_should_return_200_for_an_unspecified_version // act var response = await GetAsync( "api/orders/42?api-version=3.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( new { Id = 42, Customer = "Customer v3.0" } ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when orders is v2.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when orders is v2.cs index 7e028287..b3e3660c 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when orders is v2.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when orders is v2.cs @@ -13,14 +13,13 @@ public async Task then_get_should_return_200() // arrange var example = new { value = new[] { new { id = 0, customer = "" } } }; - // act var response = await GetAsync( "api/orders?api-version=2.0" ); - var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert orders.value.Should().BeEquivalentTo( - new[] { new { id = 1, customer = "Customer v2.0" } }, + [new { id = 1, customer = "Customer v2.0" }], options => options.ExcludingMissingMembers() ); } @@ -30,10 +29,9 @@ public async Task then_get_with_key_should_return_200() // arrange var example = new { id = 0, customer = "" }; - // act var response = await GetAsync( "api/orders/42?api-version=2.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is any version.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is any version.cs index b354d6f6..5cbdd898 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is any version.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is any version.cs @@ -16,7 +16,7 @@ public async Task then_patch_should_return_400_for_an_unsupported_version() // act var response = await PatchAsync( $"api/people/42?api-version=4.0", person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v1.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v1.cs index 8ceb3261..23f466ab 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v1.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v1.cs @@ -18,11 +18,11 @@ public async Task then_get_should_return_200( string requestUrl ) // act var response = await GetAsync( requestUrl ); - var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert people.value.Should().BeEquivalentTo( - new[] { new { id = 1, firstName = "Bill", lastName = "Mei" } }, + [new { id = 1, firstName = "Bill", lastName = "Mei" }], options => options.ExcludingMissingMembers() ); } @@ -36,7 +36,7 @@ public async Task then_get_with_key_should_return_200( string requestUrl ) // act var response = await GetAsync( requestUrl ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( @@ -52,7 +52,7 @@ public async Task then_patch_should_return_405_if_supported_in_any_version() // act var response = await PatchAsync( $"api/people/42?api-version=1.0", person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( MethodNotAllowed ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v2.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v2.cs index f7cb6003..b94b9989 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v2.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v2.cs @@ -16,11 +16,11 @@ public async Task then_get_should_return_200() // act var response = await GetAsync( "api/people?api-version=2.0" ); - var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert people.value.Should().BeEquivalentTo( - new[] { new { id = 1, firstName = "Bill", lastName = "Mei", email = "bill.mei@somewhere.com" } }, + [new { id = 1, firstName = "Bill", lastName = "Mei", email = "bill.mei@somewhere.com" }], options => options.ExcludingMissingMembers() ); } @@ -32,7 +32,7 @@ public async Task then_get_with_key_should_return_200() // act var response = await GetAsync( "api/people/42?api-version=2.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v3.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v3.cs index 7590c760..f363f33e 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v3.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with Web API controllers/when people is v3.cs @@ -16,11 +16,11 @@ public async Task then_get_should_return_200() // act var response = await GetAsync( "api/people?api-version=3.0" ); - var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert people.value.Should().BeEquivalentTo( - new[] { new { id = 1, firstName = "Bill", lastName = "Mei", email = "bill.mei@somewhere.com", phone = "555-555-5555" } }, + [new { id = 1, firstName = "Bill", lastName = "Mei", email = "bill.mei@somewhere.com", phone = "555-555-5555" }], options => options.ExcludingMissingMembers() ); } @@ -32,7 +32,7 @@ public async Task then_get_with_key_should_return_200() // act var response = await GetAsync( "api/people/42?api-version=3.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( @@ -48,7 +48,7 @@ public async Task then_patch_should_return_405_if_supported_in_any_version() // act var response = await PatchAsync( $"api/people/42?api-version=3.0", person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( MethodNotAllowed ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/BasicAcceptanceTest.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/BasicAcceptanceTest.cs index 65a5334e..6de03bc4 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/BasicAcceptanceTest.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/BasicAcceptanceTest.cs @@ -16,7 +16,7 @@ public async Task then_service_document_should_return_404_for_unsupported_url_ap // act var response = await GetAsync( requestUrl ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); @@ -34,7 +34,7 @@ public async Task then_the_service_document_should_return_only_path_for_an_unsup // act var response = await GetAsync( requestUrl ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert @@ -51,8 +51,8 @@ public async Task then_X24metadata_should_return_404_for_unsupported_url_api_ver Client.DefaultRequestHeaders.Clear(); // act - var response = await Client.GetAsync( "v4/$metadata" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var response = await Client.GetAsync( "v4/$metadata", CancellationToken ); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string and split into two types.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string and split into two types.cs index ad4f5364..c59deb08 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string and split into two types.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string and split into two types.cs @@ -35,7 +35,7 @@ public async Task then_get_should_return_400_for_an_unsupported_version() // act var response = await GetAsync( "api/people?api-version=4.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -65,7 +65,7 @@ public async Task then_patch_should_return_405_if_supported_in_any_version( stri // act var response = await PatchAsync( requestUrl, person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( MethodNotAllowed ); @@ -80,7 +80,7 @@ public async Task then_patch_should_return_400_for_an_unsupported_version() // act var response = await PatchAsync( "api/people/42?api-version=4.0", person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -95,7 +95,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/people" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string.cs index b73cde2c..6206938f 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string.cs @@ -31,7 +31,7 @@ public async Task then_get_should_return_400_for_an_unsupported_version() // act var response = await GetAsync( "api/orders?api-version=2.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -46,7 +46,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/orders" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment and split into two types.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment and split into two types.cs index 2bf77644..d0303e72 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment and split into two types.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment and split into two types.cs @@ -50,7 +50,7 @@ public async Task then_patch_should_return_405_if_supported_in_any_version( stri // act var response = await PatchAsync( requestUrl, person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( MethodNotAllowed ); @@ -65,7 +65,7 @@ public async Task then_patch_should_return_404_for_an_unsupported_version() // act var response = await PatchAsync( "v4/people/42", person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment.cs index 6fad8acf..8600b3de 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment.cs @@ -31,7 +31,7 @@ public async Task then_get_should_return_404_for_an_unsupported_version() // act var response = await GetAsync( "v2/orders" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); 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 9c420a43..9c3c2ac6 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 @@ -46,7 +46,7 @@ public async Task then_the_service_document_should_return_400_for_an_unsupported // act var response = await GetAsync( "api?api-version=4.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -60,7 +60,7 @@ public async Task then_X24metadata_should_allow_an_unspecified_version() Client.DefaultRequestHeaders.Clear(); // act - var response = await Client.GetAsync( "api/$metadata" ); + var response = await Client.GetAsync( "api/$metadata", CancellationToken ); // assert response.StatusCode.Should().Be( OK ); @@ -78,7 +78,7 @@ public async Task then_X24metadata_should_be_versionX2Dspecific( string apiVersi Client.DefaultRequestHeaders.Clear(); // act - var response = await Client.GetAsync( requestUrl ); + var response = await Client.GetAsync( requestUrl, CancellationToken ); // assert response.StatusCode.Should().Be( OK ); @@ -91,8 +91,8 @@ public async Task then_X24metadata_should_return_400_for_an_unsupported_version( Client.DefaultRequestHeaders.Clear(); // act - var response = await Client.GetAsync( "api/$metadata?api-version=4.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var response = await Client.GetAsync( "api/$metadata?api-version=4.0", CancellationToken ); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string and split into two types.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string and split into two types.cs index 46e15b8d..d51a670e 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string and split into two types.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string and split into two types.cs @@ -35,7 +35,7 @@ public async Task then_get_should_return_400_for_an_unsupported_version() // act var response = await GetAsync( "api/people?api-version=4.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -65,7 +65,7 @@ public async Task then_patch_should_return_405_if_supported_in_any_version( stri // act var response = await PatchAsync( requestUrl, person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( MethodNotAllowed ); @@ -80,7 +80,7 @@ public async Task then_patch_should_return_400_for_an_unsupported_version() // act var response = await PatchAsync( "api/people/42?api-version=4.0", person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -95,7 +95,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/people" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string.cs index 4956d346..7372503e 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string.cs @@ -31,7 +31,7 @@ public async Task then_get_should_return_400_for_an_unsupported_version() // act var response = await GetAsync( "api/orders?api-version=2.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -46,7 +46,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/orders" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment and split into two types.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment and split into two types.cs index 911d0a3d..e5e81ea4 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment and split into two types.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment and split into two types.cs @@ -50,7 +50,7 @@ public async Task then_patch_should_return_405_if_supported_in_any_version( stri // act var response = await PatchAsync( requestUrl, person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( MethodNotAllowed ); @@ -65,7 +65,7 @@ public async Task then_patch_should_return_404_for_an_unsupported_version() // act var response = await PatchAsync( "v4/people/42", person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment.cs index 04a72ab9..7ce421a5 100644 --- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment.cs +++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment.cs @@ -31,7 +31,7 @@ public async Task then_get_should_return_404_for_an_unsupported_version() // act var response = await GetAsync( "v2/orders" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); 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 f6744216..028d70ab 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 @@ -50,7 +50,7 @@ private static IReadOnlyList FilterResults( { if ( conventions.Count == 0 ) { - return Array.Empty(); + return []; } var results = default( List ); @@ -59,7 +59,7 @@ private static IReadOnlyList FilterResults( { var apiDescription = apiDescriptions[i]; - if ( apiDescription.EdmModel() != null || !apiDescription.IsODataLike() ) + if ( apiDescription.EdmModel != null || !apiDescription.IsODataLike ) { continue; } @@ -83,12 +83,12 @@ private static void ApplyAdHocEdm( for ( var i = 0; i < models.Count; i++ ) { var model = models[i]; - var version = model.GetApiVersion(); + var version = model.ApiVersion; for ( var j = 0; j < results.Count; j++ ) { var result = results[j]; - var metadata = result.ActionDescriptor.GetApiVersionMetadata(); + var metadata = result.ActionDescriptor.ApiVersionMetadata; if ( metadata.IsMappedTo( version ) ) { 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 24399a61..aa2d7eb7 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 @@ -69,7 +69,7 @@ public ODataApiExplorer( HttpConfiguration configuration, ODataApiExplorerOption /// /// The associated mode type builder. protected virtual IModelTypeBuilder ModelTypeBuilder => - modelTypeBuilder ??= Configuration.DependencyResolver.GetModelTypeBuilder(); + modelTypeBuilder ??= Configuration.DependencyResolver.ModelTypeBuilder; /// protected override bool ShouldExploreAction( @@ -85,7 +85,7 @@ protected override bool ShouldExploreAction( return base.ShouldExploreAction( actionRouteParameterValue, actionDescriptor, route, apiVersion ); } - if ( actionDescriptor.ControllerDescriptor.ControllerType.IsMetadataController() ) + if ( actionDescriptor.ControllerDescriptor.ControllerType.IsMetadataController ) { if ( actionDescriptor.ActionName == nameof( MetadataController.GetServiceDocument ) ) { @@ -113,7 +113,7 @@ protected override bool ShouldExploreAction( } } - return actionDescriptor.GetApiVersionMetadata().IsMappedTo( apiVersion ); + return actionDescriptor.ApiVersionMetadata.IsMappedTo( apiVersion ); } /// @@ -126,7 +126,7 @@ protected override bool ShouldExploreController( ArgumentNullException.ThrowIfNull( controllerDescriptor ); ArgumentNullException.ThrowIfNull( route ); - if ( controllerDescriptor.ControllerType.IsMetadataController() ) + if ( controllerDescriptor.ControllerType.IsMetadataController ) { controllerDescriptor.ControllerName = "OData"; return Options.MetadataOptions > ODataMetadataOptions.None; @@ -546,9 +546,12 @@ private void PopulateActionDescriptions( var requestFormatters = new List(); var responseFormatters = new List(); var supportedMethods = GetHttpMethodsSupportedByAction( route, actionDescriptor ); - var metadata = actionDescriptor.GetApiVersionMetadata(); + var metadata = actionDescriptor.ApiVersionMetadata; var model = metadata.Map( ApiVersionMapping.Explicit ); - var deprecated = !model.IsApiVersionNeutral && model.DeprecatedApiVersions.Contains( apiVersion ); + var deprecationPolicy = DeprecationPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ); + var deprecated = model.IsApiVersionNeutral + ? deprecationPolicy != null && deprecationPolicy.IsEffective( DateTimeOffset.Now ) + : model.DeprecatedApiVersions.Contains( apiVersion ); PopulateMediaTypeFormatters( actionDescriptor, routeBuilderContext.ParameterDescriptions, route, responseType, requestFormatters, responseFormatters ); @@ -566,7 +569,7 @@ private void PopulateActionDescriptions( ApiVersion = apiVersion, IsDeprecated = deprecated, SunsetPolicy = SunsetPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ), - DeprecationPolicy = DeprecationPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ), + DeprecationPolicy = deprecationPolicy, Properties = { [typeof( IEdmModel )] = routeBuilderContext.EdmModel }, }; 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 89372768..9e5f7ed2 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.1.1 - 7.1.0.0 + 10.0.0 + 10.0.0.0 net45;net472 Asp.Versioning ASP.NET Web API Versioning API Explorer for OData v4.0 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 410f48ff..b8a58478 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.ToArray() ); + VisitEnableQuery( [.. attributes] ); } } \ No newline at end of file diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataQueryOptionDescriptionContext.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataQueryOptionDescriptionContext.cs index 1218eda3..4ce7327d 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataQueryOptionDescriptionContext.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataQueryOptionDescriptionContext.cs @@ -13,7 +13,7 @@ namespace Asp.Versioning.Conventions; public partial class ODataQueryOptionDescriptionContext { [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static IEdmModel? ResolveModel( ApiDescription description ) => description.EdmModel(); + private static IEdmModel? ResolveModel( ApiDescription description ) => description.EdmModel; private static bool HasSingleResult( ApiDescription description, out Type? resultType ) { diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs new file mode 100644 index 00000000..b1aa222b --- /dev/null +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.Conventions; + +using Microsoft.AspNet.OData.Query; + +/// +/// Provides additional implementation specific to Microsoft ASP.NET Web API. +/// +public partial class ODataQueryOptionSettings +{ + private DefaultQuerySettings? querySettings; + + /// + /// Gets or sets the default OData query settings. + /// + /// The default OData query settings. + public DefaultQuerySettings DefaultQuerySettings + { + get => querySettings ??= new(); + set => querySettings = value; + } +} \ No newline at end of file diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/DependencyResolverExtensions.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/DependencyResolverExtensions.cs index 1175a093..5752f712 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/DependencyResolverExtensions.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/DependencyResolverExtensions.cs @@ -7,8 +7,11 @@ namespace Asp.Versioning; internal static class DependencyResolverExtensions { - internal static TService? GetService( this IDependencyResolver resolver ) => (TService) resolver.GetService( typeof( TService ) ); + extension( IDependencyResolver resolver ) + { + internal TService? GetService() => (TService) resolver.GetService( typeof( TService ) ); - internal static IModelTypeBuilder GetModelTypeBuilder( this IDependencyResolver resolver ) => - resolver.GetService() ?? new DefaultModelTypeBuilder(); + internal IModelTypeBuilder ModelTypeBuilder => + resolver.GetService() ?? new DefaultModelTypeBuilder(); + } } \ No newline at end of file diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ReleaseNotes.txt b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ReleaseNotes.txt index e48e0271..5f282702 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ReleaseNotes.txt +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ReleaseNotes.txt @@ -1 +1 @@ -Fixed invalid property setter on substituted type ([#1104](https://github.com/dotnet/aspnet-api-versioning/issues/1104)) \ No newline at end of file + \ No newline at end of file diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Routing/ODataRouteBuilder.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Routing/ODataRouteBuilder.cs index 0e9b49f4..a69fc9f0 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Routing/ODataRouteBuilder.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Routing/ODataRouteBuilder.cs @@ -71,14 +71,14 @@ internal IReadOnlyList ExpandNavigationPropertyLinkTemplate( string temp { if ( IsNullOrEmpty( template ) ) { - return Array.Empty(); + return []; } var token = Concat( "{", NavigationProperty, "}" ); if ( template.IndexOf( token, OrdinalIgnoreCase ) < 0 ) { - return new[] { template }; + return [template]; } IEdmEntityType entity; @@ -92,7 +92,7 @@ internal IReadOnlyList ExpandNavigationPropertyLinkTemplate( string temp entity = Context.Singleton.EntityType(); break; default: - return Array.Empty(); + return []; } var properties = entity.NavigationProperties().ToArray(); @@ -289,7 +289,7 @@ private void AppendPathFromConventions( IList segments, string controlle default: var action = Context.ActionDescriptor; - if ( action.ControllerDescriptor.ControllerType.IsMetadataController() ) + if ( action.ControllerDescriptor.ControllerType.IsMetadataController ) { if ( action.ActionName == nameof( MetadataController.GetServiceDocument ) ) { 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 3ff17b36..64414f43 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 @@ -44,7 +44,7 @@ internal ODataRouteBuilderContext( ParameterDescriptions = parameterDescriptions; Options = options; UrlKeyDelimiter = UrlKeyDelimiterOrDefault( - configuration.GetUrlKeyDelimiter() ?? + configuration.UrlKeyDelimiter ?? Services.GetService()?.UrlKeyDelimiter ); var selector = Services.GetRequiredService(); @@ -67,7 +67,7 @@ internal ODataRouteBuilderContext( Operation = ResolveOperation( container, actionDescriptor ); ActionType = GetActionType( actionDescriptor ); IsRouteExcluded = ActionType == ODataRouteActionType.Unknown && - !actionDescriptor.ControllerDescriptor.ControllerType.IsMetadataController(); + !actionDescriptor.ControllerDescriptor.ControllerType.IsMetadataController; if ( Operation?.IsAction() == true ) { @@ -100,7 +100,7 @@ private void ConvertODataActionParametersToTypedModel( IModelTypeBuilder modelTy var description = ParameterDescriptions[i]; var parameter = description.ParameterDescriptor; - if ( parameter != null && parameter.ParameterType.IsODataActionParameters() ) + if ( parameter != null && parameter.ParameterType.IsODataActionParameters ) { var selector = Services.GetRequiredService(); var model = selector.SelectModel( ApiVersion )!; @@ -120,9 +120,9 @@ private static IList FilterParameters( HttpActionDescri { var type = parameters[i].ParameterType; - if ( type.IsODataQueryOptions() || - type.IsODataActionParameters() || - type.IsODataPath() || + if ( type.IsODataQueryOptions || + type.IsODataActionParameters || + type.IsODataPath || type.Equals( cancellationToken ) ) { parameters.RemoveAt( i ); diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs index cba8d13e..cc32e94f 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http.Description; using Asp.Versioning.Description; @@ -11,104 +13,118 @@ namespace System.Web.Http.Description; /// public static class ApiDescriptionExtensions { - /// - /// Gets the entity data model (EDM) associated with the API description. - /// /// The API description to get the model for. - /// The associated EDM model or null if there is no associated model. - public static IEdmModel? EdmModel( this ApiDescription apiDescription ) + extension( ApiDescription apiDescription ) { - if ( apiDescription is VersionedApiDescription description ) + /// + /// Gets the entity data model (EDM) associated with the API description. + /// + /// The associated EDM model or null if there is no associated model. + public IEdmModel? EdmModel { - return description.GetProperty(); - } - - return default; - } + get + { + if ( apiDescription is VersionedApiDescription description ) + { + return description.GetProperty(); + } - /// - /// Gets the entity set associated with the API description. - /// - /// The API description to get the entity set for. - /// The associated entity set or null if there is no associated entity set. - public static IEdmEntitySet? EntitySet( this ApiDescription apiDescription ) - { - if ( apiDescription is not VersionedApiDescription description ) - { - return default; + return default; + } } - var key = typeof( IEdmEntitySet ); - - if ( description.Properties.TryGetValue( key, out var value ) ) + /// + /// Gets the entity set associated with the API description. + /// + /// The associated entity set or null if there is no associated entity set. + public IEdmEntitySet? EntitySet { - return (IEdmEntitySet) value; - } + get + { + if ( apiDescription is not VersionedApiDescription description ) + { + return default; + } - var container = description.EdmModel()?.EntityContainer; + var key = typeof( IEdmEntitySet ); - if ( container == null ) - { - return default; - } + if ( description.Properties.TryGetValue( key, out var value ) ) + { + return (IEdmEntitySet) value; + } - var entitySetName = description.ActionDescriptor.ControllerDescriptor.ControllerName; - var entitySet = container.FindEntitySet( entitySetName ); + var container = description.EdmModel?.EntityContainer; - description.Properties[key] = entitySet; + if ( container == null ) + { + return default; + } - return entitySet; - } + var entitySetName = description.ActionDescriptor.ControllerDescriptor.ControllerName; + var entitySet = container.FindEntitySet( entitySetName ); - /// - /// Gets the entity type associated with the API description. - /// - /// The API description to get the entity type for. - /// The associated entity type or null if there is no associated entity type. - public static IEdmEntityType? EntityType( this ApiDescription apiDescription ) => apiDescription.EntitySet()?.EntityType(); - - /// - /// Gets the operation associated with the API description. - /// - /// The API description to get the operation for. - /// The associated EDM operation or null if there is no associated operation. - public static IEdmOperation? Operation( this ApiDescription apiDescription ) - { - if ( apiDescription is VersionedApiDescription description ) - { - return description.GetProperty(); - } + description.Properties[key] = entitySet; - return default; - } + return entitySet; + } + } - /// - /// Gets the route prefix associated with the API description. - /// - /// The API description to get the route prefix for. - /// The associated route prefix or null. - public static string? RoutePrefix( this ApiDescription apiDescription ) - { - if ( apiDescription == null ) + /// + /// Gets the entity type associated with the API description. + /// + /// The associated entity type or null if there is no associated entity type. + public IEdmEntityType? EntityType => apiDescription.EntitySet?.EntityType(); + + /// + /// Gets the operation associated with the API description. + /// + /// The associated EDM operation or null if there is no associated operation. + public IEdmOperation? Operation { - throw new ArgumentNullException( nameof( apiDescription ) ); + get + { + if ( apiDescription is VersionedApiDescription description ) + { + return description.GetProperty(); + } + + return default; + } } - return apiDescription.Route is ODataRoute route ? route.RoutePrefix : default; - } + /// + /// Gets the route prefix associated with the API description. + /// + /// The associated route prefix or null. + public string? RoutePrefix + { + get + { + if ( apiDescription == null ) + { + throw new ArgumentNullException( nameof( apiDescription ) ); + } - internal static bool IsODataLike( this ApiDescription description ) - { - var parameters = description.ParameterDescriptions; + return apiDescription.Route is ODataRoute route ? route.RoutePrefix : default; + } + } - for ( var i = 0; i < parameters.Count; i++ ) + internal bool IsODataLike { - if ( parameters[i].ParameterDescriptor.ParameterType.IsODataQueryOptions() ) + get { - return true; + var parameters = apiDescription.ParameterDescriptions; + + for ( var i = 0; i < parameters.Count; i++ ) + { + if ( parameters[i].ParameterDescriptor.ParameterType.IsODataQueryOptions ) + { + return true; + } + } + + return false; } } - - return false; } } \ No newline at end of file diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs index 19002503..f0bd302f 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning; @@ -15,88 +17,93 @@ namespace System.Web.Http; /// public static class HttpConfigurationExtensions { - /// - /// Adds or replaces the configured API explorer with an implementation that supports OData and API versioning. - /// /// The configuration used to add the API explorer. - /// The newly registered versioned OData API explorer. - /// This method always replaces the with a new instance of . This method also - /// configures the to not use , which enables exploring all OData - /// controllers without additional configuration. - public static ODataApiExplorer AddODataApiExplorer( this HttpConfiguration configuration ) + extension( HttpConfiguration configuration ) { - if ( configuration == null ) + /// + /// Adds or replaces the configured API explorer with an implementation that supports OData and API versioning. + /// + /// The newly registered versioned OData API explorer. + /// This method always replaces the with a new instance of . This method also + /// configures the to not use , which enables exploring all OData + /// controllers without additional configuration. + public ODataApiExplorer AddODataApiExplorer() { - throw new ArgumentNullException( nameof( configuration ) ); - } + if ( configuration == null ) + { + throw new ArgumentNullException( nameof( configuration ) ); + } - return configuration.AddODataApiExplorer( new ODataApiExplorerOptions( configuration ) ); - } - - /// - /// Adds or replaces the configured API explorer with an implementation that supports OData and API versioning. - /// - /// The configuration used to add the API explorer. - /// An action used to configure the provided options. - /// The newly registered versioned API explorer. - /// This method always replaces the with a new instance of . - public static ODataApiExplorer AddODataApiExplorer( this HttpConfiguration configuration, Action setupAction ) - { - if ( configuration == null ) - { - throw new ArgumentNullException( nameof( configuration ) ); + return configuration.AddODataApiExplorer( new ODataApiExplorerOptions( configuration ) ); } - if ( setupAction == null ) + /// + /// Adds or replaces the configured API explorer with an implementation that supports OData and API versioning. + /// + /// An action used to configure the provided options. + /// The newly registered versioned API explorer. + /// This method always replaces the with a new instance of . + public ODataApiExplorer AddODataApiExplorer( Action setupAction ) { - throw new ArgumentNullException( nameof( setupAction ) ); - } + if ( configuration == null ) + { + throw new ArgumentNullException( nameof( configuration ) ); + } - var options = new ODataApiExplorerOptions( configuration ); + if ( setupAction == null ) + { + throw new ArgumentNullException( nameof( setupAction ) ); + } - setupAction( options ); - return configuration.AddODataApiExplorer( options ); - } + var options = new ODataApiExplorerOptions( configuration ); - private static ODataApiExplorer AddODataApiExplorer( this HttpConfiguration configuration, ODataApiExplorerOptions options ) - { - var apiExplorer = new ODataApiExplorer( configuration, options ); - configuration.Services.Replace( typeof( IApiExplorer ), apiExplorer ); - return apiExplorer; - } - - internal static IServiceProvider GetODataRootContainer( this HttpConfiguration configuration, IHttpRoute route ) - { - const string RootContainerMappingsKey = "Microsoft.AspNet.OData.RootContainerMappingsKey"; - const string NonODataRootContainerKey = "Microsoft.AspNet.OData.NonODataRootContainerKey"; - var properties = configuration.Properties; - var containers = (ConcurrentDictionary) properties.GetOrAdd( RootContainerMappingsKey, key => new ConcurrentDictionary() ); - var routeName = configuration.Routes.GetRouteName( route ); + setupAction( options ); + return configuration.AddODataApiExplorer( options ); + } - if ( !string.IsNullOrEmpty( routeName ) && containers.TryGetValue( routeName!, out var serviceProvider ) ) + private ODataApiExplorer AddODataApiExplorer( ODataApiExplorerOptions options ) { - return serviceProvider; + var apiExplorer = new ODataApiExplorer( configuration, options ); + configuration.Services.Replace( typeof( IApiExplorer ), apiExplorer ); + return apiExplorer; } - if ( route is not ODataRoute && - properties.TryGetValue( NonODataRootContainerKey, out var value ) && - ( serviceProvider = value as IServiceProvider ) is not null ) + internal IServiceProvider GetODataRootContainer( IHttpRoute route ) { - return serviceProvider; + const string RootContainerMappingsKey = "Microsoft.AspNet.OData.RootContainerMappingsKey"; + const string NonODataRootContainerKey = "Microsoft.AspNet.OData.NonODataRootContainerKey"; + var properties = configuration.Properties; + var containers = (ConcurrentDictionary) properties.GetOrAdd( RootContainerMappingsKey, key => new ConcurrentDictionary() ); + var routeName = configuration.Routes.GetRouteName( route ); + + if ( !string.IsNullOrEmpty( routeName ) && containers.TryGetValue( routeName!, out var serviceProvider ) ) + { + return serviceProvider; + } + + if ( route is not ODataRoute && + properties.TryGetValue( NonODataRootContainerKey, out var value ) && + ( serviceProvider = value as IServiceProvider ) is not null ) + { + return serviceProvider; + } + + throw new InvalidOperationException( ODataExpSR.NullContainer ); } - throw new InvalidOperationException( ODataExpSR.NullContainer ); - } + internal ODataUrlKeyDelimiter? UrlKeyDelimiter + { + get + { + const string UrlKeyDelimiterKey = "Microsoft.AspNet.OData.UrlKeyDelimiterKey"; - internal static ODataUrlKeyDelimiter? GetUrlKeyDelimiter( this HttpConfiguration configuration ) - { - const string UrlKeyDelimiterKey = "Microsoft.AspNet.OData.UrlKeyDelimiterKey"; + if ( configuration.Properties.TryGetValue( UrlKeyDelimiterKey, out var value ) ) + { + return value as ODataUrlKeyDelimiter; + } - if ( configuration.Properties.TryGetValue( UrlKeyDelimiterKey, out var value ) ) - { - return value as ODataUrlKeyDelimiter; + return default; + } } - - return default; } } \ No newline at end of file diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/HttpRouteCollectionExtensions.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/HttpRouteCollectionExtensions.cs index ad0ef36e..62ef7135 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/HttpRouteCollectionExtensions.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/System.Web.Http/HttpRouteCollectionExtensions.cs @@ -1,21 +1,26 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using System.Web.Http.Routing; internal static class HttpRouteCollectionExtensions { - internal static string? GetRouteName( this HttpRouteCollection routes, IHttpRoute route ) + extension( HttpRouteCollection routes ) { - foreach ( var item in routes.ToDictionary() ) + internal string? GetRouteName( IHttpRoute route ) { - if ( Equals( item.Value, route ) ) + foreach ( var item in routes.ToDictionary() ) { - return item.Key; + if ( Equals( item.Value, route ) ) + { + return item.Key; + } } - } - return default; + return default; + } } } \ No newline at end of file diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/net45/TupleExtensions.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/net45/TupleExtensions.cs index fd45ba9e..83028f9a 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/net45/TupleExtensions.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/net45/TupleExtensions.cs @@ -1,12 +1,17 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System; internal static class TupleExtensions { - internal static void Deconstruct( this Tuple tuple, out T1 item1, out T2 item2 ) + extension( Tuple tuple ) { - item1 = tuple.Item1; - item2 = tuple.Item2; + internal void Deconstruct( out T1 item1, out T2 item2 ) + { + item1 = tuple.Item1; + item2 = tuple.Item2; + } } } \ 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 7229ae69..e820e9f1 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.1.0 - 7.1.0.0 + 10.0.0 + 10.0.0.0 net45;net472 Asp.Versioning API Versioning for ASP.NET Web API with OData v4.0 @@ -15,7 +15,7 @@ - + diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Controllers/VersionedMetadataController.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Controllers/VersionedMetadataController.cs index 572e5a00..45fe77a4 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Controllers/VersionedMetadataController.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Controllers/VersionedMetadataController.cs @@ -58,7 +58,7 @@ public virtual IHttpActionResult GetOptions() }, }; - response.Content.Headers.Add( "Allow", new[] { "GET", "OPTIONS" } ); + response.Content.Headers.Add( "Allow", ["GET", "OPTIONS"] ); response.Headers.Add( ODataConstants.ODataVersionHeader, ODataUtils.ODataVersionToString( ODataVersion.V4 ) ); diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Microsoft.OData/IContainerBuilderExtensions.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Microsoft.OData/IContainerBuilderExtensions.cs index 101fd54f..9b5215f6 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Microsoft.OData/IContainerBuilderExtensions.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Microsoft.OData/IContainerBuilderExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.OData; using Asp.Versioning.OData; @@ -16,32 +18,34 @@ namespace Microsoft.OData; /// public static class IContainerBuilderExtensions { - /// - /// Adds service API versioning to the specified container builder. - /// /// The extended container builder. - /// The name of the route to add API versioning to. - /// The sequence of EDM models to use for parsing OData paths. /// The original . - public static IContainerBuilder AddApiVersioning( this IContainerBuilder builder, string routeName, IEnumerable models ) => - builder.AddService( Transient, sp => sp.GetRequiredService().SelectModel( sp ) ) - .AddService( Singleton, sp => NewEdmModelSelector( sp, models ) ) - .AddService( Singleton, sp => NewRoutingConventions( sp, routeName ) ); + extension( IContainerBuilder builder ) + { + /// + /// Adds service API versioning to the specified container builder. + /// + /// The name of the route to add API versioning to. + /// The sequence of EDM models + /// to use for parsing OData paths. + public IContainerBuilder AddApiVersioning( string routeName, IEnumerable models ) => + builder.AddService( Transient, sp => sp.GetRequiredService().SelectModel( sp ) ) + .AddService( Singleton, sp => NewEdmModelSelector( sp, models ) ) + .AddService( Singleton, sp => NewRoutingConventions( sp, routeName ) ); - /// - /// Adds service API versioning to the specified container builder. - /// - /// The extended container builder. - /// The sequence of EDM models to use for parsing OData paths. - /// The OData routing conventions to use for controller and action selection. - /// The original . - public static IContainerBuilder AddApiVersioning( - this IContainerBuilder builder, - IEnumerable models, - IEnumerable routingConventions ) => - builder.AddService( Transient, sp => sp.GetRequiredService().SelectModel( sp ) ) - .AddService( Singleton, sp => NewEdmModelSelector( sp, models ) ) - .AddService( Singleton, sp => AddOrUpdate( routingConventions.ToList() ).AsEnumerable() ); + /// + /// Adds service API versioning to the specified container builder. + /// + /// The sequence of EDM models + /// to use for parsing OData paths. + /// The OData routing conventions to use for controller and action selection. + public IContainerBuilder AddApiVersioning( + IEnumerable models, + IEnumerable routingConventions ) => + builder.AddService( Transient, sp => sp.GetRequiredService().SelectModel( sp ) ) + .AddService( Singleton, sp => NewEdmModelSelector( sp, models ) ) + .AddService( Singleton, sp => AddOrUpdate( [.. routingConventions] ).AsEnumerable() ); + } [MethodImpl( MethodImplOptions.AggressiveInlining )] private static IEnumerable NewRoutingConventions( IServiceProvider serviceProvider, string routeName ) => @@ -50,7 +54,7 @@ private static IEnumerable NewRoutingConventions( IServ [MethodImpl( MethodImplOptions.AggressiveInlining )] private static IEdmModelSelector NewEdmModelSelector( IServiceProvider serviceProvider, IEnumerable models ) { - var options = serviceProvider.GetRequiredService().GetApiVersioningOptions(); + var options = serviceProvider.GetRequiredService().ApiVersioningOptions; return new EdmModelSelector( models, options.ApiVersionSelector ); } } \ No newline at end of file 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 ed7497b9..baf6ecf1 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 @@ -66,7 +66,7 @@ public EdmModelSelector( IEnumerable models, IApiVersionSelector apiV versions.Sort(); maxVersion = versions.Count == 0 ? ApiVersion.Default : versions[versions.Count - 1]; - ApiVersions = versions.ToArray(); + ApiVersions = [.. versions]; Models = collection; } @@ -113,11 +113,11 @@ public EdmModelSelector( IEnumerable models, IApiVersionSelector apiV return Models[maxVersion]; } - var version = request.GetRequestedApiVersion(); + var version = request.RequestedApiVersion; if ( version is null ) { - var model = new ApiVersionModel( ApiVersions, Enumerable.Empty() ); + var model = new ApiVersionModel( ApiVersions, [] ); if ( ( version = selector.SelectVersion( request, model ) ) is null ) { @@ -130,7 +130,7 @@ public EdmModelSelector( IEnumerable models, IApiVersionSelector apiV private static void AddVersionFromModel( IEdmModel model, IList versions, IDictionary collection ) { - if ( model.GetApiVersion() is not ApiVersion version ) + if ( model.ApiVersion is not ApiVersion version ) { var message = string.Format( CultureInfo.CurrentCulture, SR.MissingAnnotation, typeof( ApiVersionAnnotation ).Name ); throw new ArgumentException( message ); 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 f08a3c18..a051d77c 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 @@ -32,7 +32,7 @@ public partial class VersionedODataModelBuilder /// Gets the API versioning options associated with the builder. /// /// The configured API versioning options. - protected ApiVersioningOptions Options => Configuration.GetApiVersioningOptions(); + protected ApiVersioningOptions Options => Configuration.ApiVersioningOptions; /// /// Gets the API versions for all known OData routes. @@ -57,7 +57,7 @@ protected virtual IReadOnlyList GetApiVersions() { var controllerType = controllerTypes[i]; - if ( !controllerType.IsODataController() ) + if ( !controllerType.IsODataController ) { continue; } @@ -73,7 +73,7 @@ protected virtual IReadOnlyList GetApiVersions() foreach ( var action in actions ) { - var model = action.GetApiVersionMetadata().Map( ApiVersionMapping.Explicit ); + var model = action.ApiVersionMetadata.Map( ApiVersionMapping.Explicit ); var versions = model.SupportedApiVersions; if ( versions.Count > 0 && supported == null ) @@ -108,7 +108,7 @@ protected virtual IReadOnlyList GetApiVersions() if ( ( supported == null || supported.Count == 0 ) && ( deprecated == null || deprecated.Count == 0 ) ) { - ConfigureMetadataController( new[] { Options.DefaultApiVersion }, Enumerable.Empty() ); + ConfigureMetadataController( [Options.DefaultApiVersion], [] ); } else { @@ -121,18 +121,18 @@ protected virtual IReadOnlyList GetApiVersions() { if ( deprecated == null ) { - return Array.Empty(); + return []; } - return deprecated.ToArray(); + return [.. deprecated]; } else if ( deprecated == null ) { - return supported.ToArray(); + return [.. supported]; } supported.UnionWith( deprecated ); - return supported.ToArray(); + return [.. supported]; } /// diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/ODataPathTemplateHandlerExtensions.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/ODataPathTemplateHandlerExtensions.cs index cc3e93dd..a509a478 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/ODataPathTemplateHandlerExtensions.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/ODataPathTemplateHandlerExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNet.OData.Routing; using Microsoft.AspNet.OData.Routing.Template; @@ -7,21 +9,21 @@ namespace Microsoft.AspNet.OData.Routing; internal static class ODataPathTemplateHandlerExtensions { - internal static ODataPathTemplate? SafeParseTemplate( - this IODataPathTemplateHandler handler, - string pathTemplate, - IServiceProvider serviceProvider ) + extension( IODataPathTemplateHandler handler ) { - try - { - return handler.ParseTemplate( pathTemplate, serviceProvider ); - } - catch ( ODataException ) + internal ODataPathTemplate? SafeParseTemplate( string pathTemplate, IServiceProvider serviceProvider ) { - // this 'should' mean the controller does not map to the current edm model. there's no way to know this without - // forcing a developer to explicitly map it. while it could be a mistake, simply yield null. this results in the - // template being skipped and will ultimately result in a 4xx if requested, which is acceptable. - return default; + try + { + return handler.ParseTemplate( pathTemplate, serviceProvider ); + } + catch ( ODataException ) + { + // this 'should' mean the controller does not map to the current edm model. there's no way to know this without + // forcing a developer to explicitly map it. while it could be a mistake, simply yield null. this results in the + // template being skipped and will ultimately result in a 4xx if requested, which is acceptable. + return default; + } } } } \ No newline at end of file diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/VersionedAttributeRoutingConvention.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/VersionedAttributeRoutingConvention.cs index eb65aa81..9d03a321 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/VersionedAttributeRoutingConvention.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/VersionedAttributeRoutingConvention.cs @@ -58,7 +58,7 @@ public VersionedAttributeRoutingConvention( if ( pathTemplateHandler is IODataPathHandler pathHandler && pathHandler.UrlKeyDelimiter == null ) { - pathHandler.UrlKeyDelimiter = configuration.GetUrlKeyDelimiter(); + pathHandler.UrlKeyDelimiter = configuration.UrlKeyDelimiter; } } @@ -88,7 +88,7 @@ public virtual bool ShouldMapController( HttpControllerDescriptor controller, Ap throw new ArgumentNullException( nameof( controller ) ); } - var model = controller.GetApiVersionModel(); + var model = controller.ApiVersionModel; return model.IsApiVersionNeutral || model.DeclaredApiVersions.Contains( apiVersion ); } @@ -107,7 +107,7 @@ public virtual bool ShouldMapAction( HttpActionDescriptor action, ApiVersion? ap throw new ArgumentNullException( nameof( action ) ); } - return action.GetApiVersionMetadata().IsMappedTo( apiVersion ); + return action.ApiVersionMetadata.IsMappedTo( apiVersion ); } /// @@ -221,7 +221,7 @@ protected virtual ApiVersion SelectApiVersion( HttpRequestMessage request ) return version; } - var options = request.GetApiVersioningOptions(); + var options = request.ApiVersioningOptions; if ( !options.AssumeDefaultVersionWhenUnspecified ) { @@ -229,8 +229,8 @@ protected virtual ApiVersion SelectApiVersion( HttpRequestMessage request ) } var modelSelector = request.GetRequestContainer().GetRequiredService(); - var versionSelector = request.GetApiVersioningOptions().ApiVersionSelector; - var model = new ApiVersionModel( modelSelector.ApiVersions, Enumerable.Empty() ); + var versionSelector = request.ApiVersioningOptions.ApiVersionSelector; + var model = new ApiVersionModel( modelSelector.ApiVersions, [] ); return versionSelector.SelectVersion( request, model ); } @@ -327,7 +327,7 @@ private IReadOnlyDictionary BuildAttrib { foreach ( var controller in controllers[i].AsEnumerable() ) { - if ( !controller.ControllerType.IsODataController() || !ShouldMapController( controller, version ) ) + if ( !controller.ControllerType.IsODataController || !ShouldMapController( controller, version ) ) { continue; } diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/VersionedMetadataRoutingConvention.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/VersionedMetadataRoutingConvention.cs index 19c494bf..9e227357 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/VersionedMetadataRoutingConvention.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Routing/VersionedMetadataRoutingConvention.cs @@ -41,7 +41,7 @@ public class VersionedMetadataRoutingConvention : IODataRoutingConvention return null; } - var properties = request.ApiVersionProperties(); + var properties = request.ApiVersionProperties; // the service document and metadata endpoints are special, but they are not neutral. if the client doesn't // specify a version, they may not know to. assume a default version by policy, but it's always allowed. @@ -49,8 +49,8 @@ public class VersionedMetadataRoutingConvention : IODataRoutingConvention if ( properties.RawRequestedApiVersions.Count == 0 ) { var modelSelector = request.GetRequestContainer().GetRequiredService(); - var versionSelector = request.GetApiVersioningOptions().ApiVersionSelector; - var model = new ApiVersionModel( modelSelector.ApiVersions, Enumerable.Empty() ); + var versionSelector = request.ApiVersioningOptions.ApiVersionSelector; + var model = new ApiVersionModel( modelSelector.ApiVersions, [] ); properties.RequestedApiVersion = versionSelector.SelectVersion( request, model ); } diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/System.Net.Http/HttpRequestMessageExtensions.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/System.Net.Http/HttpRequestMessageExtensions.cs index 47a9f361..1d7389f3 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/System.Net.Http/HttpRequestMessageExtensions.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/System.Net.Http/HttpRequestMessageExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Net.Http; using Asp.Versioning; @@ -9,21 +11,24 @@ namespace System.Net.Http; internal static class HttpRequestMessageExtensions { - internal static ApiVersion? GetRequestedApiVersionOrReturnBadRequest( this HttpRequestMessage request ) + extension( HttpRequestMessage request ) { - var properties = request.ApiVersionProperties(); - - if ( properties.RawRequestedApiVersions.Count < 2 ) + internal ApiVersion? GetRequestedApiVersionOrReturnBadRequest() { - return properties.RequestedApiVersion; - } + var properties = request.ApiVersionProperties; - var error = new ODataError() - { - ErrorCode = ProblemDetailsDefaults.Ambiguous.Code, - Message = new AmbiguousApiVersionException().Message, - }; + if ( properties.RawRequestedApiVersions.Count < 2 ) + { + return properties.RequestedApiVersion; + } + + var error = new ODataError() + { + ErrorCode = ProblemDetailsDefaults.Ambiguous.Code, + Message = new AmbiguousApiVersionException().Message, + }; - throw new HttpResponseException( request.CreateResponse( BadRequest, error ) ); + throw new HttpResponseException( request.CreateResponse( BadRequest, error ) ); + } } } \ No newline at end of file diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/System.Web.Http/HttpConfigurationExtensions.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/System.Web.Http/HttpConfigurationExtensions.cs index eb88078d..c2edf7ad 100644 --- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/System.Web.Http/HttpConfigurationExtensions.cs +++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/System.Web.Http/HttpConfigurationExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning.OData; @@ -18,241 +20,224 @@ namespace System.Web.Http; /// public static class HttpConfigurationExtensions { - /// - /// Maps the specified OData route and the OData route attributes. - /// /// The server configuration. - /// The name of the route to map. - /// The prefix to add to the OData route's path template. - /// The model builer used to create - /// an EDM model per API version. - /// The added . - public static ODataRoute MapVersionedODataRoute( - this HttpConfiguration configuration, - string routeName, - string routePrefix, - VersionedODataModelBuilder modelBuilder ) + extension( HttpConfiguration configuration ) { - if ( modelBuilder == null ) + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The model builer used to create + /// an EDM model per API version. + /// The added . + public ODataRoute MapVersionedODataRoute( string routeName, string routePrefix, VersionedODataModelBuilder modelBuilder ) { - throw new ArgumentNullException( nameof( modelBuilder ) ); - } + if ( modelBuilder == null ) + { + throw new ArgumentNullException( nameof( modelBuilder ) ); + } - return configuration.MapVersionedODataRoute( routeName, routePrefix, modelBuilder.GetEdmModels( routePrefix ) ); - } + return configuration.MapVersionedODataRoute( routeName, routePrefix, modelBuilder.GetEdmModels( routePrefix ) ); + } - /// - /// Maps the specified OData route and the OData route attributes. - /// - /// The server configuration. - /// The name of the route to map. - /// The prefix to add to the OData route's path template. - /// The model builer used to create - /// an EDM model per API version. - /// The configuring action to add the services to the root container. - /// The added . - public static ODataRoute MapVersionedODataRoute( - this HttpConfiguration configuration, - string routeName, - string routePrefix, - VersionedODataModelBuilder modelBuilder, - Action configureAction ) - { - if ( modelBuilder == null ) + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The model builer used to create + /// an EDM model per API version. + /// The configuring action to add the services to the root container. + /// The added . + public ODataRoute MapVersionedODataRoute( + string routeName, + string routePrefix, + VersionedODataModelBuilder modelBuilder, + Action configureAction ) { - throw new ArgumentNullException( nameof( modelBuilder ) ); + if ( modelBuilder == null ) + { + throw new ArgumentNullException( nameof( modelBuilder ) ); + } + + return configuration.MapVersionedODataRoute( routeName, routePrefix, modelBuilder.GetEdmModels( routePrefix ), configureAction ); } - return configuration.MapVersionedODataRoute( routeName, routePrefix, modelBuilder.GetEdmModels( routePrefix ), configureAction ); - } + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The sequence of EDM models to use for parsing OData paths. + /// The configuring action to add the services to the root container. + /// The added . + public ODataRoute MapVersionedODataRoute( + string routeName, + string routePrefix, + IEnumerable models, + Action configureAction ) => + AddApiVersionConstraintIfNecessary( + configuration, + configuration.MapODataServiceRoute( + routeName, + routePrefix, + builder => + { + builder.AddApiVersioning( routeName, models ); + configureAction?.Invoke( builder ); + } ) ); - /// - /// Maps the specified OData route and the OData route attributes. - /// - /// The server configuration. - /// The name of the route to map. - /// The prefix to add to the OData route's path template. - /// The sequence of EDM models to use for parsing OData paths. - /// The configuring action to add the services to the root container. - /// The added . - public static ODataRoute MapVersionedODataRoute( - this HttpConfiguration configuration, - string routeName, - string routePrefix, - IEnumerable models, - Action configureAction ) => - AddApiVersionConstraintIfNecessary( - configuration, - configuration.MapODataServiceRoute( - routeName, - routePrefix, - builder => - { - builder.AddApiVersioning( routeName, models ); - configureAction?.Invoke( builder ); - } ) ); + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The sequence of EDM models to use for parsing OData paths. + /// The added . + public ODataRoute MapVersionedODataRoute( string routeName, string routePrefix, IEnumerable models ) => + AddApiVersionConstraintIfNecessary( + configuration, + configuration.MapODataServiceRoute( routeName, routePrefix, builder => builder.AddApiVersioning( routeName, models ) ) ); - /// - /// Maps the specified OData route and the OData route attributes. - /// - /// The server configuration. - /// The name of the route to map. - /// The prefix to add to the OData route's path template. - /// The sequence of EDM models to use for parsing OData paths. - /// The added . - public static ODataRoute MapVersionedODataRoute( - this HttpConfiguration configuration, - string routeName, - string routePrefix, - IEnumerable models ) => - AddApiVersionConstraintIfNecessary( - configuration, - configuration.MapODataServiceRoute( routeName, routePrefix, builder => builder.AddApiVersioning( routeName, models ) ) ); + /// + /// Maps the specified OData route and the OData route attributes. When the is + /// non-null, it will create a '$batch' endpoint to handle the batch requests. + /// + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The sequence of EDM models to use for parsing OData paths. + /// The . + /// The added . + public ODataRoute MapVersionedODataRoute( + string routeName, + string routePrefix, + IEnumerable models, + ODataBatchHandler batchHandler ) => + AddApiVersionConstraintIfNecessary( + configuration, + configuration.MapODataServiceRoute( + routeName, + routePrefix, + builder => builder.AddApiVersioning( routeName, models ) + .AddService( Singleton, sp => batchHandler ) ) ); - /// - /// Maps the specified OData route and the OData route attributes. When the is - /// non-null, it will create a '$batch' endpoint to handle the batch requests. - /// - /// The server configuration. - /// The name of the route to map. - /// The prefix to add to the OData route's path template. - /// The sequence of EDM models to use for parsing OData paths. - /// The . - /// The added . - public static ODataRoute MapVersionedODataRoute( - this HttpConfiguration configuration, - string routeName, - string routePrefix, - IEnumerable models, - ODataBatchHandler batchHandler ) => - AddApiVersionConstraintIfNecessary( - configuration, - configuration.MapODataServiceRoute( - routeName, - routePrefix, - builder => builder.AddApiVersioning( routeName, models ) - .AddService( Singleton, sp => batchHandler ) ) ); + /// + /// Maps the specified OData route and the OData route attributes. When the + /// is non-null, it will map it as the default handler for the route. + /// + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The sequence of EDM models to use for parsing OData paths. + /// The default for this route. + /// The added . + public ODataRoute MapVersionedODataRoute( + string routeName, + string routePrefix, + IEnumerable models, + HttpMessageHandler defaultHandler ) => + AddApiVersionConstraintIfNecessary( + configuration, + configuration.MapODataServiceRoute( + routeName, + routePrefix, + builder => builder.AddApiVersioning( routeName, models ) + .AddService( Singleton, sp => defaultHandler ) ) ); - /// - /// Maps the specified OData route and the OData route attributes. When the - /// is non-null, it will map it as the default handler for the route. - /// - /// The server configuration. - /// The name of the route to map. - /// The prefix to add to the OData route's path template. - /// The sequence of EDM models to use for parsing OData paths. - /// The default for this route. - /// The added . - public static ODataRoute MapVersionedODataRoute( - this HttpConfiguration configuration, - string routeName, - string routePrefix, - IEnumerable models, - HttpMessageHandler defaultHandler ) => - AddApiVersionConstraintIfNecessary( - configuration, - configuration.MapODataServiceRoute( - routeName, - routePrefix, - builder => builder.AddApiVersioning( routeName, models ) - .AddService( Singleton, sp => defaultHandler ) ) ); + /// + /// Maps the specified OData route. + /// + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The sequence of EDM models to use for parsing OData paths. + /// The to use for parsing the OData path. + /// The OData routing conventions to use for controller and action selection. + /// The added . + public ODataRoute MapVersionedODataRoute( + string routeName, + string routePrefix, + IEnumerable models, + IODataPathHandler pathHandler, + IEnumerable routingConventions ) => + AddApiVersionConstraintIfNecessary( + configuration, + configuration.MapODataServiceRoute( + routeName, + routePrefix, + builder => builder.AddApiVersioning( models, routingConventions ) + .AddService( Singleton, sp => pathHandler ) ) ); - /// - /// Maps the specified OData route. - /// - /// The server configuration. - /// The name of the route to map. - /// The prefix to add to the OData route's path template. - /// The sequence of EDM models to use for parsing OData paths. - /// The to use for parsing the OData path. - /// The OData routing conventions to use for controller and action selection. - /// The added . - public static ODataRoute MapVersionedODataRoute( - this HttpConfiguration configuration, - string routeName, - string routePrefix, - IEnumerable models, - IODataPathHandler pathHandler, - IEnumerable routingConventions ) => - AddApiVersionConstraintIfNecessary( - configuration, - configuration.MapODataServiceRoute( - routeName, - routePrefix, - builder => builder.AddApiVersioning( models, routingConventions ) - .AddService( Singleton, sp => pathHandler ) ) ); + /// + /// Maps the specified OData route. When the is non-null, it will + /// create a '$batch' endpoint to handle the batch requests. + /// + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The sequence of EDM models to use for parsing OData paths. + /// The to use for parsing the OData path. + /// The OData routing conventions to use for controller and action selection. + /// The . + /// The added . + public ODataRoute MapVersionedODataRoute( + string routeName, + string routePrefix, + IEnumerable models, + IODataPathHandler pathHandler, + IEnumerable routingConventions, + ODataBatchHandler batchHandler ) => + AddApiVersionConstraintIfNecessary( + configuration, + configuration.MapODataServiceRoute( + routeName, + routePrefix, + builder => + builder.AddApiVersioning( models, routingConventions ) + .AddService( Singleton, sp => pathHandler ) + .AddService( Singleton, sp => batchHandler ) ) ); - /// - /// Maps the specified OData route. When the is non-null, it will - /// create a '$batch' endpoint to handle the batch requests. - /// - /// The server configuration. - /// The name of the route to map. - /// The prefix to add to the OData route's path template. - /// The sequence of EDM models to use for parsing OData paths. - /// The to use for parsing the OData path. - /// The OData routing conventions to use for controller and action selection. - /// The . - /// The added . - public static ODataRoute MapVersionedODataRoute( - this HttpConfiguration configuration, - string routeName, - string routePrefix, - IEnumerable models, - IODataPathHandler pathHandler, - IEnumerable routingConventions, - ODataBatchHandler batchHandler ) => - AddApiVersionConstraintIfNecessary( - configuration, - configuration.MapODataServiceRoute( - routeName, - routePrefix, - builder => - builder.AddApiVersioning( models, routingConventions ) - .AddService( Singleton, sp => pathHandler ) - .AddService( Singleton, sp => batchHandler ) ) ); + /// + /// Maps the specified OData route. When the is non-null, it will map + /// it as the handler for the route. + /// + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The sequence of EDM models to use for parsing OData paths. + /// The to use for parsing the OData path. + /// The OData routing conventions to use for controller and action selection. + /// The default for this route. + /// The added . + public ODataRoute MapVersionedODataRoute( + string routeName, + string routePrefix, + IEnumerable models, + IODataPathHandler pathHandler, + IEnumerable routingConventions, + HttpMessageHandler defaultHandler ) => + AddApiVersionConstraintIfNecessary( + configuration, + configuration.MapODataServiceRoute( + routeName, + routePrefix, + builder => + builder.AddApiVersioning( models, routingConventions ) + .AddService( Singleton, sp => pathHandler ) + .AddService( Singleton, sp => defaultHandler ) ) ); - /// - /// Maps the specified OData route. When the is non-null, it will map - /// it as the handler for the route. - /// - /// The server configuration. - /// The name of the route to map. - /// The prefix to add to the OData route's path template. - /// The sequence of EDM models to use for parsing OData paths. - /// The to use for parsing the OData path. - /// The OData routing conventions to use for controller and action selection. - /// The default for this route. - /// The added . - public static ODataRoute MapVersionedODataRoute( - this HttpConfiguration configuration, - string routeName, - string routePrefix, - IEnumerable models, - IODataPathHandler pathHandler, - IEnumerable routingConventions, - HttpMessageHandler defaultHandler ) => - AddApiVersionConstraintIfNecessary( - configuration, - configuration.MapODataServiceRoute( - routeName, - routePrefix, - builder => - builder.AddApiVersioning( models, routingConventions ) - .AddService( Singleton, sp => pathHandler ) - .AddService( Singleton, sp => defaultHandler ) ) ); + internal ODataUrlKeyDelimiter? UrlKeyDelimiter + { + get + { + const string UrlKeyDelimiterKey = "Microsoft.AspNet.OData.UrlKeyDelimiterKey"; - internal static ODataUrlKeyDelimiter? GetUrlKeyDelimiter( this HttpConfiguration configuration ) - { - const string UrlKeyDelimiterKey = "Microsoft.AspNet.OData.UrlKeyDelimiterKey"; + if ( configuration.Properties.TryGetValue( UrlKeyDelimiterKey, out var value ) ) + { + return value as ODataUrlKeyDelimiter; + } - if ( configuration.Properties.TryGetValue( UrlKeyDelimiterKey, out var value ) ) - { - return value as ODataUrlKeyDelimiter; + configuration.Properties[UrlKeyDelimiterKey] = null; + return null; + } } - - configuration.Properties[UrlKeyDelimiterKey] = null; - return null; } private static ODataRoute AddApiVersionConstraintIfNecessary( HttpConfiguration configuration, ODataRoute route ) @@ -269,7 +254,7 @@ private static ODataRoute AddApiVersionConstraintIfNecessary( HttpConfiguration return route; } - var options = configuration.GetApiVersioningOptions(); + var options = configuration.ApiVersioningOptions; if ( route.Constraints.ContainsKey( options.RouteConstraintName ) ) { diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Asp.Versioning.WebApi.OData.ApiExplorer.Tests.csproj b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Asp.Versioning.WebApi.OData.ApiExplorer.Tests.csproj index 470d2cd4..117d989b 100644 --- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Asp.Versioning.WebApi.OData.ApiExplorer.Tests.csproj +++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Asp.Versioning.WebApi.OData.ApiExplorer.Tests.csproj @@ -1,14 +1,10 @@  - net452;net472 + net472 Asp.Versioning - - - - diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Conventions/ODataQueryOptionsConventionBuilderTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Conventions/ODataQueryOptionsConventionBuilderTest.cs index 9416c63e..3e247e27 100644 --- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Conventions/ODataQueryOptionsConventionBuilderTest.cs +++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Conventions/ODataQueryOptionsConventionBuilderTest.cs @@ -31,7 +31,7 @@ public void apply_should_apply_configured_conventions() builder.Add( convention.Object ); // act - builder.ApplyTo( new[] { description }, settings ); + builder.ApplyTo( [description], settings ); // assert convention.Verify( c => c.ApplyTo( description ), Times.Once() ); 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 72c1025f..e9c70b68 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 @@ -311,9 +311,10 @@ public void apply_to_should_use_default_query_settings() [Theory] [MemberData( nameof( EnableQueryAttributeData ) )] - public void apply_to_should_use_enable_query_attribute( ApiDescription description ) + public void apply_to_should_use_enable_query_attribute( Type controllerType ) { // arrange + var description = NewApiDescription( controllerType ); var validationSettings = new ODataValidationSettings() { AllowedQueryOptions = AllowedQueryOptions.None, @@ -329,8 +330,7 @@ public void apply_to_should_use_enable_query_attribute( ApiDescription descripti // assert description.ParameterDescriptions.Should().BeEquivalentTo( - new[] - { + [ new { Name = "$select", @@ -373,7 +373,7 @@ public void apply_to_should_use_enable_query_attribute( ApiDescription descripti DefaultValue = default( object ), }, }, - }, + ], options => options.ExcludingMissingMembers() ); } @@ -402,8 +402,7 @@ public void apply_to_should_use_model_bound_query_attributes() // assert description.ParameterDescriptions.Should().BeEquivalentTo( - new[] - { + [ new { Name = "$select", @@ -460,7 +459,7 @@ public void apply_to_should_use_model_bound_query_attributes() DefaultValue = (object) false, }, }, - }, + ], options => options.ExcludingMissingMembers() ); } @@ -505,13 +504,12 @@ public void apply_to_should_process_odataX2Dlike_api_description() .AllowOrderBy( "title", "published" ); // act - builder.ApplyTo( new[] { description }, settings ); + builder.ApplyTo( [description], settings ); // assert description.ParameterDescriptions.RemoveAt( 0 ); description.ParameterDescriptions.Should().BeEquivalentTo( - new[] - { + [ new { Name = "$select", @@ -551,18 +549,12 @@ public void apply_to_should_process_odataX2Dlike_api_description() DefaultValue = (object) false, }, }, - }, + ], options => options.ExcludingMissingMembers() ); } - public static IEnumerable EnableQueryAttributeData - { - get - { - yield return new object[] { NewApiDescription( typeof( SinglePartController ) ) }; - yield return new object[] { NewApiDescription( typeof( MultipartController ) ) }; - } - } + public static TheoryData EnableQueryAttributeData => + new( typeof( SinglePartController ), typeof( MultipartController ) ); private static ApiDescription NewApiDescription( string method = "GET", bool singleResult = default ) { diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/ControllerTypeCollection.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/ControllerTypeCollection.cs index bb8fabfb..730ba91a 100644 --- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/ControllerTypeCollection.cs +++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/ControllerTypeCollection.cs @@ -9,7 +9,7 @@ public class ControllerTypeCollection : Collection, IHttpControllerTypeRes { public ControllerTypeCollection() { } - public ControllerTypeCollection( params Type[] controllerTypes ) : base( controllerTypes.ToList() ) { } + public ControllerTypeCollection( params Type[] controllerTypes ) : base( [.. controllerTypes] ) { } public ICollection GetControllerTypes( IAssembliesResolver assembliesResolver ) => this; } \ No newline at end of file diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/ODataApiExplorerTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/ODataApiExplorerTest.cs index 34540a00..568a153e 100644 --- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/ODataApiExplorerTest.cs +++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/ODataApiExplorerTest.cs @@ -16,9 +16,10 @@ public class ODataApiExplorerTest { [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_descriptions_should_collate_expected_versions( HttpConfiguration configuration ) + public void api_descriptions_should_collate_expected_versions( TestConfigurations.EdmKind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var apiExplorer = new ODataApiExplorer( configuration ); // act @@ -34,9 +35,10 @@ public void api_descriptions_should_collate_expected_versions( HttpConfiguration [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_descriptions_should_group_versioned_controllers( HttpConfiguration configuration ) + public void api_descriptions_should_group_versioned_controllers( TestConfigurations.EdmKind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var assembliesResolver = configuration.Services.GetAssembliesResolver(); var controllerTypes = configuration.Services .GetHttpControllerTypeResolver() @@ -57,9 +59,10 @@ public void api_descriptions_should_group_versioned_controllers( HttpConfigurati [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_descriptions_should_flatten_versioned_controllers( HttpConfiguration configuration ) + public void api_descriptions_should_flatten_versioned_controllers( TestConfigurations.EdmKind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var assembliesResolver = configuration.Services.GetAssembliesResolver(); var controllerTypes = configuration.Services .GetHttpControllerTypeResolver() @@ -80,9 +83,10 @@ public void api_descriptions_should_flatten_versioned_controllers( HttpConfigura [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_descriptions_should_not_contain_metadata_controllers( HttpConfiguration configuration ) + public void api_descriptions_should_not_contain_metadata_controllers( TestConfigurations.EdmKind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var apiExplorer = new ODataApiExplorer( configuration ); // act @@ -129,9 +133,10 @@ public void api_descriptions_should_contain_metadata_controllers( ODataMetadataO [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_description_group_should_explore_v3_actions( HttpConfiguration configuration ) + public void api_description_group_should_explore_v3_actions( TestConfigurations.EdmKind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var apiVersion = new ApiVersion( 3, 0 ); var apiExplorer = new ODataApiExplorer( configuration ); var descriptionGroup = apiExplorer.ApiDescriptions[apiVersion]; @@ -142,8 +147,7 @@ public void api_description_group_should_explore_v3_actions( HttpConfiguration c // assert descriptions.Should().BeEquivalentTo( - new[] - { + [ new { ID = $"GET{relativePaths[0]}", @@ -172,7 +176,7 @@ public void api_description_group_should_explore_v3_actions( HttpConfiguration c RelativePath = relativePaths[3], Version = apiVersion, }, - }, + ], options => options.ExcludingMissingMembers() ); } @@ -190,8 +194,7 @@ public void api_description_group_should_explore_navigation_properties() // assert descriptions.Should().BeEquivalentTo( - new[] - { + [ new { HttpMethod = Get, version, RelativePath = "api/Products" }, new { HttpMethod = Get, version, RelativePath = "api/Suppliers" }, new { HttpMethod = Get, version, RelativePath = "api/Products/{key}" }, @@ -211,7 +214,7 @@ public void api_description_group_should_explore_navigation_properties() new { HttpMethod = Delete, version, RelativePath = "api/Suppliers/{key}" }, new { HttpMethod = Delete, version, RelativePath = "api/Products/{key}/supplier/$ref" }, new { HttpMethod = Delete, version, RelativePath = "api/Suppliers/{key}/Products/$ref?$id={$id}" }, - }, + ], options => options.ExcludingMissingMembers() ); } diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/TestConfigurations.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/TestConfigurations.cs index 5e705df8..b1b8d17e 100644 --- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/TestConfigurations.cs +++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Description/TestConfigurations.cs @@ -7,19 +7,38 @@ namespace Asp.Versioning.Description; using Asp.Versioning.OData; using Asp.Versioning.Simulators.Configuration; using Asp.Versioning.Simulators.Models; -using System.Collections; using System.Web.Http; using System.Web.Http.Dispatcher; +using static Asp.Versioning.Description.TestConfigurations; -public class TestConfigurations : IEnumerable +public class TestConfigurations : TheoryData { - public IEnumerator GetEnumerator() + public enum EdmKind { - yield return new object[] { NewOrdersConfiguration() }; - yield return new object[] { NewPeopleConfiguration() }; + /// + /// Indicates the Orders EDM. + /// + Orders, + + /// + /// Indicates the People EDM. + /// + People, } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public TestConfigurations() + { + Add( EdmKind.Orders ); + Add( EdmKind.People ); + } + + public static HttpConfiguration Get( EdmKind kind ) => + kind switch + { + EdmKind.Orders => NewOrdersConfiguration(), + EdmKind.People => NewPeopleConfiguration(), + _ => throw new ArgumentOutOfRangeException( nameof( kind ) ), + }; public static HttpConfiguration NewOrdersConfiguration() { 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 5d56a57a..9238d3bd 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http.Description; using Asp.Versioning; @@ -21,7 +23,7 @@ public void edm_model_should_be_retrieved_from_properties() var apiDescription = CreateApiDescription( model ); // act - var result = apiDescription.EdmModel(); + var result = apiDescription.EdmModel; // assert result.Should().BeSameAs( model ); @@ -36,7 +38,7 @@ public void entity_set_should_be_retrieved_from_properties() var apiDescription = CreateApiDescription( model ); // act - var result = apiDescription.EntitySet(); + var result = apiDescription.EntitySet; // assert result.Should().BeSameAs( entitySet ); @@ -51,7 +53,7 @@ public void entity_type_should_be_retrieved_from_properties() var apiDescription = CreateApiDescription( model ); // act - var result = apiDescription.EntityType(); + var result = apiDescription.EntityType; // assert result.Should().BeSameAs( entityType ); diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs index acc51442..6300b421 100644 --- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs +++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning.ApiExplorer; 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 30285b43..d0edd16f 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,7 +1,7 @@  - net452;net472 + 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 0f4db376..c1c13807 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 @@ -32,10 +32,10 @@ public async Task options_should_return_expected_headers() resolver.AddService( typeof( IPolicyManager ), - ( sp, t ) => new SunsetPolicyManager( sp.GetRequiredService().GetApiVersioningOptions() ) ); + ( sp, t ) => new SunsetPolicyManager( sp.GetRequiredService().ApiVersioningOptions ) ); resolver.AddService( typeof( IPolicyManager ), - ( sp, t ) => new DeprecationPolicyManager( sp.GetRequiredService().GetApiVersioningOptions() ) ); + ( sp, t ) => new DeprecationPolicyManager( sp.GetRequiredService().ApiVersioningOptions ) ); configuration.DependencyResolver = resolver; configuration.AddApiVersioning( options => @@ -61,7 +61,7 @@ public async Task options_should_return_expected_headers() using var client = new HttpClient( server ); // act - var response = await client.SendAsync( request ); + var response = await client.SendAsync( request, TestContext.Current.CancellationToken ); // assert response.EnsureSuccessStatusCode(); diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/OData/EdmModelSelectorTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/OData/EdmModelSelectorTest.cs index 49f71ad3..abbb6d3e 100644 --- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/OData/EdmModelSelectorTest.cs +++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/OData/EdmModelSelectorTest.cs @@ -15,7 +15,7 @@ public void new_edm_model_selector_should_use_model_annotations() var model = NewEdm( new ApiVersion( 2.0 ) ); // act - var selector = new EdmModelSelector( new[] { model }, Mock.Of() ); + var selector = new EdmModelSelector( [model], Mock.Of() ); // assert selector.ApiVersions.Single().Should().Be( new ApiVersion( 2.0 ) ); @@ -30,7 +30,7 @@ public void contains_should_have_expected_api_version( double version, bool expe var model = NewEdm( new ApiVersion( 2.0 ) ); // act - var selector = new EdmModelSelector( new[] { model }, Mock.Of() ); + var selector = new EdmModelSelector( [model], Mock.Of() ); // assert selector.Contains( new ApiVersion( version ) ).Should().Be( expected ); @@ -41,7 +41,7 @@ public void select_model_return_matching_edm() { // arrange var model = NewEdm( new ApiVersion( 2.0 ) ); - var selector = new EdmModelSelector( new[] { model }, Mock.Of() ); + var selector = new EdmModelSelector( [model], Mock.Of() ); // act var result = selector.SelectModel( new ApiVersion( 2.0 ) ); @@ -55,7 +55,7 @@ public void select_model_return_null_for_unmatched_api_version() { // arrange var model = NewEdm( new ApiVersion( 2.0 ) ); - var selector = new EdmModelSelector( new[] { model }, Mock.Of() ); + var selector = new EdmModelSelector( [model], Mock.Of() ); // act var result = selector.SelectModel( new ApiVersion( 1.0 ) ); @@ -69,7 +69,7 @@ public void select_model_should_return_newest_edm_without_request() { // arrange var model = NewEdm( new ApiVersion( 2.0 ) ); - var selector = new EdmModelSelector( new[] { model }, Mock.Of() ); + var selector = new EdmModelSelector( [model], Mock.Of() ); // act var result = selector.SelectModel( Mock.Of() ); @@ -85,7 +85,7 @@ public void select_model_should_return_edm_from_requested_api_version() var model = NewEdm( new ApiVersion( 2.0 ) ); var serviceProvider = NewServiceProvider( new ApiVersion( 2.0 ) ); var selector = new EdmModelSelector( - new[] { NewEdm( new ApiVersion( 1.0 ) ), model }, + [NewEdm( new ApiVersion( 1.0 ) ), model], Mock.Of() ); // act @@ -102,7 +102,7 @@ public void select_model_should_return_edm_from_selected_api_version() var model = NewEdm( new ApiVersion( 1.0 ) ); var serviceProvider = NewServiceProvider(); var selector = new EdmModelSelector( - new[] { NewEdm( new ApiVersion( 2.0 ) ), model }, + [NewEdm( new ApiVersion( 2.0 ) ), model], new LowestImplementedApiVersionSelector( new() ) ); // act @@ -115,7 +115,7 @@ public void select_model_should_return_edm_from_selected_api_version() private static IServiceProvider NewServiceProvider( ApiVersion apiVersion = default ) { var request = new HttpRequestMessage(); - var properties = request.ApiVersionProperties(); + var properties = request.ApiVersionProperties; properties.RequestedApiVersion = apiVersion; diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/OData/VersionedODataModelBuilderTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/OData/VersionedODataModelBuilderTest.cs index 31845519..9af633b2 100644 --- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/OData/VersionedODataModelBuilderTest.cs +++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/OData/VersionedODataModelBuilderTest.cs @@ -34,7 +34,7 @@ public void get_edm_models_should_return_expected_results() var model = builder.GetEdmModels().Single(); // assert - model.GetApiVersion().Should().Be( apiVersion ); + model.ApiVersion.Should().Be( apiVersion ); modelCreated.Verify( f => f( It.IsAny(), model ), Times.Once() ); } diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Routing/VersionedMetadataRoutingConventionTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Routing/VersionedMetadataRoutingConventionTest.cs index 8ef196ea..31b2faab 100644 --- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Routing/VersionedMetadataRoutingConventionTest.cs +++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Routing/VersionedMetadataRoutingConventionTest.cs @@ -69,31 +69,25 @@ private static void SetRequestContainer( HttpRequestMessage request ) var selector = new Mock(); var serviceProvider = new Mock(); - selector.SetupGet( s => s.ApiVersions ).Returns( new[] { ApiVersion.Default } ); + selector.SetupGet( s => s.ApiVersions ).Returns( [ApiVersion.Default] ); serviceProvider.Setup( sp => sp.GetService( typeof( IEdmModelSelector ) ) ).Returns( selector.Object ); request.Properties[RequestContainerKey] = serviceProvider.Object; } - public static IEnumerable SelectControllerData + public static TheoryData SelectControllerData => new() { - get - { - yield return new object[] { "", "VersionedMetadata" }; - yield return new object[] { "$metadata", "VersionedMetadata" }; - yield return new object[] { "Tests", null }; - yield return new object[] { "Tests/42", null }; - } - } + { "", "VersionedMetadata" }, + { "$metadata", "VersionedMetadata" }, + { "Tests", null }, + { "Tests/42", null }, + }; - public static IEnumerable SelectActionData + public static TheoryData SelectActionData => new() { - get - { - yield return new object[] { "", "GET", "GetServiceDocument" }; - yield return new object[] { "$metadata", "GET", "GetMetadata" }; - yield return new object[] { "$metadata", "OPTIONS", "GetOptions" }; - yield return new object[] { "Tests", "GET", null }; - yield return new object[] { "Tests/42", "GET", null }; - } - } + { "", "GET", "GetServiceDocument" }, + { "$metadata", "GET", "GetMetadata" }, + { "$metadata", "OPTIONS", "GetOptions" }, + { "Tests", "GET", null }, + { "Tests/42", "GET", null }, + }; } \ No newline at end of file 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 c2e86cbc..d569a84a 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning; @@ -91,7 +93,7 @@ private static IReadOnlyList GetRoutingConventions( Htt var serviceProvider = GetODataRootContainer( configuration, key ); var routingConventions = (IEnumerable) serviceProvider.GetService( typeof( IEnumerable ) ); - return routingConventions.ToArray(); + return [.. routingConventions]; } [ApiVersion( "1.0" )] 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 63aaa0ec..2ff6f419 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 @@ -16,7 +16,7 @@ public partial class ApiExplorerOptions /// Initializes a new instance of the class. /// /// The current configuration associated with the options. - public ApiExplorerOptions( HttpConfiguration configuration ) => options = new( configuration.GetApiVersioningOptions ); + public ApiExplorerOptions( HttpConfiguration configuration ) => options = new( () => configuration.ApiVersioningOptions ); /// /// Gets the default API version applied to services that do not have explicit versions. 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 e2403079..1ced5194 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 @@ -102,7 +102,7 @@ public IDocumentationProvider DocumentationProvider /// The configured sunset policy manager. protected IPolicyManager SunsetPolicyManager { - get => sunsetPolicyManager ??= Configuration.GetSunsetPolicyManager(); + get => sunsetPolicyManager ??= DependencyResolverExtensions.get_SunsetPolicyManager( Configuration ); set => sunsetPolicyManager = value; } @@ -112,7 +112,7 @@ protected IPolicyManager SunsetPolicyManager /// The configured deprecation policy manager. protected IPolicyManager DeprecationPolicyManager { - get => deprecationPolicyManager ??= Configuration.GetDeprecationPolicyManager(); + get => deprecationPolicyManager ??= DependencyResolverExtensions.get_DeprecationPolicyManager( Configuration ); set => deprecationPolicyManager = value; } @@ -167,7 +167,7 @@ protected virtual bool ShouldExploreAction( if ( ( setting == null || !setting.IgnoreApi ) && MatchRegexConstraint( route, RouteValueKeys.Action, actionRouteParameterValue ) ) { - return actionDescriptor.GetApiVersionMetadata().IsMappedTo( apiVersion ); + return actionDescriptor.ApiVersionMetadata.IsMappedTo( apiVersion ); } return false; @@ -247,11 +247,11 @@ protected virtual ApiDescriptionGroupCollection InitializeApiDescriptions() for ( var i = 0; i < routes.Length; i++ ) { var route = routes[i]; - var directRouteCandidates = route.GetDirectRouteCandidates(); + var directRouteCandidates = HttpRouteExtensions.get_DirectRouteCandidates( route ); var directRouteController = GetDirectRouteController( directRouteCandidates, apiVersion ); var apiDescriptionGroup = newApiDescriptions.GetOrAdd( apiVersion, GetGroupName ); var descriptionsFromRoute = ( directRouteController != null && directRouteCandidates != null ) ? - ExploreDirectRouteControllers( directRouteController, directRouteCandidates.Select( c => c.ActionDescriptor ).ToArray(), route, apiVersion ) : + ExploreDirectRouteControllers( directRouteController, [.. directRouteCandidates.Select( c => c.ActionDescriptor )], route, apiVersion ) : ExploreRouteControllers( controllerMappings, route, apiVersion ); apiDescriptionGroup.SunsetPolicy = sunsetPolicy; @@ -276,7 +276,7 @@ protected virtual ApiDescriptionGroupCollection InitializeApiDescriptions() } else { - var overrideImplicitlyMappedApiDescription = description.ActionDescriptor.GetApiVersionMetadata().MappingTo( apiVersion ) == Explicit; + var overrideImplicitlyMappedApiDescription = description.ActionDescriptor.ApiVersionMetadata.MappingTo( apiVersion ) == Explicit; if ( overrideImplicitlyMappedApiDescription ) { @@ -399,7 +399,7 @@ protected virtual bool TryExpandUriParameters( // model build parameter based on route constraint like "api/v{version:apiVersion}" AddPlaceholder( parameterValuesForRoute, parameterDescription.Name ); } - else if ( parameterType.CanConvertFromString() ) + else if ( TypeExtensions.get_CanConvertFromString( parameterType ) ) { // Simple type generates query string like "?name={name}" AddPlaceholder( parameterValuesForRoute, parameterDescription.Name ); @@ -408,7 +408,7 @@ protected virtual bool TryExpandUriParameters( { var parameterName = parameterDescription.ParameterDescriptor.ParameterName; var innerType = GetCollectionElementType( parameterType ); - var innerTypeProperties = innerType.GetBindableProperties().ToArray(); + var innerTypeProperties = innerType.BindableProperties.ToArray(); if ( innerTypeProperties.Any() ) { @@ -440,7 +440,7 @@ protected virtual bool TryExpandUriParameters( AddPlaceholder( parameterValuesForRoute, parameterName + "[1].key" ); AddPlaceholder( parameterValuesForRoute, parameterName + "[1].value" ); } - else if ( parameterDescription.CanConvertPropertiesFromString() ) + else if ( parameterDescription.CanConvertPropertiesFromString ) { if ( emitPrefixes ) { @@ -449,7 +449,7 @@ protected virtual bool TryExpandUriParameters( // Inserting the individual properties of the object in the query string as all the complex object can not be converted from string, // but all its individual properties can. - AddPlaceholderForProperties( parameterValuesForRoute, parameterDescription.GetBindableProperties(), prefix ); + AddPlaceholderForProperties( parameterValuesForRoute, parameterDescription.BindableProperties, prefix ); } break; @@ -525,7 +525,7 @@ private IEnumerable FlattenApiVersions( IDictionary g ); for ( var i = 0; i < model.DeclaredApiVersions.Count; i++ ) @@ -535,7 +535,7 @@ private IEnumerable FlattenApiVersions( IDictionary FlattenApiVersions( IDictionary FlattenApiVersions( IDictionary FlattenApiVersions( IDictionary apiDescriptions, ApiVersion apiVersion ) { - if ( controllerDescriptor.IsAttributeRouted() ) + if ( HttpControllerDescriptorExtensions.get_IsAttributeRouted( controllerDescriptor ) ) { return; } @@ -832,7 +832,8 @@ private void PopulateActionDescriptions( { foreach ( var actionDescriptor in actionDescriptors ) { - if ( ShouldExploreAction( actionVariableValue ?? string.Empty, actionDescriptor, route, apiVersion ) && !actionDescriptor.IsAttributeRouted() ) + if ( ShouldExploreAction( actionVariableValue ?? string.Empty, actionDescriptor, route, apiVersion ) && + !System.Web.Http.HttpActionDescriptorExtensions.get_IsAttributeRouted( actionDescriptor ) ) { PopulateActionDescriptions( actionDescriptor, route, localPath, apiDescriptions, apiVersion ); } @@ -860,22 +861,25 @@ private void PopulateActionDescriptions( var supportedRequestBodyFormatters = bodyParameter != null ? formatters.Where( f => f.CanReadType( bodyParameter.ParameterDescriptor.ParameterType ) ) : - Enumerable.Empty(); + []; var responseDescription = CreateResponseDescription( actionDescriptor ); var returnType = responseDescription.ResponseType ?? responseDescription.DeclaredType; var supportedResponseFormatters = ( returnType != null && returnType != typeof( void ) ) ? formatters.Where( f => f.CanWriteType( returnType ) ) : - Enumerable.Empty(); + []; supportedRequestBodyFormatters = GetInnerFormatters( supportedRequestBodyFormatters ); supportedResponseFormatters = GetInnerFormatters( supportedResponseFormatters ); var supportedMethods = GetHttpMethodsSupportedByAction( route, actionDescriptor ); - var metadata = actionDescriptor.GetApiVersionMetadata(); + var metadata = actionDescriptor.ApiVersionMetadata; var model = metadata.Map( Explicit ); - var deprecated = !model.IsApiVersionNeutral && model.DeprecatedApiVersions.Contains( apiVersion ); + var deprecationPolicy = DeprecationPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ); + var deprecated = model.IsApiVersionNeutral + ? deprecationPolicy != null && deprecationPolicy.IsEffective( DateTimeOffset.Now ) + : model.DeprecatedApiVersions.Contains( apiVersion ); for ( var i = 0; i < supportedMethods.Count; i++ ) { @@ -890,7 +894,7 @@ private void PopulateActionDescriptions( ApiVersion = apiVersion, IsDeprecated = deprecated, SunsetPolicy = SunsetPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ), - DeprecationPolicy = DeprecationPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ), + DeprecationPolicy = deprecationPolicy, }; foreach ( var supportedResponseFormatter in supportedResponseFormatters ) @@ -944,8 +948,8 @@ private static bool ShouldEmitPrefixes( ICollection par return parameterDescriptions.Count( parameter => parameter.Source == FromUri && parameter.ParameterDescriptor != null && - !parameter.ParameterDescriptor.ParameterType.CanConvertFromString() && - parameter.CanConvertPropertiesFromString() ) > 1; + !TypeExtensions.get_CanConvertFromString( parameter.ParameterDescriptor.ParameterType ) && + parameter.CanConvertPropertiesFromString ) > 1; } private static Type GetCollectionElementType( Type collectionType ) => @@ -982,7 +986,7 @@ private IList CreateParameterDescriptions( IParsedRoute parsedRoute, IDictionary routeDefaults ) { - IList parameterDescriptions = new List(); + IList parameterDescriptions = []; var actionBinding = GetActionBinding( actionDescriptor ); // try get parameter binding information if available @@ -1083,7 +1087,7 @@ private ApiParameterDescription CreateParameterDescriptionFromBinding( HttpParam { parameterDescription.Source = FromBody; } - else if ( parameterBinding.WillReadUri() ) + else if ( HttpParameterBindingExtensions.get_WillReadUri( parameterBinding ) ) { parameterDescription.Source = FromUri; } @@ -1100,11 +1104,11 @@ private static Collection RemoveInvalidApiDescriptions( for ( var i = 0; i < apiDescriptions.Count; i++ ) { var description = apiDescriptions[i]; - var apiDescriptionId = description.GetUniqueID(); + var apiDescriptionId = description.UniqueID; if ( filteredDescriptions.ContainsKey( apiDescriptionId ) ) { - if ( description.ActionDescriptor.GetApiVersionMetadata().MappingTo( apiVersion ) == Explicit ) + if ( description.ActionDescriptor.ApiVersionMetadata.MappingTo( apiVersion ) == Explicit ) { filteredDescriptions[apiDescriptionId] = description; } @@ -1115,7 +1119,7 @@ private static Collection RemoveInvalidApiDescriptions( } } - return new( filteredDescriptions.Values.ToList() ); + return new( [.. filteredDescriptions.Values] ); } private static bool MatchRegexConstraint( IHttpRoute route, string parameterName, string parameterValue ) 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 9622bbfd..d30a6e08 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.1.0 - 7.1.0.0 + 10.0.0 + 10.0.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/CollectionExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/CollectionExtensions.cs index 00a2cf40..9c35dcd5 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/CollectionExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/CollectionExtensions.cs @@ -1,23 +1,28 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Collections.Generic; internal static class CollectionExtensions { - internal static int IndexOf( this IEnumerable sequence, TItem item, IEqualityComparer comparer ) + extension( IEnumerable sequence ) { - var index = 0; - - foreach ( var element in sequence ) + internal int IndexOf( T item, IEqualityComparer comparer ) { - if ( comparer.Equals( element, item ) ) + var index = 0; + + foreach ( var element in sequence ) { - return index; + if ( comparer.Equals( element, item ) ) + { + return index; + } + + index++; } - index++; + return -1; } - - return -1; } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionComparer.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionComparer.cs index a238ae97..5e7fb4d4 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionComparer.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionComparer.cs @@ -99,13 +99,13 @@ public virtual bool Equals( ApiDescription x, ApiDescription y ) return Equals( x1, y1 ); } - id1 = x1.GetUniqueID(); + id1 = x1.UniqueID; id2 = y.ID; } else if ( y is VersionedApiDescription y1 ) { id1 = x.ID; - id2 = y1.GetUniqueID(); + id2 = y1.UniqueID; } else { 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 f92c6857..621e6764 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 @@ -51,7 +51,7 @@ public ApiVersionParameterDescriptionContext( ApiDescription apiDescription, Api /// Gets a value indicating whether the current API is version-neutral. /// /// True if the current API is version-neutral; otherwise, false. - protected bool IsApiVersionNeutral => ApiDescription.ActionDescriptor.GetApiVersionMetadata().IsApiVersionNeutral; + protected bool IsApiVersionNeutral => ApiDescription.ActionDescriptor.ApiVersionMetadata.IsApiVersionNeutral; /// /// Gets the options associated with the API explorer. @@ -254,7 +254,7 @@ private static bool FirstParameterIsOptional( } var mapping = ApiVersionMapping.Explicit | ApiVersionMapping.Implicit; - var model = apiDescription.ActionDescriptor.GetApiVersionMetadata().Map( mapping ); + var model = apiDescription.ActionDescriptor.ApiVersionMetadata.Map( mapping ); var defaultApiVersion = options.ApiVersionSelector.SelectVersion( model ); return apiVersion == defaultApiVersion; diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/StringExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/StringExtensions.cs index 67cdee88..b23b4ae2 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/StringExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/StringExtensions.cs @@ -1,49 +1,54 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System; using System.Text; internal static class StringExtensions { - // REF: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs#L890 - internal static string Replace( this string @string, string oldValue, string? newValue, StringComparison comparisonType ) + extension( string @string ) { - if ( string.IsNullOrEmpty( oldValue ) ) - { - throw new ArgumentNullException( nameof( oldValue ) ); - } - - var length = @string.Length; - - if ( length == 0 ) - { - return @string; - } - - var result = new StringBuilder( length ); - var start = 0; - var matchLength = oldValue.Length; - var index = @string.IndexOf( oldValue, start, comparisonType ); - - while ( index >= 0 ) - { - result.Append( @string.Substring( start, index - start ) ); - result.Append( newValue ); - start = index + matchLength; - index = @string.IndexOf( oldValue, start, comparisonType ); - } - - if ( result.Length == 0 ) + // REF: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs#L890 + internal string Replace( string oldValue, string? newValue, StringComparison comparisonType ) { - return @string; + if ( string.IsNullOrEmpty( oldValue ) ) + { + throw new ArgumentNullException( nameof( oldValue ) ); + } + + var length = @string.Length; + + if ( length == 0 ) + { + return @string; + } + + var result = new StringBuilder( length ); + var start = 0; + var matchLength = oldValue.Length; + var index = @string.IndexOf( oldValue, start, comparisonType ); + + while ( index >= 0 ) + { + result.Append( @string.Substring( start, index - start ) ); + result.Append( newValue ); + start = index + matchLength; + index = @string.IndexOf( oldValue, start, comparisonType ); + } + + if ( result.Length == 0 ) + { + return @string; + } + + if ( start < length ) + { + result.Append( @string.Substring( start ) ); + } + + return result.ToString(); } - - if ( start < length ) - { - result.Append( @string.Substring( start ) ); - } - - return result.ToString(); } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Controllers/HttpActionDescriptorExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Controllers/HttpActionDescriptorExtensions.cs index c60cb315..62df5aeb 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Controllers/HttpActionDescriptorExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Controllers/HttpActionDescriptorExtensions.cs @@ -1,5 +1,8 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0079 +#pragma warning disable IDE0130 + namespace System.Web.Http.Controllers; using System.Net.Http; @@ -7,16 +10,19 @@ namespace System.Web.Http.Controllers; internal static class HttpActionDescriptorExtensions { - internal static IList GetHttpMethods( this HttpActionDescriptor actionDescriptor, IHttpRoute route ) + extension( HttpActionDescriptor actionDescriptor ) { - IList actionHttpMethods = actionDescriptor.SupportedHttpMethods; - var httpMethodConstraint = route.Constraints.Values.OfType().FirstOrDefault(); - - if ( httpMethodConstraint == null ) + internal IList GetHttpMethods( IHttpRoute route ) { - return actionHttpMethods; - } + IList actionHttpMethods = actionDescriptor.SupportedHttpMethods; + var httpMethodConstraint = route.Constraints.Values.OfType().FirstOrDefault(); - return httpMethodConstraint.AllowedMethods.Intersect( actionHttpMethods ).ToList(); + if ( httpMethodConstraint == null ) + { + return actionHttpMethods; + } + + return [.. httpMethodConstraint.AllowedMethods.Intersect( actionHttpMethods )]; + } } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs index 09d8b5fe..ad86f105 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http.Description; using Asp.Versioning; @@ -12,175 +14,188 @@ namespace System.Web.Http.Description; /// public static class ApiDescriptionExtensions { - /// - /// Gets the API version associated with the API description. - /// /// The API description to get the API version for. - /// The associated API version or null. - /// This method always returns null unless the API description - /// is of type . - public static ApiVersion? GetApiVersion( this ApiDescription apiDescription ) - { - if ( apiDescription is VersionedApiDescription versionedApiDescription ) - { - return versionedApiDescription.ApiVersion; - } - - return default; - } - - /// - /// Gets a value indicating whether the associated API description is deprecated. - /// - /// The API description to evaluate. - /// True if the API description is deprecated; otherwise, false. - /// This method always returns false unless the API description - /// is of type . - public static bool IsDeprecated( this ApiDescription apiDescription ) - { - if ( apiDescription is VersionedApiDescription versionedApiDescription ) - { - return versionedApiDescription.IsDeprecated; - } - - return false; - } - - /// - /// Gets the group name associated with the API description. - /// - /// The API description to get the group name for. - /// The associated group name or null. - /// This method always returns null unless the API description - /// is of type . - public static string? GetGroupName( this ApiDescription apiDescription ) - { - if ( apiDescription is VersionedApiDescription versionedApiDescription ) - { - return versionedApiDescription.GroupName; - } - - return default; - } - - /// - /// Gets the unique API description identifier. - /// - /// The API description to get the unique identifier for. - /// The unique identifier of the API description. - /// If the API description is of type - /// the return value will be in the format of "{}-{}"; - /// otherwise, the return value will be "{}". - public static string GetUniqueID( this ApiDescription apiDescription ) - { - if ( apiDescription == null ) - { - throw new ArgumentNullException( nameof( apiDescription ) ); - } - - if ( apiDescription is VersionedApiDescription versionedApiDescription ) - { - return $"{versionedApiDescription.ID}-{versionedApiDescription.ApiVersion}"; - } - - return apiDescription.ID; - } - - /// - /// Attempts to update the relate path of the specified API description and remove the corresponding parameter according to the specified options. - /// - /// The API description to attempt to update. - /// The current API Explorer options. - /// True if the API description was updated; otherwise, false. - public static bool TryUpdateRelativePathAndRemoveApiVersionParameter( this ApiDescription apiDescription, ApiExplorerOptions options ) + extension( ApiDescription apiDescription ) { - if ( apiDescription == null ) - { - throw new ArgumentNullException( nameof( apiDescription ) ); - } - - if ( options == null ) - { - throw new ArgumentNullException( nameof( options ) ); + /// + /// Gets the API version associated with the API description. + /// + /// The associated API version or null. + /// This method always returns null unless the API description + /// is of type . + public ApiVersion? ApiVersion + { + get + { + if ( apiDescription is VersionedApiDescription versionedApiDescription ) + { + return versionedApiDescription.ApiVersion; + } + + return default; + } + } + + /// + /// Gets a value indicating whether the associated API description is deprecated. + /// + /// True if the API description is deprecated; otherwise, false. + /// This method always returns false unless the API description + /// is of type . + public bool IsDeprecated + { + get + { + if ( apiDescription is VersionedApiDescription versionedApiDescription ) + { + return versionedApiDescription.IsDeprecated; + } + + return false; + } + } + + /// + /// Gets the group name associated with the API description. + /// + /// The associated group name or null. + /// This method always returns null unless the API description + /// is of type . + public string? GroupName + { + get + { + if ( apiDescription is VersionedApiDescription versionedApiDescription ) + { + return versionedApiDescription.GroupName; + } + + return default; + } + } + + /// + /// Gets the unique API description identifier. + /// + /// The unique identifier of the API description. + /// If the API description is of type + /// the return value will be in the format of "{}-{}"; + /// otherwise, the return value will be "{}". + public string UniqueID + { + get + { + if ( apiDescription == null ) + { + throw new ArgumentNullException( nameof( apiDescription ) ); + } + + if ( apiDescription is VersionedApiDescription versionedApiDescription ) + { + return $"{versionedApiDescription.ID}-{versionedApiDescription.ApiVersion}"; + } + + return apiDescription.ID; + } + } + + /// + /// Attempts to update the relate path of the specified API description and remove the corresponding parameter according to the specified options. + /// + /// The current API Explorer options. + /// True if the API description was updated; otherwise, false. + public bool TryUpdateRelativePathAndRemoveApiVersionParameter( ApiExplorerOptions options ) + { + if ( apiDescription == null ) + { + throw new ArgumentNullException( nameof( apiDescription ) ); + } + + if ( options == null ) + { + throw new ArgumentNullException( nameof( options ) ); + } + + if ( !options.SubstituteApiVersionInUrl || apiDescription is not VersionedApiDescription versionedApiDescription ) + { + return false; + } + + var relativePath = apiDescription.RelativePath; + + if ( string.IsNullOrEmpty( relativePath ) ) + { + return false; + } + + var parameters = versionedApiDescription.ParameterDescriptions; + var parameter = parameters.FirstOrDefault( p => p.ParameterDescriptor is ApiVersionParameterDescriptor pd && pd.FromPath ); + + if ( parameter == null ) + { + return false; + } + + var token = '{' + parameter.ParameterDescriptor.ParameterName + '}'; + var value = versionedApiDescription.ApiVersion.ToString( options.SubstitutionFormat, InvariantCulture ); + var newRelativePath = relativePath.Replace( token, value ); + + if ( relativePath == newRelativePath ) + { + return false; + } + + apiDescription.RelativePath = newRelativePath; + parameters.Remove( parameter ); + return true; } - - if ( !options.SubstituteApiVersionInUrl || apiDescription is not VersionedApiDescription versionedApiDescription ) - { - return false; - } - - var relativePath = apiDescription.RelativePath; - - if ( string.IsNullOrEmpty( relativePath ) ) - { - return false; - } - - var parameters = versionedApiDescription.ParameterDescriptions; - var parameter = parameters.FirstOrDefault( p => p.ParameterDescriptor is ApiVersionParameterDescriptor pd && pd.FromPath ); - - if ( parameter == null ) - { - return false; - } - - var token = '{' + parameter.ParameterDescriptor.ParameterName + '}'; - var value = versionedApiDescription.ApiVersion.ToString( options.SubstitutionFormat, InvariantCulture ); - var newRelativePath = relativePath.Replace( token, value ); - - if ( relativePath == newRelativePath ) - { - return false; - } - - apiDescription.RelativePath = newRelativePath; - parameters.Remove( parameter ); - return true; } - /// - /// Gets a property of the specified type from the API description. - /// - /// The type of property to retrieve. /// The API description to get the property from. - /// The value of the property, if present; otherwise, the default value of . - public static T GetProperty( this VersionedApiDescription apiDescription ) + extension( VersionedApiDescription apiDescription ) { - if ( apiDescription == null ) - { - throw new ArgumentNullException( nameof( apiDescription ) ); - } - - if ( apiDescription.Properties.TryGetValue( typeof( T ), out var value ) ) - { - return (T) value; - } - - return default!; - } - - /// - /// Sets a property of the specified type on the API description. - /// - /// The type of property to set. - /// The API description to set the property on. - /// The value to add or update. - public static void SetProperty( this VersionedApiDescription apiDescription, T value ) - { - if ( apiDescription == null ) - { - throw new ArgumentNullException( nameof( apiDescription ) ); - } - - var key = typeof( T ); - - if ( !key.IsValueType && value is null ) - { - apiDescription.Properties.Remove( key ); - } - else - { - apiDescription.Properties[key] = value!; + /// + /// Gets a property of the specified type from the API description. + /// + /// The type of property to retrieve. + /// The value of the property, if present; otherwise, the default value of . + public T GetProperty() + { + if ( apiDescription == null ) + { + throw new ArgumentNullException( nameof( apiDescription ) ); + } + + if ( apiDescription.Properties.TryGetValue( typeof( T ), out var value ) ) + { + return (T) value; + } + + return default!; + } + + /// + /// Sets a property of the specified type on the API description. + /// + /// The type of property to set. + /// The value to add or update. + public void SetProperty( T value ) + { + if ( apiDescription == null ) + { + throw new ArgumentNullException( nameof( apiDescription ) ); + } + + var key = typeof( T ); + + if ( !key.IsValueType && value is null ) + { + apiDescription.Properties.Remove( key ); + } + else + { + apiDescription.Properties[key] = value!; + } } } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Description/ApiParameterDescriptionExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Description/ApiParameterDescriptionExtensions.cs index e13472ad..c7438aa7 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Description/ApiParameterDescriptionExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/Description/ApiParameterDescriptionExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http.Description; using Asp.Versioning; @@ -7,9 +9,12 @@ namespace System.Web.Http.Description; internal static class ApiParameterDescriptionExtensions { - internal static IEnumerable GetBindableProperties( this ApiParameterDescription description ) => - description.ParameterDescriptor.ParameterType.GetBindableProperties(); + extension( ApiParameterDescription description ) + { + internal IEnumerable BindableProperties => + description.ParameterDescriptor.ParameterType.BindableProperties; - internal static bool CanConvertPropertiesFromString( this ApiParameterDescription description ) => - description.GetBindableProperties().All( p => p.PropertyType.CanConvertFromString() ); + internal bool CanConvertPropertiesFromString => + description.BindableProperties.All( p => TypeExtensions.get_CanConvertFromString( p.PropertyType ) ); + } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs index 62a5a619..ec898fd9 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning.ApiExplorer; @@ -10,42 +12,43 @@ namespace System.Web.Http; /// public static class HttpConfigurationExtensions { - /// - /// Adds or replaces the configured API explorer with an implementation that supports API versioning. - /// - /// The configuration used to add the API explorer. - /// The newly registered versioned API explorer. - /// This method always replaces the with a new instance of . - public static VersionedApiExplorer AddVersionedApiExplorer( this HttpConfiguration configuration ) => - configuration.AddVersionedApiExplorer( static _ => { } ); - - /// - /// Adds or replaces the configured API explorer with an implementation that supports API versioning. - /// /// The configuration used to add the API explorer. - /// An action used to configure the provided options. - /// The newly registered versioned API explorer. - /// This method always replaces the with a new instance of . - public static VersionedApiExplorer AddVersionedApiExplorer( this HttpConfiguration configuration, Action setupAction ) + extension( HttpConfiguration configuration ) { - if ( configuration == null ) + /// + /// Adds or replaces the configured API explorer with an implementation that supports API versioning. + /// + /// The newly registered versioned API explorer. + /// This method always replaces the with a new instance of . + public VersionedApiExplorer AddVersionedApiExplorer() => configuration.AddVersionedApiExplorer( static _ => { } ); + + /// + /// Adds or replaces the configured API explorer with an implementation that supports API versioning. + /// + /// An action used to configure the provided options. + /// The newly registered versioned API explorer. + /// This method always replaces the with a new instance of . + public VersionedApiExplorer AddVersionedApiExplorer( Action setupAction ) { - throw new ArgumentNullException( nameof( configuration ) ); - } + if ( configuration == null ) + { + throw new ArgumentNullException( nameof( configuration ) ); + } - if ( setupAction == null ) - { - throw new ArgumentNullException( nameof( setupAction ) ); - } + if ( setupAction == null ) + { + throw new ArgumentNullException( nameof( setupAction ) ); + } - var options = new ApiExplorerOptions( configuration ); + var options = new ApiExplorerOptions( configuration ); - setupAction( options ); + setupAction( options ); - var apiExplorer = new VersionedApiExplorer( configuration, options ); + var apiExplorer = new VersionedApiExplorer( configuration, options ); - configuration.Services.Replace( typeof( IApiExplorer ), apiExplorer ); + configuration.Services.Replace( typeof( IApiExplorer ), apiExplorer ); - return apiExplorer; + return apiExplorer; + } } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/MediaTypeFormatterAdapterFactory.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/MediaTypeFormatterAdapterFactory.cs index 1d71cc4b..d9b7443a 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/MediaTypeFormatterAdapterFactory.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/MediaTypeFormatterAdapterFactory.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning; @@ -123,7 +125,7 @@ private static class SupportedMediaTypesInitializer internal static void Initialize( MediaTypeFormatter instance ) { var list = new List(); - var collection = newCollection.Invoke( new object[] { list } ); + var collection = newCollection.Invoke( [list] ); // the _supportedMediaTypes field is "readonly", which is why we must use Reflection instead of compiling an expression; // interestingly, the Reflection API lets us break rules that expression compilation does not @@ -131,7 +133,7 @@ internal static void Initialize( MediaTypeFormatter instance ) // since the value for the SupportedMediaTypes property comes from the backing field, we must do this here, even // though it's possible to set this property with a compiled expression - property.SetMethod.Invoke( instance, new object[] { collection } ); + property.SetMethod.Invoke( instance, [collection] ); } } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/MediaTypeFormatterExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/MediaTypeFormatterExtensions.cs index 0d25bb7f..ea751f0d 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/MediaTypeFormatterExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/System.Web.Http/MediaTypeFormatterExtensions.cs @@ -1,11 +1,15 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using System.Net.Http.Formatting; internal static class MediaTypeFormatterExtensions { - internal static MediaTypeFormatter Clone( this MediaTypeFormatter formatter ) => - MediaTypeFormatterAdapterFactory.GetOrCreateCloneFunction( formatter )( formatter ); + extension( MediaTypeFormatter formatter ) + { + internal MediaTypeFormatter Clone() => MediaTypeFormatterAdapterFactory.GetOrCreateCloneFunction( formatter )( formatter ); + } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/TypeExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/TypeExtensions.cs index c2247fbd..69811b00 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/TypeExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/TypeExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System; using System.Reflection; @@ -7,35 +9,38 @@ namespace System; internal static class TypeExtensions { - internal static Type[]? GetTypeArgumentsIfMatch( this Type closedType, Type matchingOpenType ) + extension( Type type ) { - if ( !closedType.IsGenericType ) + internal Type[]? GetTypeArgumentsIfMatch( Type matchingOpenType ) { - return null; - } + if ( !type.IsGenericType ) + { + return null; + } - var openType = closedType.GetGenericTypeDefinition(); + var openType = type.GetGenericTypeDefinition(); - return ( matchingOpenType == openType ) ? closedType.GetGenericArguments() : null; - } + return ( matchingOpenType == openType ) ? type.GetGenericArguments() : null; + } - internal static IEnumerable GetBindableProperties( this Type type ) => - type.GetProperties( Instance | Public ).Where( p => p.GetGetMethod() != null && p.GetSetMethod() != null ); + internal IEnumerable BindableProperties => + type.GetProperties( Instance | Public ).Where( p => p.GetGetMethod() != null && p.GetSetMethod() != null ); - internal static Type[]? GetGenericBinderTypeArgs( this Type supportedInterfaceType, Type modelType ) - { - if ( !modelType.IsGenericType || modelType.IsGenericTypeDefinition ) + internal Type[]? GetGenericBinderTypeArgs( Type modelType ) { - return null; - } + if ( !modelType.IsGenericType || modelType.IsGenericTypeDefinition ) + { + return null; + } - var modelTypeArguments = modelType.GetGenericArguments(); + var modelTypeArguments = modelType.GetGenericArguments(); - if ( modelTypeArguments.Length != supportedInterfaceType.GetGenericArguments().Length ) - { - return null; - } + if ( modelTypeArguments.Length != type.GetGenericArguments().Length ) + { + return null; + } - return modelTypeArguments; + return modelTypeArguments; + } } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApiVersionRequestProperties.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApiVersionRequestProperties.cs index 0383fb9c..6cc3c975 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApiVersionRequestProperties.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApiVersionRequestProperties.cs @@ -37,8 +37,8 @@ public class ApiVersionRequestProperties /// The unparsed API version values for the current request. public IReadOnlyList RawRequestedApiVersions { - get => rawApiVersions ??= request.GetApiVersioningOptions().ApiVersionReader.Read( request ); - set => rawApiVersions = value.ToArray(); + get => rawApiVersions ??= request.ApiVersioningOptions.ApiVersionReader.Read( request ); + set => rawApiVersions = [.. value]; } /// @@ -56,7 +56,7 @@ public string? RawRequestedApiVersion 0 => default, 1 => values[0], _ => throw new AmbiguousApiVersionException( - string.Format( CultureInfo.CurrentCulture, CommonSR.MultipleDifferentApiVersionsRequested, string.Join( ", ", values ) ), + string.Format( CultureInfo.CurrentCulture, Format.MultipleDifferentApiVersionsRequested, string.Join( ", ", values ) ), values ), }; } @@ -88,7 +88,7 @@ public ApiVersion? RequestedApiVersion return apiVersion; } - var parser = request.GetConfiguration().GetApiVersionParser(); + var parser = request.GetConfiguration().ApiVersionParser; try { diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApplyContentTypeVersionActionFilter.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApplyContentTypeVersionActionFilter.cs index 609d4854..f3268296 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApplyContentTypeVersionActionFilter.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApplyContentTypeVersionActionFilter.cs @@ -32,7 +32,7 @@ public override void OnActionExecuted( HttpActionExecutedContext actionExecutedC return; } - var apiVersion = actionExecutedContext.Request.GetRequestedApiVersion(); + var apiVersion = actionExecutedContext.Request.RequestedApiVersion; if ( apiVersion == null ) { 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 e226b019..0ce3f7f9 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.1.0 - 7.1.0.0 + 10.0.0 + 10.0.0.0 net45;net472 ASP.NET Web API Versioning A service API versioning library for Microsoft ASP.NET Web API. @@ -33,7 +33,7 @@ - + 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 4ae17299..4fbb4353 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ActionSelectorCacheItem.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ActionSelectorCacheItem.cs @@ -48,13 +48,13 @@ internal ActionSelectorCacheItem( HttpControllerDescriptor controllerDescriptor combinedCandidateActions[i] = new( actionDescriptor ); actionParameterNames.Add( actionDescriptor, - actionDescriptor.ActionBinding + [.. actionDescriptor.ActionBinding .ParameterBindings .Where( binding => !binding.Descriptor.IsOptional && - binding.Descriptor.ParameterType.CanConvertFromString() && - binding.WillReadUri() ) + binding.Descriptor.ParameterType.CanConvertFromString && + binding.WillReadUri ) .Select( binding => binding.Descriptor.Prefix ?? - binding.Descriptor.ParameterName ).ToArray() ); + binding.Descriptor.ParameterName )] ); } combinedActionNameMapping = @@ -74,7 +74,7 @@ private void InitializeStandardActions() var selectionCache = new StandardActionSelectionCache(); - if ( controllerDescriptor.IsAttributeRouted() ) + if ( controllerDescriptor.IsAttributeRouted ) { selectionCache.StandardCandidateActions = []; } @@ -88,7 +88,7 @@ private void InitializeStandardActions() var action = (ReflectedHttpActionDescriptor) candidate.ActionDescriptor; if ( action.MethodInfo.DeclaringType != controllerDescriptor.ControllerType || - !candidate.ActionDescriptor.IsAttributeRouted() ) + !candidate.ActionDescriptor.IsAttributeRouted ) { standardCandidateActions.Add( candidate ); } @@ -166,7 +166,7 @@ private ActionSelectionResult FindAction( } var ambiguityList = CreateAmbiguousMatchList( selectedCandidates ); - var message = string.Format( CultureInfo.CurrentCulture, SR.ApiControllerActionSelector_AmbiguousMatch, ambiguityList ); + var message = string.Format( CultureInfo.CurrentCulture, BackportSR.ApiControllerActionSelector_AmbiguousMatch, ambiguityList ); return new( new InvalidOperationException( message ) ); } @@ -226,20 +226,20 @@ private IReadOnlyList FindMatchingActions( var precedenceCandidates = RunPrecedenceFilter( orderCandidates ); var selectedCandidates = FindActionMatchMostRouteAndQueryParameters( precedenceCandidates ); - return selectedCandidates.Select( c => new CandidateHttpActionDescriptor( c ) ).ToArray(); + return [.. selectedCandidates.Select( c => new CandidateHttpActionDescriptor( c ) )]; } private IEnumerable GetAllowedMethods( HttpControllerContext controllerContext ) { var request = controllerContext.Request; - var apiModel = controllerContext.ControllerDescriptor.GetApiVersionModel(); - var version = apiModel.IsApiVersionNeutral ? ApiVersion.Neutral : request.ApiVersionProperties().RequestedApiVersion!; + var apiModel = controllerContext.ControllerDescriptor.ApiVersionModel; + var version = apiModel.IsApiVersionNeutral ? ApiVersion.Neutral : request.ApiVersionProperties.RequestedApiVersion!; var httpMethods = new HashSet(); for ( var i = 0; i < combinedCandidateActions.Length; i++ ) { var actionDescriptor = combinedCandidateActions[i].ActionDescriptor; - var endpointModel = actionDescriptor.GetApiVersionMetadata().Map( Explicit ); + var endpointModel = actionDescriptor.ApiVersionMetadata.Map( Explicit ); if ( endpointModel.IsApiVersionNeutral || endpointModel.ImplementedApiVersions.Contains( version ) ) { @@ -259,7 +259,7 @@ private HttpResponseMessage CreateSelectionError( HttpControllerContext controll return CreateActionNotFoundResponse( controllerContext ); } - var apiModel = controllerContext.ControllerDescriptor.GetApiVersionModel(); + var apiModel = controllerContext.ControllerDescriptor.ApiVersionModel; var httpMethods = GetAllowedMethods( controllerContext ); var exceptionFactory = new HttpResponseExceptionFactory( controllerContext.Request, apiModel ); @@ -269,8 +269,8 @@ private HttpResponseMessage CreateSelectionError( HttpControllerContext controll private HttpResponseMessage CreateActionNotFoundResponse( HttpControllerContext controllerContext ) { var culture = CultureInfo.CurrentCulture; - var message = string.Format( culture, SR.ResourceNotFound, controllerContext.Request.RequestUri ); - var messageDetail = string.Format( culture, SR.ApiControllerActionSelector_ActionNotFound, controllerDescriptor.ControllerName ); + var message = string.Format( culture, BackportSR.ResourceNotFound, controllerContext.Request.RequestUri ); + var messageDetail = string.Format( culture, BackportSR.ApiControllerActionSelector_ActionNotFound, controllerDescriptor.ControllerName ); return controllerContext.Request.CreateErrorResponse( NotFound, message, messageDetail ); } @@ -293,7 +293,7 @@ private static List GetInitialCandidateWithParameterL foreach ( var subRouteData in subRoutes ) { var combinedParameterNames = GetCombinedParameterNames( queryNameValuePairs, subRouteData.Values ); - var candidates = subRouteData.Route.GetDirectRouteCandidates(); + var candidates = subRouteData.Route.DirectRouteCandidates; if ( candidates == null ) { @@ -337,7 +337,7 @@ private CandidateAction[] GetInitialCandidateList( HttpControllerContext control if ( actionsFoundByName.Length == 0 ) { - var apiModel = controllerContext.ControllerDescriptor.GetApiVersionModel(); + var apiModel = controllerContext.ControllerDescriptor.ApiVersionModel; var exceptionFactory = new HttpResponseExceptionFactory( controllerContext.Request, apiModel ); var httpMethods = GetAllowedMethods( controllerContext ); @@ -364,7 +364,7 @@ private CandidateAction[] GetInitialCandidateList( HttpControllerContext control } private static CandidateAction[] FilterIncompatibleMethods( HttpMethod incomingMethod, CandidateAction[] candidatesFoundByName ) => - candidatesFoundByName.Where( c => c.ActionDescriptor.SupportedHttpMethods.Contains( incomingMethod ) ).ToArray(); + [.. candidatesFoundByName.Where( c => c.ActionDescriptor.SupportedHttpMethods.Contains( incomingMethod ) )]; internal ILookup GetActionMapping() => combinedActionNameMapping; @@ -512,7 +512,7 @@ internal static string CreateAmbiguousMatchList( IEnumerable GetActionMapping( HttpContr } var request = controllerContext.Request; - var requestedVersion = request.GetRequestedApiVersion(); + var requestedVersion = request.RequestedApiVersion; if ( candidateActions.Count == 1 ) { var action = candidateActions[0]; - var metadata = action.GetApiVersionMetadata(); + var metadata = action.ApiVersionMetadata; return metadata.MappingTo( requestedVersion ) != None ? action : null; } @@ -83,7 +83,7 @@ public virtual ILookup GetActionMapping( HttpContr for ( var i = 0; i < candidateActions.Count; i++ ) { var action = candidateActions[i]; - var metadata = action.GetApiVersionMetadata(); + var metadata = action.ApiVersionMetadata; switch ( metadata.MappingTo( requestedVersion ) ) { @@ -112,7 +112,7 @@ public virtual ILookup GetActionMapping( HttpContr private static Exception CreateAmbiguousActionException( IEnumerable matches ) { var ambiguityList = ActionSelectorCacheItem.CreateAmbiguousMatchList( matches ); - var message = string.Format( CultureInfo.CurrentCulture, SR.ApiControllerActionSelector_AmbiguousMatch, ambiguityList ); + var message = string.Format( CultureInfo.CurrentCulture, BackportSR.ApiControllerActionSelector_AmbiguousMatch, ambiguityList ); return new InvalidOperationException( message ); } 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 a9f4d9af..293eaea8 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionParameterBinding.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionParameterBinding.cs @@ -26,7 +26,7 @@ public override Task ExecuteBindingAsync( CancellationToken cancellationToken ) { ArgumentNullException.ThrowIfNull( actionContext ); - var value = actionContext.Request.ApiVersionProperties().RequestedApiVersion; + 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 884a1747..6efef217 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/HttpControllerDescriptorGroup.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/HttpControllerDescriptorGroup.cs @@ -64,15 +64,15 @@ public HttpControllerDescriptorGroup( HttpConfiguration configuration, string co /// the controller is created using the first item in the group. public override IHttpController CreateController( HttpRequestMessage request ) { - var properties = request.ApiVersionProperties(); + var properties = request.ApiVersionProperties; if ( properties.SelectedController is HttpControllerDescriptor descriptor ) { return descriptor.CreateController( request ); } - var url = request.RequestUri.SafeFullPath(); - var message = string.Format( CultureInfo.CurrentCulture, SR.NoControllerSelected, url, properties.RawRequestedApiVersion ); + var url = request.RequestUri.SafePath; + var message = string.Format( CultureInfo.CurrentCulture, BackportSR.NoControllerSelected, url, properties.RawRequestedApiVersion ); throw new InvalidOperationException( message ); } @@ -93,7 +93,7 @@ public override Collection GetCustomAttributes( bool inherit ) attributes.UnionWith( descriptors[i].GetCustomAttributes( inherit ) ); } - return new( attributes.ToList() ); + return new( [.. attributes] ); } /// @@ -111,7 +111,7 @@ public override Collection GetFilters() filters.UnionWith( descriptors[i].GetFilters() ); } - return new( filters.ToList() ); + return new( [.. filters] ); } /// 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 9dd43313..e2d45c81 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ActionApiVersionConventionBuilderBase.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ActionApiVersionConventionBuilderBase.cs @@ -25,7 +25,7 @@ public virtual void ApplyTo( HttpActionDescriptor item ) ApiVersionMetadata metadata; var name = NamingConvention.GroupName( item.ControllerDescriptor.ControllerName ); - if ( VersionNeutral || ( apiModel = item.ControllerDescriptor.GetApiVersionModel() ).IsApiVersionNeutral ) + if ( VersionNeutral || ( apiModel = item.ControllerDescriptor.ApiVersionModel ).IsApiVersionNeutral ) { metadata = string.IsNullOrEmpty( name ) ? ApiVersionMetadata.Neutral @@ -48,7 +48,7 @@ public virtual void ApplyTo( HttpActionDescriptor item ) } else { - emptyVersions = Enumerable.Empty(); + emptyVersions = []; endpointModel = new( declaredVersions: emptyVersions, inheritedSupported, @@ -68,7 +68,7 @@ public virtual void ApplyTo( HttpActionDescriptor item ) } else { - emptyVersions = Enumerable.Empty(); + emptyVersions = []; endpointModel = new( declaredVersions: mapped, supportedVersions: apiModel.SupportedApiVersions, @@ -80,6 +80,6 @@ public virtual void ApplyTo( HttpActionDescriptor item ) metadata = new( apiModel, endpointModel, name ); } - item.SetApiVersionMetadata( metadata ); + item.ApiVersionMetadata = metadata; } } \ No newline at end of file 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 1480d3ba..e25d82cb 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ControllerApiVersionConventionBuilderBase.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ControllerApiVersionConventionBuilderBase.cs @@ -65,22 +65,17 @@ private void ApplyActionConventions( HttpControllerDescriptor controller ) ApiVersionMetadata.Neutral : new ApiVersionMetadata( ApiVersionModel.Neutral, ApiVersionModel.Neutral, name ); - controller.SetApiVersionModel( ApiVersionModel.Neutral ); + controller.ApiVersionModel = ApiVersionModel.Neutral; for ( var i = 0; i < actions.Length; i++ ) { - actions[i].SetApiVersionMetadata( metadata ); + actions[i].ApiVersionMetadata = metadata; } return; } - controller.SetApiVersionModel( - new ApiVersionModel( - SupportedVersions, - DeprecatedVersions, - AdvertisedVersions, - DeprecatedAdvertisedVersions ) ); + controller.ApiVersionModel = new( SupportedVersions, DeprecatedVersions, AdvertisedVersions, DeprecatedAdvertisedVersions ); var controllerBuilder = new ControllerApiVersionConventionBuilder( controller.ControllerType ); diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DependencyResolverExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DependencyResolverExtensions.cs index 13fec24a..7f4f6db4 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DependencyResolverExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DependencyResolverExtensions.cs @@ -9,37 +9,42 @@ namespace Asp.Versioning; internal static class DependencyResolverExtensions { - internal static TService? GetService( this IDependencyResolver resolver ) => - (TService) resolver.GetService( typeof( TService ) ); - - internal static TService GetRequiredService( this IDependencyResolver resolver ) + extension( IDependencyResolver resolver ) { - var service = resolver.GetService(); - var message = string.Format( CultureInfo.CurrentCulture, SR.NoServiceRegistered, typeof( TService ) ); - return service ?? throw new InvalidOperationException( message ); + internal TService? GetService() => (TService) resolver.GetService( typeof( TService ) ); + + internal TService GetRequiredService() + { + var service = resolver.GetService(); + var message = string.Format( CultureInfo.CurrentCulture, BackportSR.NoServiceRegistered, typeof( TService ) ); + return service ?? throw new InvalidOperationException( message ); + } } - internal static IApiVersionParser GetApiVersionParser( this HttpConfiguration configuration ) => - configuration.DependencyResolver.GetService() ?? - configuration.ApiVersioningServices().GetRequiredService(); + extension( HttpConfiguration configuration ) + { + internal IApiVersionParser ApiVersionParser => + configuration.DependencyResolver.GetService() ?? + configuration.ApiVersioningServices.GetRequiredService(); - internal static IReportApiVersions GetApiVersionReporter( this HttpConfiguration configuration ) => - configuration.DependencyResolver.GetService() ?? - configuration.ApiVersioningServices().GetRequiredService(); + internal IReportApiVersions ApiVersionReporter => + configuration.DependencyResolver.GetService() ?? + configuration.ApiVersioningServices.GetRequiredService(); - internal static IControllerNameConvention GetControllerNameConvention( this HttpConfiguration configuration ) => - configuration.DependencyResolver.GetService() ?? - configuration.ApiVersioningServices().GetRequiredService(); + internal IControllerNameConvention ControllerNameConvention => + configuration.DependencyResolver.GetService() ?? + configuration.ApiVersioningServices.GetRequiredService(); - internal static IProblemDetailsFactory GetProblemDetailsFactory( this HttpConfiguration configuration ) => - configuration.DependencyResolver.GetService() ?? - configuration.ApiVersioningServices().GetRequiredService(); + internal IProblemDetailsFactory ProblemDetailsFactory => + configuration.DependencyResolver.GetService() ?? + configuration.ApiVersioningServices.GetRequiredService(); - internal static IPolicyManager GetSunsetPolicyManager( this HttpConfiguration configuration ) => - configuration.DependencyResolver.GetService>() ?? - configuration.ApiVersioningServices().GetRequiredService>(); + internal IPolicyManager SunsetPolicyManager => + configuration.DependencyResolver.GetService>() ?? + configuration.ApiVersioningServices.GetRequiredService>(); - internal static IPolicyManager GetDeprecationPolicyManager( this HttpConfiguration configuration ) => - configuration.DependencyResolver.GetService>() ?? - configuration.ApiVersioningServices().GetRequiredService>(); + internal IPolicyManager DeprecationPolicyManager => + 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 c889b994..613d9f2d 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ApiVersionControllerSelector.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ApiVersionControllerSelector.cs @@ -49,7 +49,7 @@ public virtual IDictionary GetControllerMappin { if ( initializing ) { - throw new InvalidOperationException( SR.ControllerSelectorMappingCycle ); + throw new InvalidOperationException( BackportSR.ControllerSelectorMappingCycle ); } initializing = true; @@ -81,7 +81,7 @@ public virtual IDictionary GetControllerMappin if ( conventionRouteResult.Succeeded ) { EnsureUrlHelper( request ); - return request.ApiVersionProperties().SelectedController = conventionRouteResult.Controller; + return request.ApiVersionProperties.SelectedController = conventionRouteResult.Controller; } exceptionFactory = new( request, context ); @@ -94,7 +94,7 @@ public virtual IDictionary GetControllerMappin if ( directRouteResult.Succeeded ) { EnsureUrlHelper( request ); - return request.ApiVersionProperties().SelectedController = directRouteResult.Controller; + return request.ApiVersionProperties.SelectedController = directRouteResult.Controller; } conventionRouteResult = conventionRouteSelector.SelectController( context ); @@ -102,7 +102,7 @@ public virtual IDictionary GetControllerMappin if ( conventionRouteResult.Succeeded ) { EnsureUrlHelper( request ); - return request.ApiVersionProperties().SelectedController = conventionRouteResult.Controller; + return request.ApiVersionProperties.SelectedController = conventionRouteResult.Controller; } exceptionFactory = new( request, context ); @@ -211,7 +211,7 @@ private static bool IsDecoratedWithAttributes( HttpControllerDescriptor controll private static void ApplyImplicitConventions( HttpControllerDescriptor controller, IHttpActionSelector actionSelector, ApiVersionModel implicitVersionModel ) { - controller.SetApiVersionModel( implicitVersionModel ); + controller.ApiVersionModel = implicitVersionModel; var mapping = actionSelector.GetActionMapping( controller ); @@ -221,13 +221,13 @@ private static void ApplyImplicitConventions( HttpControllerDescriptor controlle } var actions = mapping.SelectMany( g => g ); - var namingConvention = controller.Configuration.GetControllerNameConvention(); + var namingConvention = controller.Configuration.ControllerNameConvention; var name = namingConvention.GroupName( controller.ControllerName ); var metadata = new ApiVersionMetadata( implicitVersionModel, implicitVersionModel, name ); foreach ( var action in actions ) { - action.SetApiVersionMetadata( metadata ); + action.ApiVersionMetadata = metadata; } } @@ -272,7 +272,7 @@ private static void CollateControllerVersions( for ( var i = 0; i < controllers.Count; i++ ) { var controller = controllers[i]; - var model = controller.GetApiVersionModel(); + var model = controller.ApiVersionModel; if ( model.IsApiVersionNeutral ) { @@ -296,7 +296,7 @@ private static void CollateActionVersions( foreach ( var action in actions ) { - var metadata = action.GetApiVersionMetadata(); + var metadata = action.ApiVersionMetadata; if ( metadata.IsApiVersionNeutral ) { @@ -336,7 +336,7 @@ private static void CollateControllerModels( for ( var i = 0; i < visitedControllers.Count; i++ ) { var (controller, model) = visitedControllers[i]; - controller.SetApiVersionModel( model.Aggregate( collatedModel ) ); + controller.ApiVersionModel = model.Aggregate( collatedModel ); } } @@ -344,14 +344,14 @@ private static void ApplyCollatedModelsToActions( HttpConfiguration configuration, List> visitedActions ) { - var namingConvention = configuration.GetControllerNameConvention(); + var namingConvention = configuration.ControllerNameConvention; for ( var i = 0; i < visitedActions.Count; i++ ) { var (controller, action, endpointModel) = visitedActions[i]; - var apiModel = controller.GetApiVersionModel(); + var apiModel = controller.ApiVersionModel; var name = namingConvention.GroupName( controller.ControllerName ); - action.SetApiVersionMetadata( new ApiVersionMetadata( apiModel, endpointModel, name ) ); + action.ApiVersionMetadata = new( apiModel, endpointModel, name ); } } @@ -364,7 +364,7 @@ private static void EnsureUrlHelper( HttpRequestMessage request ) return; } - var options = request.GetApiVersioningOptions(); + var options = request.ApiVersioningOptions; if ( options.ApiVersionReader.VersionsByUrl() ) { @@ -374,7 +374,7 @@ private static void EnsureUrlHelper( HttpRequestMessage request ) private ControllerSelectionContext NewSelectionContext( HttpRequestMessage request ) { - var properties = request.ApiVersionProperties(); + var properties = request.ApiVersionProperties; var context = new ControllerSelectionContext( request, GetControllerName, controllerInfoCache ); HttpResponseExceptionFactory factory; HttpResponseMessage response; 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 729390ce..df1bc918 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelectionContext.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelectionContext.cs @@ -23,12 +23,12 @@ internal ControllerSelectionContext( Lazy> controllerInfoCache ) { Request = request; - requestProperties = request.ApiVersionProperties(); + requestProperties = request.ApiVersionProperties; this.controllerName = new Lazy( () => controllerName( Request ) ); this.controllerInfoCache = controllerInfoCache; RouteData = request.GetRouteData(); conventionRouteCandidates = new Lazy( GetConventionRouteCandidates ); - directRouteCandidates = new Lazy( () => RouteData?.GetDirectRouteCandidates() ); + directRouteCandidates = new Lazy( () => RouteData?.DirectRouteCandidates ); allVersions = new Lazy( CreateAggregatedModel ); } @@ -99,7 +99,7 @@ private static IEnumerable Enumerate( CandidateAction[] candida { for ( var i = 0; i < candidates.Length; i++ ) { - yield return candidates[i].ActionDescriptor.GetApiVersionMetadata().Map( Explicit ); + yield return candidates[i].ActionDescriptor.ApiVersionMetadata.Map( Explicit ); } } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelector.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelector.cs index e2c86819..52c1d109 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelector.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelector.cs @@ -23,7 +23,7 @@ protected static ICollection SelectBestCandidates( IRe for ( var i = 0; i < candidates.Count; i++ ) { var action = candidates[i].ActionDescriptor; - var metadata = action.GetApiVersionMetadata(); + var metadata = action.ApiVersionMetadata; switch ( metadata.MappingTo( apiVersion ) ) { @@ -43,7 +43,7 @@ protected static ICollection SelectBestCandidates( IRe bestMatches.UnionWith( implicitMatches ); break; case 1: - if ( bestMatch!.GetApiVersionMetadata().IsApiVersionNeutral ) + if ( bestMatch!.ApiVersionMetadata.IsApiVersionNeutral ) { bestMatches.UnionWith( implicitMatches ); } diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ConventionRouteControllerSelector.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ConventionRouteControllerSelector.cs index 322b5c45..12661904 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ConventionRouteControllerSelector.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ConventionRouteControllerSelector.cs @@ -39,7 +39,7 @@ public override ControllerSelectionResult SelectController( ControllerSelectionC break; case 1: result.Controller = bestMatches.First(); - result.Controller.SetPossibleCandidates( context.ConventionRouteCandidates!.Select( c => c.ActionDescriptor.ControllerDescriptor ).ToArray() ); + result.Controller.SetPossibleCandidates( [.. context.ConventionRouteCandidates!.Select( c => c.ActionDescriptor.ControllerDescriptor )] ); break; default: if ( TryDisambiguateControllerByAction( request, bestMatches, out var resolvedController ) ) @@ -66,7 +66,7 @@ private static Exception CreateAmbiguousControllerException( IHttpRoute route, s var message = string.Format( CultureInfo.CurrentCulture, - SR.DefaultControllerFactory_ControllerNameAmbiguous_WithRouteTemplate, + BackportSR.DefaultControllerFactory_ControllerNameAmbiguous_WithRouteTemplate, controllerName, route.RouteTemplate, builder, diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/DirectRouteControllerSelector.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/DirectRouteControllerSelector.cs index c08cdb6e..8d7636e3 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/DirectRouteControllerSelector.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/DirectRouteControllerSelector.cs @@ -57,7 +57,7 @@ private static Exception CreateAmbiguousControllerException( IEnumerable> InitializeCache() var services = configuration.Services; var assembliesResolver = services.GetAssembliesResolver(); var typeResolver = services.GetHttpControllerTypeResolver(); - var convention = configuration.GetControllerNameConvention(); + var convention = configuration.ControllerNameConvention; 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 22fac02a..aef27c54 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpResponseExceptionFactory.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpResponseExceptionFactory.cs @@ -34,13 +34,13 @@ internal HttpResponseExceptionFactory( HttpRequestMessage request, ApiVersionMod private ApiVersionModel AllApiVersions => apiVersionModel ??= context!.AllVersions; - private ApiVersioningOptions Options => configuration.GetApiVersioningOptions(); + private ApiVersioningOptions Options => configuration.ApiVersioningOptions; - private IProblemDetailsFactory ProblemDetails => configuration.GetProblemDetailsFactory(); + private IProblemDetailsFactory ProblemDetails => configuration.ProblemDetailsFactory; private ITraceWriter TraceWriter => configuration.Services.GetTraceWriter() ?? NullTraceWriter.Instance; - private IReportApiVersions ApiVersionReporter => configuration.GetApiVersionReporter(); + private IReportApiVersions ApiVersionReporter => configuration.ApiVersionReporter; internal HttpResponseException NewUnmatchedException( ControllerSelectionResult conventionRouteResult, @@ -52,7 +52,7 @@ internal HttpResponseException NewUnmatchedException( if ( couldMatch ) { - properties = request.ApiVersionProperties(); + properties = request.ApiVersionProperties; if ( properties.RawRequestedApiVersions.Count == 0 ) { @@ -73,7 +73,7 @@ internal HttpResponseException NewUnmatchedException( { if ( couldMatch ) { - properties ??= request.ApiVersionProperties(); + properties = request.ApiVersionProperties; if ( properties.RequestedApiVersion is ApiVersion apiVersion ) { @@ -113,9 +113,9 @@ internal HttpResponseException NewUnmatchedException( internal HttpResponseMessage CreateBadRequestForInvalidApiVersion() { - var requestedVersion = request.ApiVersionProperties().RawRequestedApiVersion; - var safeUrl = request.RequestUri.SafeFullPath(); - var detail = string.Format( CultureInfo.CurrentCulture, SR.VersionedResourceNotSupported, safeUrl, requestedVersion ); + var requestedVersion = request.ApiVersionProperties.RawRequestedApiVersion; + var safeUrl = request.RequestUri.SafePath; + var detail = string.Format( CultureInfo.CurrentCulture, BackportSR.VersionedResourceNotSupported, safeUrl, requestedVersion ); TraceWriter.Info( request, ControllerSelectorCategory, detail ); @@ -130,7 +130,7 @@ internal HttpResponseMessage CreateBadRequestForAmbiguousApiVersion( IReadOnlyLi { var detail = string.Format( CultureInfo.InvariantCulture, - CommonSR.MultipleDifferentApiVersionsRequested, + Format.MultipleDifferentApiVersionsRequested, string.Join( ", ", apiVersions ) ); TraceWriter.Info( request, ProblemDetailsDefaults.Ambiguous.Code, detail ); @@ -159,7 +159,7 @@ private static bool CouldMatchApiVersion( ControllerSelectionResult conventionRo private HttpResponseMessage CreateBadRequestForUnspecifiedApiVersion() { - var detail = SR.ApiVersionUnspecified; + var detail = BackportSR.ApiVersionUnspecified; TraceWriter.Info( request, ControllerSelectorCategory, detail ); @@ -172,8 +172,8 @@ private HttpResponseMessage CreateBadRequestForUnspecifiedApiVersion() private HttpResponseMessage CreateResponseForUnsupportedApiVersion( ApiVersion requestedVersion, HttpStatusCode statusCode ) { - var safeUrl = request.RequestUri.SafeFullPath(); - var detail = string.Format( CultureInfo.CurrentCulture, SR.VersionedResourceNotSupported, safeUrl, requestedVersion ); + var safeUrl = request.RequestUri.SafePath; + var detail = string.Format( CultureInfo.CurrentCulture, BackportSR.VersionedResourceNotSupported, safeUrl, requestedVersion ); TraceWriter.Info( request, ControllerSelectorCategory, detail ); @@ -187,8 +187,8 @@ private HttpResponseMessage CreateResponseForUnsupportedApiVersion( ApiVersion r internal HttpResponseMessage CreateMethodNotAllowedResponse( IEnumerable allowedMethods ) { var requestedMethod = request.Method; - var version = request.GetRequestedApiVersion()?.ToString() ?? "(null)"; - var detail = string.Format( CultureInfo.CurrentCulture, SR.VersionedMethodNotSupported, version, requestedMethod ); + var version = request.RequestedApiVersion?.ToString() ?? "(null)"; + var detail = string.Format( CultureInfo.CurrentCulture, BackportSR.VersionedMethodNotSupported, version, requestedMethod ); TraceWriter.Info( request, ControllerSelectorCategory, detail ); @@ -213,13 +213,13 @@ internal HttpResponseException NewMethodNotAllowedException( IEnumerable Read( HttpRequestMessage request ) if ( count == 0 ) { - return Array.Empty(); + return []; } var version = default( string ); @@ -68,6 +68,6 @@ public virtual IReadOnlyList Read( HttpRequestMessage request ) return version == null ? [] : [version]; } - return versions.ToArray(); + return [.. versions]; } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IApiVersionSelectorExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IApiVersionSelectorExtensions.cs index 37d63336..e134d2b6 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IApiVersionSelectorExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IApiVersionSelectorExtensions.cs @@ -7,16 +7,19 @@ namespace Asp.Versioning; /// 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 ) + extension( IApiVersionSelector selector ) { - ArgumentNullException.ThrowIfNull( selector ); - using var request = new HttpRequestMessage(); - return selector.SelectVersion( request, model ); + /// + /// Selects an API version given the specified API version information. + /// + /// The model to select the version from. + /// The selected API version. + public ApiVersion SelectVersion( 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/IProblemDetailsFactory.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IProblemDetailsFactory.cs index 4c701714..42755996 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IProblemDetailsFactory.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IProblemDetailsFactory.cs @@ -2,25 +2,17 @@ namespace Asp.Versioning; -#if NETFRAMEWORK -using HttpRequest = System.Net.Http.HttpRequestMessage; -#else -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -#endif +using System.Net.Http; /// /// Defines the behavior of a factory to produce problem details. /// -#if !NETFRAMEWORK -[CLSCompliant( false )] -#endif public interface IProblemDetailsFactory { /// /// Creates and returns a new problem details instance. /// - /// The current HTTP request. + /// The current HTTP request. /// The value for . /// The value for . /// The value for . @@ -28,7 +20,7 @@ public interface IProblemDetailsFactory /// The value for . /// A new instance. ProblemDetails CreateProblemDetails( - HttpRequest request, + HttpRequestMessage request, int? statusCode = null, string? title = null, string? type = null, diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReaderBuilder.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReaderBuilder.cs index 413f20e0..3ddb5624 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReaderBuilder.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReaderBuilder.cs @@ -34,7 +34,7 @@ from segment in content.Subsegments.OfType() if ( segments.Count() > 1 ) { - var message = string.Format( CultureInfo.CurrentCulture, CommonSR.InvalidMediaTypeTemplate, template ); + var message = string.Format( CultureInfo.CurrentCulture, Format.InvalidMediaTypeTemplate, template ); throw new System.ArgumentException( message, nameof( template ) ); } } diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetails.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetails.cs index e3640c0e..e2fea2bf 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetails.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetails.cs @@ -24,7 +24,8 @@ public class ProblemDetails /// /// A dictionary of additional extension data. [JsonConstructor] - public ProblemDetails( IDictionary extensions ) => this.extensions = extensions; + public ProblemDetails( IDictionary? extensions ) => + this.extensions = extensions ?? new Dictionary( StringComparer.Ordinal ); /// /// Gets or sets a URI reference [RFC3986] that identifies the problem type. This specification encourages that, when diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetailsFactory.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetailsFactory.cs index baaad563..e25073d6 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetailsFactory.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetailsFactory.cs @@ -71,9 +71,9 @@ internal static T AddInvalidExtensions( return problemDetails; } - var safeUrl = request.RequestUri.SafeFullPath(); - var requestedVersion = request.ApiVersionProperties().RawRequestedApiVersion; - var message = string.Format( CurrentCulture, SR.VersionedControllerNameNotFound, safeUrl, requestedVersion ); + var safeUrl = request.RequestUri.SafePath; + var requestedVersion = request.ApiVersionProperties.RawRequestedApiVersion; + var message = string.Format( CurrentCulture, BackportSR.VersionedControllerNameNotFound, safeUrl, requestedVersion ); applyMessage( problemDetails, message ); @@ -97,18 +97,18 @@ internal static T AddUnsupportedExtensions( { case 400: case 404: - messageFormat = SR.VersionedControllerNameNotFound; + messageFormat = BackportSR.VersionedControllerNameNotFound; break; case 405: - messageFormat = SR.VersionedActionNameNotFound; + messageFormat = BackportSR.VersionedActionNameNotFound; break; default: return problemDetails; } - var safeUrl = request.RequestUri.SafeFullPath(); + var safeUrl = request.RequestUri.SafePath; var requestedMethod = request.Method; - var version = request.ApiVersionProperties().RawRequestedApiVersion ?? "(null)"; + var version = request.ApiVersionProperties.RawRequestedApiVersion ?? "(null)"; var message = string.Format( CurrentCulture, messageFormat, safeUrl, version, requestedMethod ); applyMessage( problemDetails, message ); diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/QueryStringApiVersionReader.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/QueryStringApiVersionReader.cs index 8b88062b..b564981a 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/QueryStringApiVersionReader.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/QueryStringApiVersionReader.cs @@ -10,13 +10,13 @@ public partial class QueryStringApiVersionReader /// public virtual IReadOnlyList Read( HttpRequestMessage request ) { - ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull( request ); var count = ParameterNames.Count; if ( count == 0 ) { - return Array.Empty(); + return []; } var version = default( string ); @@ -62,6 +62,6 @@ public virtual IReadOnlyList Read( HttpRequestMessage request ) return version == null ? [] : [version]; } - return versions.ToArray(); + return [.. versions]; } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ReportApiVersionsAttribute.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ReportApiVersionsAttribute.cs index 782118fa..41b15eae 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ReportApiVersionsAttribute.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ReportApiVersionsAttribute.cs @@ -29,8 +29,8 @@ public override void OnActionExecuted( HttpActionExecutedContext actionExecutedC var context = actionExecutedContext.ActionContext; var action = context.ActionDescriptor; - var reporter = reportApiVersions ?? context.ControllerContext.Configuration.GetApiVersionReporter(); - var model = action.GetApiVersionMetadata().Map( reporter.Mapping ); + var reporter = reportApiVersions ?? context.ControllerContext.Configuration.ApiVersionReporter; + var model = action.ApiVersionMetadata.Map( reporter.Mapping ); response.RequestMessage ??= actionExecutedContext.Request; reporter.Report( response, model ); 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 ad930d65..238ea9c2 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionRouteConstraint.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionRouteConstraint.cs @@ -37,8 +37,8 @@ public bool Match( HttpRequestMessage request, IHttpRoute route, string paramete return !string.IsNullOrEmpty( value ); } - var parser = request.GetConfiguration().GetApiVersionParser(); - var properties = request.ApiVersionProperties(); + var parser = request.GetConfiguration().ApiVersionParser; + var properties = request.ApiVersionProperties; properties.RouteParameter = parameterName; properties.RawRequestedApiVersion = value; 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 8a1beea4..58e10e1f 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionUrlHelper.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionUrlHelper.cs @@ -55,7 +55,7 @@ public override string Route( string routeName, IDictionary rout return routeValues; } - var properties = Request.ApiVersionProperties(); + var properties = Request.ApiVersionProperties; var key = properties.RouteParameter; if ( string.IsNullOrEmpty( key ) ) diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ParsedRouteAdapter{T}.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ParsedRouteAdapter{T}.cs index 76ac7302..703939e5 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ParsedRouteAdapter{T}.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ParsedRouteAdapter{T}.cs @@ -64,7 +64,7 @@ private IReadOnlyList AdaptToPathSegments() adapters.Add( adapter ); } - return adapters.ToArray(); + return [.. adapters]; } private static Func> NewPathSegmentsAccessor() diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/PathContentSegmentAdapter{T}.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/PathContentSegmentAdapter{T}.cs index 7f62ae53..dcf2742f 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/PathContentSegmentAdapter{T}.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/PathContentSegmentAdapter{T}.cs @@ -56,7 +56,7 @@ private IReadOnlyList AdaptToPathSubsegments() adapters.Add( adapter ); } - return adapters.ToArray(); + return [.. adapters]; } private static Func NewCatchAllAccessor() diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/RouteParser.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/RouteParser.cs index 7af33fae..d984fb49 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/RouteParser.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/RouteParser.cs @@ -59,7 +59,7 @@ private static Func NewParseFunc() { var routeParserType = Type.GetType( "System.Web.Http.Routing.RouteParser, System.Web.Http", throwOnError: true, ignoreCase: false ); var routeTemplate = Parameter( typeof( string ), "routeTemplate" ); - var parse = routeParserType.GetRuntimeMethod( nameof( Parse ), new[] { typeof( string ) } ); + var parse = routeParserType.GetRuntimeMethod( nameof( Parse ), [typeof( string )] ); var body = Call( parse, routeTemplate ); var lambda = Lambda>( body, routeTemplate ); 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 121d6e75..d9abba89 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/UrlHelperExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/UrlHelperExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http.Routing; using Asp.Versioning.Routing; @@ -10,26 +12,28 @@ namespace System.Web.Http.Routing; /// public static class UrlHelperExtensions { - /// - /// Returns a new URL helper that includes the requested API version. - /// /// The extended URL helper. - /// A new URL helper that excludes the requested - /// API version or the original URL helper if - /// unnecessary. - /// Excluding the requested API version is useful in a limited set of scenarios - /// such as building a URL from an API that versions by URL segment to an API that is - /// version-neutral. A version-neutral API would not use the specified route value and - /// it would be erroneously added as a query string parameter. - public static UrlHelper WithoutApiVersion( this UrlHelper urlHelper ) + extension( UrlHelper urlHelper ) { - ArgumentNullException.ThrowIfNull( urlHelper ); - - if ( urlHelper is WithoutApiVersionUrlHelper ) + /// + /// Returns a new URL helper that includes the requested API version. + /// + /// A new URL helper that excludes the requested + /// API version or the original URL helper if unnecessary. + /// Excluding the requested API version is useful in a limited set of scenarios + /// such as building a URL from an API that versions by URL segment to an API that is + /// version-neutral. A version-neutral API would not use the specified route value and + /// it would be erroneously added as a query string parameter. + public UrlHelper WithoutApiVersion() { - return urlHelper; - } + ArgumentNullException.ThrowIfNull( urlHelper ); - return new WithoutApiVersionUrlHelper( urlHelper ); + if ( urlHelper is WithoutApiVersionUrlHelper ) + { + return urlHelper; + } + + return new WithoutApiVersionUrlHelper( urlHelper ); + } } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/WithoutApiVersionUrlHelper.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/WithoutApiVersionUrlHelper.cs index 58686bd0..952015b1 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/WithoutApiVersionUrlHelper.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/WithoutApiVersionUrlHelper.cs @@ -12,7 +12,7 @@ internal sealed class WithoutApiVersionUrlHelper : UrlHelper public WithoutApiVersionUrlHelper( UrlHelper decorated ) => this.decorated = decorated; - private ApiVersionRequestProperties Properties => decorated.Request.ApiVersionProperties(); + private ApiVersionRequestProperties Properties => decorated.Request.ApiVersionProperties; public override string Content( string path ) { 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 e6163773..714139d9 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Net.Http; using Asp.Versioning; @@ -16,123 +18,129 @@ public static class HttpRequestMessageExtensions private const string RoutingContextKey = "MS_RoutingContext"; private const string ApiVersionPropertiesKey = "MS_" + nameof( ApiVersionRequestProperties ); - private static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, Func errorCreator ) + extension( HttpRequestMessage request ) { - var configuration = request.GetConfiguration(); - var error = errorCreator( request.ShouldIncludeErrorDetail() ); - - if ( configuration == null ) + private HttpResponseMessage CreateErrorResponse( HttpStatusCode statusCode, Func errorCreator ) { - configuration = new HttpConfiguration(); - request.RegisterForDispose( configuration ); - request.SetConfiguration( configuration ); - } - - return request.CreateResponse( statusCode, error, configuration ); - } + var configuration = request.GetConfiguration(); + var error = errorCreator( request.ShouldIncludeErrorDetail() ); - internal static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, string message, string messageDetail ) - { - return request.CreateErrorResponse( - statusCode, - includeErrorDetail => + if ( configuration == null ) { - var error = new HttpError( message ); + configuration = new HttpConfiguration(); + request.RegisterForDispose( configuration ); + request.SetConfiguration( configuration ); + } + + return request.CreateResponse( statusCode, error, configuration ); + } - if ( includeErrorDetail ) + internal HttpResponseMessage CreateErrorResponse( HttpStatusCode statusCode, string message, string messageDetail ) + { + return request.CreateErrorResponse( + statusCode, + includeErrorDetail => { - error.MessageDetail = messageDetail; - } + var error = new HttpError( message ); - return error; - } ); - } + if ( includeErrorDetail ) + { + error.MessageDetail = messageDetail; + } - /// - /// Gets the current API versioning options. - /// - /// The request to get the API versioning options for. - /// The current API versioning options. - public static ApiVersioningOptions GetApiVersioningOptions( this HttpRequestMessage request ) - { - var configuration = request.GetConfiguration(); + return error; + } ); + } - if ( configuration == null ) + /// + /// Gets the current API versioning options. + /// + /// The current API versioning options. + public ApiVersioningOptions ApiVersioningOptions { - configuration = new HttpConfiguration(); - request.RegisterForDispose( configuration ); - request.SetConfiguration( configuration ); - } + get + { + var configuration = request.GetConfiguration(); - return configuration.GetApiVersioningOptions(); - } + if ( configuration == null ) + { + configuration = new HttpConfiguration(); + request.RegisterForDispose( configuration ); + request.SetConfiguration( configuration ); + } - /// - /// Gets the current API versioning request properties. - /// - /// The request to get the API versioning properties for. - /// The current API versioning properties. - public static ApiVersionRequestProperties ApiVersionProperties( this HttpRequestMessage request ) - { - ArgumentNullException.ThrowIfNull( request ); + return configuration.ApiVersioningOptions; + } + } - if ( request.Properties.TryGetValue( ApiVersionPropertiesKey, out ApiVersionRequestProperties? properties ) ) + /// + /// Gets the current API versioning request properties. + /// + /// The current API versioning properties. + public ApiVersionRequestProperties ApiVersionProperties { - return properties!; - } + get + { + ArgumentNullException.ThrowIfNull( request ); - var forceRouteConstraintEvaluation = !request.Properties.ContainsKey( RoutingContextKey ); + if ( request.Properties.TryGetValue( ApiVersionPropertiesKey, out ApiVersionRequestProperties? properties ) ) + { + return properties!; + } - request.Properties[ApiVersionPropertiesKey] = properties = new( request ); + var forceRouteConstraintEvaluation = !request.Properties.ContainsKey( RoutingContextKey ); - if ( forceRouteConstraintEvaluation && request.GetConfiguration() is HttpConfiguration configuration ) - { - // HACK: do NOT use 'HttpRouteCollection.GetRouteData' because it can result in a LockRecursionException when hosted on IIS - // REF: https://github.com/microsoft/referencesource/blob/master/System.Web/Routing/RouteCollection.cs#L159 - var routes = configuration.Routes; - var context = request.GetRequestContext(); - var virtualPathRoot = context?.VirtualPathRoot ?? routes.VirtualPathRoot ?? string.Empty; - - // HACK: do NOT use a normal 'for' loop here because the IIS implementation does not support indexing - foreach ( var route in routes ) - { - if ( route.GetRouteData( virtualPathRoot, request ) is not null ) + request.Properties[ApiVersionPropertiesKey] = properties = new( request ); + + if ( forceRouteConstraintEvaluation && request.GetConfiguration() is HttpConfiguration configuration ) { - break; + // HACK: do NOT use 'HttpRouteCollection.GetRouteData' because it can result in a LockRecursionException when hosted on IIS + // REF: https://github.com/microsoft/referencesource/blob/master/System.Web/Routing/RouteCollection.cs#L159 + var routes = configuration.Routes; + var context = request.GetRequestContext(); + var virtualPathRoot = context?.VirtualPathRoot ?? routes.VirtualPathRoot ?? string.Empty; + + // HACK: do NOT use a normal 'for' loop here because the IIS implementation does not support indexing + foreach ( var route in routes ) + { + if ( route.GetRouteData( virtualPathRoot, request ) is not null ) + { + break; + } + } } + + return properties; } } - return properties; - } + /// + /// Gets the current service API version requested. + /// + /// The requested API version. + /// This method will return null no service API version was requested or the requested + /// service API version is in an invalid format. + /// Multiple, different API versions were requested. + public ApiVersion? RequestedApiVersion => request.ApiVersionProperties.RequestedApiVersion; - /// - /// Gets the current service API version requested. - /// - /// The request to get the API version for. - /// The requested API version. - /// This method will return null no service API version was requested or the requested - /// service API version is in an invalid format. - /// Multiple, different API versions were requested. - public static ApiVersion? GetRequestedApiVersion( this HttpRequestMessage request ) => request.ApiVersionProperties().RequestedApiVersion; - - internal static Tuple GetProblemDetailsResponseType( this HttpRequestMessage request ) - { - var configuration = request.GetConfiguration(); - var negotiator = configuration.Services.GetContentNegotiator(); - var result = negotiator.Negotiate( typeof( ProblemDetails ), request, configuration.Formatters ); - - return result.MediaType.MediaType switch + internal Tuple GetProblemDetailsResponseType() { - 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 ), - }; + var configuration = request.GetConfiguration(); + var negotiator = configuration.Services.GetContentNegotiator(); + var result = negotiator.Negotiate( typeof( ProblemDetails ), request, configuration.Formatters ); + + return result.MediaType.MediaType switch + { + 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 7af8d37f..4ec9d1dc 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Net.Http; using Asp.Versioning; @@ -16,24 +18,27 @@ public static class HttpResponseMessageExtensions private const string Deprecation = nameof( Deprecation ); private const string Link = nameof( Link ); - /// - /// Writes the sunset policy to the specified HTTP response. - /// /// The HTTP response to write to. - /// The sunset policy to write. - public static void WriteSunsetPolicy( this HttpResponseMessage response, SunsetPolicy sunsetPolicy ) + extension( HttpResponseMessage response ) { - ArgumentNullException.ThrowIfNull( response ); - ArgumentNullException.ThrowIfNull( sunsetPolicy ); + /// + /// Writes the sunset policy to the specified HTTP response. + /// + /// The sunset policy to write. + public void WriteSunsetPolicy( SunsetPolicy sunsetPolicy ) + { + ArgumentNullException.ThrowIfNull( response ); + ArgumentNullException.ThrowIfNull( sunsetPolicy ); - var headers = response.Headers; + var headers = response.Headers; - if ( sunsetPolicy.Date.HasValue ) - { - headers.Add( Sunset, sunsetPolicy.Date.Value.ToString( "r" ) ); - } + if ( sunsetPolicy.Date.HasValue ) + { + headers.Add( Sunset, sunsetPolicy.Date.Value.ToString( "r" ) ); + } - AddLinkHeaders( headers, sunsetPolicy.Links ); + AddLinkHeaders( headers, sunsetPolicy.Links ); + } } /// @@ -61,7 +66,7 @@ public static void WriteDeprecationPolicy( this HttpResponseMessage response, De private static void AddLinkHeaders( HttpResponseHeaders headers, IList links ) { var values = headers.TryGetValues( Link, out var existing ) - ? existing is ICollection collection && !collection.IsReadOnly ? collection : new List( existing ) + ? existing is ICollection collection && !collection.IsReadOnly ? collection : [.. existing] : new List( capacity: links.Count ); for ( var i = 0; i < links.Count; i++ ) 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 77a1f230..4966901f 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 @@ -1,10 +1,11 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning; using Backport; -using System.ComponentModel; using System.Web.Http.Controllers; /// @@ -14,36 +15,35 @@ public static class HttpActionDescriptorExtensions { private const string AttributeRoutedPropertyKey = "MS_IsAttributeRouted"; - /// - /// Gets the API version information associated with an action. - /// /// The action to evaluate. - /// The API version information for the action. - public static ApiVersionMetadata GetApiVersionMetadata( this HttpActionDescriptor action ) + extension( HttpActionDescriptor action ) { - ArgumentNullException.ThrowIfNull( action ); - - if ( action.Properties.TryGetValue( typeof( ApiVersionMetadata ), out ApiVersionMetadata? value ) ) + /// + /// Gets or sets the API version information associated with an action. + /// + /// The API version information for the action. + /// Setting this property is meant for infrastructure and should not be used by application code. + public ApiVersionMetadata ApiVersionMetadata { - return value!; + get + { + ArgumentNullException.ThrowIfNull( action ); + + if ( action.Properties.TryGetValue( typeof( ApiVersionMetadata ), out ApiVersionMetadata? value ) ) + { + return value!; + } + + return Asp.Versioning.ApiVersionMetadata.Empty; + } + set + { + ArgumentNullException.ThrowIfNull( action ); + action.Properties.AddOrUpdate( typeof( ApiVersionMetadata ), value, ( key, oldValue ) => value ); + } } - return ApiVersionMetadata.Empty; - } - - /// - /// Sets the API version information associated with an action. - /// - /// The action to evaluate.' - /// The API version information for the action. - /// This API is meant for infrastructure and should not be used by application code. - [EditorBrowsable( EditorBrowsableState.Never )] - public static void SetApiVersionMetadata( this HttpActionDescriptor action, ApiVersionMetadata value ) - { - ArgumentNullException.ThrowIfNull( action ); - action.Properties.AddOrUpdate( typeof( ApiVersionMetadata ), value, ( key, oldValue ) => value ); + internal bool IsAttributeRouted => + action.Properties.TryGetValue( AttributeRoutedPropertyKey, out bool? value ) && ( value ?? false ); } - - internal static bool IsAttributeRouted( this HttpActionDescriptor action ) => - action.Properties.TryGetValue( AttributeRoutedPropertyKey, out bool? value ) && ( value ?? false ); } \ No newline at end of file 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 0051de64..94409c2a 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning; @@ -20,85 +22,115 @@ public static class HttpConfigurationExtensions { private const string ApiVersioningServicesKey = "MS_ApiVersioningServices"; - /// - /// Gets the current API versioning options. - /// /// The current configuration. - /// The current API versioning options. - public static ApiVersioningOptions GetApiVersioningOptions( this HttpConfiguration configuration ) + extension( HttpConfiguration configuration ) { - ArgumentNullException.ThrowIfNull( configuration ); - return configuration.ApiVersioningServices().ApiVersioningOptions; - } + /// + /// Gets the current API versioning options. + /// + /// The current API versioning options. + public ApiVersioningOptions ApiVersioningOptions + { + get + { + ArgumentNullException.ThrowIfNull( configuration ); + return configuration.ApiVersioningServices.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; - } + /// + /// Converts problem details into error objects. + /// + /// 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 void ConvertProblemDetailsToErrorObject() + { + ArgumentNullException.ThrowIfNull( configuration ); + configuration.Initializer += EnableErrorObjectResponses; + } - /// - /// Adds service API versioning to the specified services collection. - /// - /// The configuration that will use service versioning. - public static void AddApiVersioning( this HttpConfiguration configuration ) - { - ArgumentNullException.ThrowIfNull( configuration ); - configuration.AddApiVersioning( new ApiVersioningOptions() ); - } + /// + /// Adds service API versioning to the specified services collection. + /// + public void AddApiVersioning() + { + ArgumentNullException.ThrowIfNull( configuration ); + configuration.AddApiVersioning( new ApiVersioningOptions() ); + } - /// - /// Adds service API versioning to the specified services collection. - /// - /// The configuration that will use service versioning. - /// An action used to configure the provided options. - public static void AddApiVersioning( this HttpConfiguration configuration, Action setupAction ) - { - ArgumentNullException.ThrowIfNull( configuration ); - ArgumentNullException.ThrowIfNull( setupAction ); + /// + /// Adds service API versioning to the specified services collection. + /// + /// An action used to configure the provided options. + public void AddApiVersioning( Action setupAction ) + { + ArgumentNullException.ThrowIfNull( configuration ); + ArgumentNullException.ThrowIfNull( setupAction ); - var options = new ApiVersioningOptions(); + var options = new ApiVersioningOptions(); - setupAction( options ); - ValidateApiVersioningOptions( options ); - configuration.AddApiVersioning( options ); - } + setupAction( options ); + ValidateApiVersioningOptions( options ); + configuration.AddApiVersioning( options ); + } - private static void AddApiVersioning( this HttpConfiguration configuration, ApiVersioningOptions options ) - { - var services = configuration.Services; + private void AddApiVersioning( ApiVersioningOptions options ) + { + var services = configuration.Services; - services.Replace( typeof( IHttpControllerSelector ), new ApiVersionControllerSelector( configuration, options ) ); - services.Replace( typeof( IHttpActionSelector ), new ApiVersionActionSelector() ); + services.Replace( typeof( IHttpControllerSelector ), new ApiVersionControllerSelector( configuration, options ) ); + services.Replace( typeof( IHttpActionSelector ), new ApiVersionActionSelector() ); - if ( options.ReportApiVersions ) - { - configuration.Filters.Add( new ReportApiVersionsAttribute() ); - } + if ( options.ReportApiVersions ) + { + configuration.Filters.Add( new ReportApiVersionsAttribute() ); + } - var reader = options.ApiVersionReader; + var reader = options.ApiVersionReader; - if ( reader.VersionsByMediaType() ) + if ( reader.VersionsByMediaType() ) + { + var parameterName = reader.GetParameterName( MediaTypeParameter ); + + if ( !string.IsNullOrEmpty( parameterName ) ) + { + configuration.Filters.Add( new ApplyContentTypeVersionActionFilter( reader ) ); + } + } + + configuration.ApiVersioningServices.ApiVersioningOptions = options; + configuration.ParameterBindingRules.Add( typeof( ApiVersion ), ApiVersionParameterBinding.Create ); + configuration.Formatters.Insert( 0, new ProblemDetailsMediaTypeFormatter( configuration.Formatters.JsonFormatter ?? new() ) ); + } + + private void EnableErrorObjectResponses() { - var parameterName = reader.GetParameterName( MediaTypeParameter ); + configuration.ApiVersioningServices.Replace( + typeof( IProblemDetailsFactory ), + static ( sc, t ) => new ErrorObjectFactory() ); + + var formatters = configuration.Formatters; + var problemDetails = ProblemDetailsMediaTypeFormatter.DefaultMediaType; - if ( !string.IsNullOrEmpty( parameterName ) ) + for ( var i = 0; i < formatters.Count; i++ ) { - configuration.Filters.Add( new ApplyContentTypeVersionActionFilter( reader ) ); + var mediaTypes = formatters[i].SupportedMediaTypes; + + for ( var j = 0; j < mediaTypes.Count; j++ ) + { + if ( mediaTypes[j].Equals( problemDetails ) ) + { + formatters.RemoveAt( i ); + return; + } + } } } - configuration.ApiVersioningServices().ApiVersioningOptions = options; - configuration.ParameterBindingRules.Add( typeof( ApiVersion ), ApiVersionParameterBinding.Create ); - configuration.Formatters.Insert( 0, new ProblemDetailsMediaTypeFormatter( configuration.Formatters.JsonFormatter ?? new() ) ); + internal DefaultContainer ApiVersioningServices => + (DefaultContainer) configuration.Properties.GetOrAdd( ApiVersioningServicesKey, key => new DefaultContainer() ); } // ApiVersion.Neutral does not have the same meaning as IApiVersionNeutral. setting @@ -114,41 +146,14 @@ private static void ValidateApiVersioningOptions( ApiVersioningOptions options ) { var message = string.Format( CultureInfo.CurrentCulture, - SR.InvalidDefaultApiVersion, + BackportSR.InvalidDefaultApiVersion, nameof( ApiVersion ), nameof( ApiVersion.Neutral ), - nameof( ApiVersioningOptions ), - nameof( ApiVersioningOptions.DefaultApiVersion ), + nameof( Asp.Versioning.ApiVersioningOptions ), + nameof( Asp.Versioning.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; - - 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 8efe2d8c..06645e74 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning; @@ -17,110 +19,109 @@ public static class HttpControllerDescriptorExtensions private const string AttributeRoutedPropertyKey = "MS_IsAttributeRouted"; private const string PossibleControllerCandidatesKey = "MS_PossibleControllerCandidates"; - /// - /// Gets the API version information associated with a controller. - /// /// The controller to evaluate. - /// The API version information for the controller. - /// - /// - /// A controller only contains implicitly declared API versions relative to an action. Most scenarios - /// should use instead. Components - /// such as the may need to know API versions declared by an action's defining controller. - /// - /// - /// This API is meant for infrastructure and should not be used by application code. - /// - /// - [EditorBrowsable( EditorBrowsableState.Never )] - public static ApiVersionModel GetApiVersionModel( this HttpControllerDescriptor controllerDescriptor ) + extension( HttpControllerDescriptor controllerDescriptor ) { - ArgumentNullException.ThrowIfNull( controllerDescriptor ); - - if ( controllerDescriptor.Properties.TryGetValue( typeof( ApiVersionModel ), out ApiVersionModel? value ) ) + /// + /// Gets or sets the API version information associated with a controller. + /// + /// The API version information for the controller. + /// + /// + /// A controller only contains implicitly declared API versions relative to an action. Most scenarios + /// should use instead. Components + /// such as the may need to know API versions declared by an action's defining controller. + /// + /// + /// This API is meant for infrastructure and should not be used by application code. + /// + /// + [EditorBrowsable( EditorBrowsableState.Never )] + public ApiVersionModel ApiVersionModel { - return value!; - } + get + { + ArgumentNullException.ThrowIfNull( controllerDescriptor ); - return ApiVersionModel.Empty; - } + if ( controllerDescriptor.Properties.TryGetValue( typeof( ApiVersionModel ), out ApiVersionModel? value ) ) + { + return value!; + } - /// - /// Sets the API version information associated with a controller. - /// - /// The controller to evaluate. - /// The API version information for the controller. - /// This API is meant for infrastructure and should not be used by application code. - [EditorBrowsable( EditorBrowsableState.Never )] - public static void SetApiVersionModel( this HttpControllerDescriptor controllerDescriptor, ApiVersionModel value ) - { - ArgumentNullException.ThrowIfNull( controllerDescriptor ); + return ApiVersionModel.Empty; + } + set + { + ArgumentNullException.ThrowIfNull( controllerDescriptor ); - controllerDescriptor.Properties.AddOrUpdate( typeof( ApiVersionModel ), value, ( key, oldValue ) => value ); + controllerDescriptor.Properties.AddOrUpdate( typeof( ApiVersionModel ), value, ( key, oldValue ) => value ); - if ( controllerDescriptor is IEnumerable grouped ) - { - foreach ( var controller in grouped ) - { - controller.Properties.AddOrUpdate( typeof( ApiVersionModel ), value, ( key, oldValue ) => value ); + if ( controllerDescriptor is IEnumerable grouped ) + { + foreach ( var controller in grouped ) + { + controller.Properties.AddOrUpdate( typeof( ApiVersionModel ), value, ( key, oldValue ) => value ); + } + } } } - } - - /// - /// Enumerates a controller descriptor as a sequence of descriptors. - /// - /// The controller descriptor to enumerate. - /// A sequence of controller descriptors. - /// This method will flatten a sequence of composite descriptors such as . - /// If the controller descriptor is not a composite, it yields itself. - public static IEnumerable AsEnumerable( this HttpControllerDescriptor controllerDescriptor ) => - AsEnumerable( controllerDescriptor, includeCandidates: false ); - - internal static IEnumerable AsEnumerable( this HttpControllerDescriptor controllerDescriptor, bool includeCandidates ) - { - ArgumentNullException.ThrowIfNull( controllerDescriptor ); - var visited = new HashSet(); + /// + /// Enumerates a controller descriptor as a sequence of descriptors. + /// + /// A sequence of controller descriptors. + /// This method will flatten a sequence of composite descriptors such as . + /// If the controller descriptor is not a composite, it yields itself. + public IEnumerable AsEnumerable() => controllerDescriptor.AsEnumerable( includeCandidates: false ); - if ( controllerDescriptor is IEnumerable groupedDescriptors ) + internal IEnumerable AsEnumerable( bool includeCandidates ) { - foreach ( var groupedDescriptor in groupedDescriptors ) + ArgumentNullException.ThrowIfNull( controllerDescriptor ); + + var visited = new HashSet(); + + if ( controllerDescriptor is IEnumerable groupedDescriptors ) { - if ( visited.Add( groupedDescriptor ) ) + foreach ( var groupedDescriptor in groupedDescriptors ) { - yield return groupedDescriptor; + if ( visited.Add( groupedDescriptor ) ) + { + yield return groupedDescriptor; + } } } - } - else - { - visited.Add( controllerDescriptor ); - yield return controllerDescriptor; - } + else + { + visited.Add( controllerDescriptor ); + yield return controllerDescriptor; + } - if ( !includeCandidates || !controllerDescriptor.Properties.TryGetValue( PossibleControllerCandidatesKey, out IEnumerable? candidates ) ) - { - yield break; + if ( !includeCandidates || !controllerDescriptor.Properties.TryGetValue( PossibleControllerCandidatesKey, out IEnumerable? candidates ) ) + { + yield break; + } + + foreach ( var candidate in candidates! ) + { + if ( visited.Add( candidate ) ) + { + yield return candidate; + } + } + + visited.Clear(); } - foreach ( var candidate in candidates! ) + internal bool IsAttributeRouted { - if ( visited.Add( candidate ) ) + get { - yield return candidate; + controllerDescriptor.Properties.TryGetValue( AttributeRoutedPropertyKey, out bool? value ); + return value ?? false; } } - visited.Clear(); + internal void SetPossibleCandidates( IEnumerable value ) => + controllerDescriptor.Properties.AddOrUpdate( PossibleControllerCandidatesKey, value, ( key, oldValue ) => value ); } - - internal static bool IsAttributeRouted( this HttpControllerDescriptor controller ) - { - controller.Properties.TryGetValue( AttributeRoutedPropertyKey, out bool? value ); - return value ?? false; - } - - internal static void SetPossibleCandidates( this HttpControllerDescriptor controllerDescriptor, IEnumerable value ) => - controllerDescriptor.Properties.AddOrUpdate( PossibleControllerCandidatesKey, value, ( key, oldValue ) => value ); } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpParameterBindingExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpParameterBindingExtensions.cs index c7c8b776..07b449f8 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpParameterBindingExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpParameterBindingExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using System.Web.Http.Controllers; @@ -8,20 +10,26 @@ namespace System.Web.Http; internal static class HttpParameterBindingExtensions { - internal static bool WillReadUri( this HttpParameterBinding parameterBinding ) + extension( HttpParameterBinding parameterBinding ) { - if ( parameterBinding is not IValueProviderParameterBinding valueProviderParameterBinding ) + internal bool WillReadUri { - return false; - } + get + { + if ( parameterBinding is not IValueProviderParameterBinding valueProviderParameterBinding ) + { + return false; + } - var valueProviderFactories = valueProviderParameterBinding.ValueProviderFactories; + var valueProviderFactories = valueProviderParameterBinding.ValueProviderFactories; - if ( valueProviderFactories.Any() && valueProviderFactories.All( factory => factory is IUriValueProviderFactory ) ) - { - return true; - } + if ( valueProviderFactories.Any() && valueProviderFactories.All( factory => factory is IUriValueProviderFactory ) ) + { + return true; + } - return false; + return false; + } + } } } \ No newline at end of file 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 64310188..897b7fc0 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Backport; @@ -12,67 +14,73 @@ namespace System.Web.Http; /// public static class HttpRouteCollectionExtensions { - /// - /// Returns the route collection as a read-only dictionary mapping configured names to routes. - /// /// The route collection to convert. - /// A new read-only dictionary of - /// routes mapped to their name. - public static IReadOnlyDictionary ToDictionary( this HttpRouteCollection routes ) + extension( HttpRouteCollection routes ) { - ArgumentNullException.ThrowIfNull( routes ); - - const string HostedHttpRouteCollection = "System.Web.Http.WebHost.Routing.HostedHttpRouteCollection"; - - try + /// + /// Returns the route collection as a read-only dictionary mapping configured names to routes. + /// + /// A new read-only dictionary of + /// routes mapped to their name. + public IReadOnlyDictionary ToDictionary() { - return routes.CopyToDictionary(); + ArgumentNullException.ThrowIfNull( routes ); + + const string HostedHttpRouteCollection = "System.Web.Http.WebHost.Routing.HostedHttpRouteCollection"; + + try + { + return routes.CopyToDictionary(); + } + catch ( NotSupportedException ) when ( routes.GetType().FullName == HostedHttpRouteCollection ) + { + return routes.BuildDictionaryFromKeys(); + } } - catch ( NotSupportedException ) when ( routes.GetType().FullName == HostedHttpRouteCollection ) + + private IReadOnlyDictionary CopyToDictionary() { - return routes.BuildDictionaryFromKeys(); - } - } + var items = new KeyValuePair[routes.Count]; - private static IReadOnlyDictionary CopyToDictionary( this HttpRouteCollection routes ) - { - var items = new KeyValuePair[routes.Count]; + routes.CopyTo( items, 0 ); - routes.CopyTo( items, 0 ); + var dictionary = new Dictionary( routes.Count, StringComparer.OrdinalIgnoreCase ); - var dictionary = new Dictionary( routes.Count, StringComparer.OrdinalIgnoreCase ); + for ( var i = 0; i < items.Length; i++ ) + { + var item = items[i]; + dictionary[item.Key] = item.Value; + } - for ( var i = 0; i < items.Length; i++ ) - { - var item = items[i]; - dictionary[item.Key] = item.Value; + return dictionary; } - return dictionary; - } + private IReadOnlyDictionary BuildDictionaryFromKeys() + { + var keys = routes.Keys; + var dictionary = new Dictionary( routes.Count, StringComparer.OrdinalIgnoreCase ); - private static IReadOnlyDictionary BuildDictionaryFromKeys( this HttpRouteCollection routes ) - { - var keys = routes.Keys(); - var dictionary = new Dictionary( routes.Count, StringComparer.OrdinalIgnoreCase ); + for ( var i = 0; i < keys.Count; i++ ) + { + var key = keys[i]; + dictionary[key] = routes[key]; + } - for ( var i = 0; i < keys.Count; i++ ) - { - var key = keys[i]; - dictionary[key] = routes[key]; + return dictionary; } - return dictionary; - } - - private static IReadOnlyList Keys( this HttpRouteCollection routes ) - { - var collection = GetDictionaryKeys( routes ); - var keys = new string[collection.Count]; + private IReadOnlyList Keys + { + get + { + var collection = GetDictionaryKeys( routes ); + var keys = new string[collection.Count]; - collection.CopyTo( keys, 0 ); + collection.CopyTo( keys, 0 ); - return keys; + return keys; + } + } } private static ICollection GetDictionaryKeys( HttpRouteCollection routes ) 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 314c9bc9..1148efc8 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning.Routing; @@ -7,32 +9,38 @@ namespace System.Web.Http; internal static class HttpRouteDataExtensions { - internal static CandidateAction[]? GetDirectRouteCandidates( this IHttpRouteData routeData ) + extension( IHttpRouteData routeData ) { - var subRoutes = routeData.GetSubRoutes(); - - if ( subRoutes == null ) + internal CandidateAction[]? DirectRouteCandidates { - if ( routeData.Route == null ) + get { - return null; - } + var subRoutes = routeData.GetSubRoutes(); - return routeData.Route.GetDirectRouteCandidates(); - } + if ( subRoutes == null ) + { + if ( routeData.Route == null ) + { + return null; + } - var list = new List(); + return routeData.Route.DirectRouteCandidates; + } - foreach ( var data in subRoutes ) - { - var directRouteCandidates = data.Route.GetDirectRouteCandidates(); + var list = new List(); - if ( directRouteCandidates != null ) - { - list.AddRange( directRouteCandidates ); + foreach ( var data in subRoutes ) + { + var directRouteCandidates = data.Route.DirectRouteCandidates; + + if ( directRouteCandidates != null ) + { + list.AddRange( directRouteCandidates ); + } + } + + return [.. list]; } } - - 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 9971270b..73b07baf 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning.Routing; @@ -9,46 +11,52 @@ namespace System.Web.Http; internal static class HttpRouteExtensions { - internal static CandidateAction[]? GetDirectRouteCandidates( this IHttpRoute route ) + extension( IHttpRoute route ) { - var dataTokens = route.DataTokens; - - if ( dataTokens == null ) - { - return null; - } - - var directRouteActions = default( HttpActionDescriptor[] ); - - if ( dataTokens.TryGetValue( RouteDataTokenKeys.Actions, out HttpActionDescriptor[]? possibleDirectRouteActions ) && - possibleDirectRouteActions != null && - possibleDirectRouteActions.Length > 0 ) - { - directRouteActions = possibleDirectRouteActions; - } - - if ( directRouteActions == null ) - { - return null; - } - - if ( !dataTokens.TryGetValue( RouteDataTokenKeys.Order, out int order ) ) - { - order = 0; - } - - if ( !dataTokens.TryGetValue( RouteDataTokenKeys.Precedence, out decimal precedence ) ) + internal CandidateAction[]? DirectRouteCandidates { - precedence = 0m; + get + { + var dataTokens = route.DataTokens; + + if ( dataTokens == null ) + { + return null; + } + + var directRouteActions = default( HttpActionDescriptor[] ); + + if ( dataTokens.TryGetValue( RouteDataTokenKeys.Actions, out HttpActionDescriptor[]? possibleDirectRouteActions ) && + possibleDirectRouteActions != null && + possibleDirectRouteActions.Length > 0 ) + { + directRouteActions = possibleDirectRouteActions; + } + + if ( directRouteActions == null ) + { + return null; + } + + if ( !dataTokens.TryGetValue( RouteDataTokenKeys.Order, out int order ) ) + { + order = 0; + } + + if ( !dataTokens.TryGetValue( RouteDataTokenKeys.Precedence, out decimal precedence ) ) + { + precedence = 0m; + } + + var candidates = new List( capacity: directRouteActions.Length ); + + for ( var i = 0; i < directRouteActions.Length; i++ ) + { + candidates.Add( new CandidateAction( directRouteActions[i], order, precedence ) ); + } + + return [.. candidates]; + } } - - var candidates = new List( capacity: directRouteActions.Length ); - - for ( var i = 0; i < directRouteActions.Length; i++ ) - { - candidates.Add( new CandidateAction( directRouteActions[i], order, precedence ) ); - } - - return [.. candidates]; } } \ No newline at end of file diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/TupleExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/TupleExtensions.cs index 991f3e6c..023f9d40 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/TupleExtensions.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/TupleExtensions.cs @@ -4,16 +4,22 @@ namespace Asp.Versioning; internal static class TupleExtensions { - internal static void Deconstruct( this Tuple tuple, out T1 item1, out T2 item2 ) + extension( Tuple tuple ) { - item1 = tuple.Item1; - item2 = tuple.Item2; + internal void Deconstruct( out T1 item1, out T2 item2 ) + { + item1 = tuple.Item1; + item2 = tuple.Item2; + } } - internal static void Deconstruct( this Tuple tuple, out T1 item1, out T2 item2, out T3 item3 ) + extension( Tuple tuple ) { - item1 = tuple.Item1; - item2 = tuple.Item2; - item3 = tuple.Item3; + internal void Deconstruct( out T1 item1, out T2 item2, out T3 item3 ) + { + item1 = tuple.Item1; + item2 = tuple.Item2; + item3 = tuple.Item3; + } } } \ 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 5cfa1e1e..8cd1f521 100644 --- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/UrlSegmentApiVersionReader.cs +++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/UrlSegmentApiVersionReader.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. namespace Asp.Versioning; + using System.Web.Http; /// @@ -11,15 +12,15 @@ public partial class UrlSegmentApiVersionReader /// public virtual IReadOnlyList Read( HttpRequestMessage request ) { - ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull( request ); if ( reentrant ) { - return Array.Empty(); + return []; } reentrant = true; - var versions = request.ApiVersionProperties().RawRequestedApiVersions; + var versions = request.ApiVersionProperties.RawRequestedApiVersions; reentrant = false; return versions; diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/ControllerTypeCollection.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/ControllerTypeCollection.cs index f7e76cee..2039b27d 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/ControllerTypeCollection.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/ControllerTypeCollection.cs @@ -9,7 +9,7 @@ public class ControllerTypeCollection : Collection, IHttpControllerTypeRes { public ControllerTypeCollection() { } - public ControllerTypeCollection( params Type[] controllerTypes ) : base( controllerTypes.ToList() ) { } + public ControllerTypeCollection( params Type[] controllerTypes ) : base( [.. controllerTypes] ) { } public ICollection GetControllerTypes( IAssembliesResolver assembliesResolver ) => this; } \ No newline at end of file diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/TestConfigurations.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/TestConfigurations.cs index af6e2a43..0fe09d9d 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/TestConfigurations.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/TestConfigurations.cs @@ -4,22 +4,41 @@ namespace Asp.Versioning.ApiExplorer; using Asp.Versioning.Conventions; using Asp.Versioning.Simulators; -using System.Collections; using System.Net.Http; using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Http.Tracing; +using static Asp.Versioning.ApiExplorer.TestConfigurations; using static System.Web.Http.RouteParameter; -public class TestConfigurations : IEnumerable +public class TestConfigurations : TheoryData { - public IEnumerator GetEnumerator() + public enum Kind { - yield return new object[] { NewConventionRouteConfiguration() }; - yield return new object[] { NewDirectRouteConfiguration() }; + /// + /// Indicates convention-based routing. + /// + ConventionBased, + + /// + /// Indicates direct routing. + /// + DirectRouteBased, + } + + public TestConfigurations() + { + Add( Kind.ConventionBased ); + Add( Kind.DirectRouteBased ); } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public static HttpConfiguration Get( Kind kind ) => + kind switch + { + Kind.ConventionBased => NewConventionRouteConfiguration(), + Kind.DirectRouteBased => NewDirectRouteConfiguration(), + _ => throw new ArgumentOutOfRangeException( nameof( kind ) ), + }; private static HttpConfiguration NewConventionRouteConfiguration() { 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 42c21028..f9aa3004 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 @@ -235,9 +235,10 @@ public void api_descriptions_should_recognize_mixedX2Dcase_parameters() [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_descriptions_should_collate_expected_versions( HttpConfiguration configuration ) + public void api_descriptions_should_collate_expected_versions( TestConfigurations.Kind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var apiExplorer = new VersionedApiExplorer( configuration ); // act @@ -254,9 +255,10 @@ public void api_descriptions_should_collate_expected_versions( HttpConfiguration [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_descriptions_should_group_versioned_controllers( HttpConfiguration configuration ) + public void api_descriptions_should_group_versioned_controllers( TestConfigurations.Kind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var assembliesResolver = configuration.Services.GetAssembliesResolver(); var controllerTypes = configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes( assembliesResolver ); var apiExplorer = new VersionedApiExplorer( configuration ); @@ -274,9 +276,10 @@ public void api_descriptions_should_group_versioned_controllers( HttpConfigurati [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_descriptions_should_flatten_versioned_controllers( HttpConfiguration configuration ) + public void api_descriptions_should_flatten_versioned_controllers( TestConfigurations.Kind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var assembliesResolver = configuration.Services.GetAssembliesResolver(); var controllerTypes = configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes( assembliesResolver ); var apiExplorer = new VersionedApiExplorer( configuration ); @@ -294,9 +297,10 @@ public void api_descriptions_should_flatten_versioned_controllers( HttpConfigura [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_description_group_should_explore_v1_actions( HttpConfiguration configuration ) + public void api_description_group_should_explore_v1_actions( TestConfigurations.Kind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var apiExplorer = new VersionedApiExplorer( configuration ); var apiVersion = new ApiVersion( 1, 0 ); var descriptionGroup = apiExplorer.ApiDescriptions[apiVersion]; @@ -318,9 +322,10 @@ public void api_description_group_should_explore_v1_actions( HttpConfiguration c [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_description_group_should_explore_v2_actions( HttpConfiguration configuration ) + public void api_description_group_should_explore_v2_actions( TestConfigurations.Kind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var apiExplorer = new VersionedApiExplorer( configuration ); var apiVersion = new ApiVersion( 2, 0 ); var descriptionGroup = apiExplorer.ApiDescriptions[apiVersion]; @@ -330,8 +335,7 @@ public void api_description_group_should_explore_v2_actions( HttpConfiguration c // assert descriptions.Should().BeEquivalentTo( - new[] - { + [ new { ID = "GETValues", @@ -346,15 +350,16 @@ public void api_description_group_should_explore_v2_actions( HttpConfiguration c RelativePath = "Values/{id}", Version = apiVersion, }, - }, + ], options => options.ExcludingMissingMembers() ); } [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_description_group_should_explore_v3_actions( HttpConfiguration configuration ) + public void api_description_group_should_explore_v3_actions( TestConfigurations.Kind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var apiExplorer = new VersionedApiExplorer( configuration ); var apiVersion = new ApiVersion( 3, 0 ); var descriptionGroup = apiExplorer.ApiDescriptions[apiVersion]; @@ -364,8 +369,7 @@ public void api_description_group_should_explore_v3_actions( HttpConfiguration c // assert descriptions.Should().BeEquivalentTo( - new[] - { + [ new { ID = "GETValues", @@ -390,15 +394,16 @@ public void api_description_group_should_explore_v3_actions( HttpConfiguration c Version = apiVersion, ActionDescriptor = new { ActionName = "Post" }, }, - }, + ], options => options.ExcludingMissingMembers() ); } [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_description_group_should_explore_v3_beta_actions( HttpConfiguration configuration ) + public void api_description_group_should_explore_v3_beta_actions( TestConfigurations.Kind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var apiExplorer = new VersionedApiExplorer( configuration ); var apiVersion = new ApiVersion( 3, 0, "beta" ); var descriptionGroup = apiExplorer.ApiDescriptions[apiVersion]; @@ -409,8 +414,7 @@ public void api_description_group_should_explore_v3_beta_actions( HttpConfigurat // assert descriptionGroup.IsDeprecated.Should().BeTrue(); descriptions.Should().BeEquivalentTo( - new[] - { + [ new { ID = "GETValues", @@ -425,15 +429,16 @@ public void api_description_group_should_explore_v3_beta_actions( HttpConfigurat RelativePath = "Values/{id}", Version = apiVersion, }, - }, + ], options => options.ExcludingMissingMembers() ); } [Theory] [ClassData( typeof( TestConfigurations ) )] - public void api_description_group_should_explore_v4_actions( HttpConfiguration configuration ) + public void api_description_group_should_explore_v4_actions( TestConfigurations.Kind kind ) { // arrange + var configuration = TestConfigurations.Get( kind ); var apiExplorer = new VersionedApiExplorer( configuration ); var apiVersion = new ApiVersion( 4, 0 ); var descriptionGroup = apiExplorer.ApiDescriptions[apiVersion]; @@ -443,8 +448,7 @@ public void api_description_group_should_explore_v4_actions( HttpConfiguration c // assert descriptions.Should().BeEquivalentTo( - new[] - { + [ new { ID = "GETValues", @@ -473,7 +477,7 @@ public void api_description_group_should_explore_v4_actions( HttpConfiguration c RelativePath = "Values/{id}", Version = apiVersion, }, - }, + ], options => options.ExcludingMissingMembers() ); } diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Asp.Versioning.WebApi.ApiExplorer.Tests.csproj b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Asp.Versioning.WebApi.ApiExplorer.Tests.csproj index 30a6bea6..c0a119c3 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Asp.Versioning.WebApi.ApiExplorer.Tests.csproj +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Asp.Versioning.WebApi.ApiExplorer.Tests.csproj @@ -1,7 +1,7 @@ - + - net452;net472 + net472 Asp.Versioning 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 4dd18890..942e2891 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 @@ -9,20 +9,23 @@ namespace Asp.Versioning.Description; internal static class InternalTypeExtensions { - internal static IHttpRoute NewRouteCollectionRoute() + extension( IHttpRoute route ) { - var type = Type.GetType( "System.Web.Http.Routing.RouteCollectionRoute, System.Web.Http", throwOnError: true, ignoreCase: false ); - return (IHttpRoute) Activator.CreateInstance( type ); - } + internal void EnsureInitialized( Func> initializer ) + { + Debug.Assert( route.GetType().Name == "RouteCollectionRoute", "Extension method only intended to support testing RouteCollectionRoute.EnsureInitialized" ); - internal static void EnsureInitialized( this IHttpRoute route, Func> initializer ) - { - Debug.Assert( route.GetType().Name == "RouteCollectionRoute", "Extension method only intended to support testing RouteCollectionRoute.EnsureInitialized" ); + var type = route.GetType(); + var method = type.GetRuntimeMethod( nameof( EnsureInitialized ), [initializer.GetType()] ); - var type = route.GetType(); - var method = type.GetRuntimeMethod( nameof( EnsureInitialized ), [initializer.GetType()] ); + method.Invoke( route, [initializer] ); + } + } - method.Invoke( route, [initializer] ); + internal static IHttpRoute NewRouteCollectionRoute() + { + var type = Type.GetType( "System.Web.Http.Routing.RouteCollectionRoute, System.Web.Http", throwOnError: true, ignoreCase: false ); + return (IHttpRoute) Activator.CreateInstance( type ); } internal static IDirectRouteBuilder NewDirectRouteBuilder( IReadOnlyCollection actions, bool targetIsAction ) diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/System.Web.Http.Description/ApiDescriptionExtensionsTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/System.Web.Http.Description/ApiDescriptionExtensionsTest.cs index 0d4815a4..70eca71b 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/System.Web.Http.Description/ApiDescriptionExtensionsTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/System.Web.Http.Description/ApiDescriptionExtensionsTest.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http.Description; using Asp.Versioning; @@ -14,7 +16,7 @@ public void get_api_version_should_return_null_by_default() var description = new ApiDescription(); // act - var apiVersion = description.GetApiVersion(); + var apiVersion = description.ApiVersion; // assert apiVersion.Should().BeNull(); @@ -28,7 +30,7 @@ public void get_api_version_should_return_property_value() var description = new VersionedApiDescription() { ApiVersion = apiVersion }; // act - var result = description.GetApiVersion(); + var result = description.ApiVersion; // assert result.Should().Be( apiVersion ); @@ -41,7 +43,7 @@ public void is_deprecated_should_return_false_by_default() var description = new ApiDescription(); // act - var deprecated = description.IsDeprecated(); + var deprecated = description.IsDeprecated; // assert deprecated.Should().BeFalse(); @@ -54,7 +56,7 @@ public void is_deprecated_should_return_property_value() var description = new VersionedApiDescription() { IsDeprecated = true }; // act - var deprecated = description.IsDeprecated(); + var deprecated = description.IsDeprecated; // assert deprecated.Should().BeTrue(); @@ -67,7 +69,7 @@ public void get_group_name_should_return_null_by_default() var description = new ApiDescription(); // act - var groupName = description.GetGroupName(); + var groupName = description.GroupName; // assert groupName.Should().BeNull(); @@ -80,7 +82,7 @@ public void get_group_name_should_return_property_value() var description = new VersionedApiDescription() { GroupName = "v1" }; // act - var groupName = description.GetGroupName(); + var groupName = description.GroupName; // assert groupName.Should().Be( "v1" ); diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Asp.Versioning.WebApi.Tests.csproj b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Asp.Versioning.WebApi.Tests.csproj index 6082ea6d..43fe93ed 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Asp.Versioning.WebApi.Tests.csproj +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Asp.Versioning.WebApi.Tests.csproj @@ -1,13 +1,9 @@  - net452;net472 + net472 Asp.Versioning - - - - diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/ApiVersionActionSelectorTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/ApiVersionActionSelectorTest.cs index fad64b4f..132f503b 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/ApiVersionActionSelectorTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/ApiVersionActionSelectorTest.cs @@ -2,7 +2,6 @@ namespace Asp.Versioning.Controllers; -using System.Collections.ObjectModel; using System.Net.Http; using System.Web.Http; using System.Web.Http.Controllers; @@ -12,12 +11,11 @@ public class ApiVersionActionSelectorTest { [Theory] [MemberData( nameof( SelectActionVersionData ) )] - public void select_action_version_should_return_expected_result( - IReadOnlyList candidates, - string version, - HttpActionDescriptor expectedAction ) + public void select_action_version_should_return_expected_result( string version, int index ) { // arrange + var candidates = NewCandidates(); + var expectedAction = candidates[index]; var configuration = new HttpConfiguration(); var request = new HttpRequestMessage( Get, "http://localhost/api/test?api-version=" + version ); var context = new HttpControllerContext() { Request = request }; @@ -33,22 +31,19 @@ public void select_action_version_should_return_expected_result( selectedAction.Should().Be( expectedAction ); } - public static IEnumerable SelectActionVersionData - { - get - { - var candidates = new List() - { - CreateActionDescriptor( "1.0" ), - CreateActionDescriptor( "2.0" ), - CreateActionDescriptor( "3.0" ), - }; + private static HttpActionDescriptor[] NewCandidates() => + [ + CreateActionDescriptor( "1.0" ), + CreateActionDescriptor( "2.0" ), + CreateActionDescriptor( "3.0" ), + ]; - yield return new object[] { candidates, "1.0", candidates[0] }; - yield return new object[] { candidates, "2.0", candidates[1] }; - yield return new object[] { candidates, "3.0", candidates[2] }; - } - } + public static TheoryData SelectActionVersionData => new() + { + { "1.0", 0 }, + { "2.0", 1 }, + { "3.0", 2 }, + }; private static HttpActionDescriptor CreateActionDescriptor( string version ) { @@ -60,10 +55,10 @@ private static HttpActionDescriptor CreateActionDescriptor( string version ) var metadata = new ApiVersionMetadata( ApiVersionModel.Empty, new ApiVersionModel( attribute.Versions[0] ) ); controllerDescriptor.Setup( cd => cd.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); actionDescriptor.Setup( ad => ad.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() { attribute } ); + .Returns( () => [attribute] ); var newActionDescriptor = actionDescriptor.Object; diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/ApiVersionParameterBindingTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/ApiVersionParameterBindingTest.cs index cbdc119f..2b30c674 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/ApiVersionParameterBindingTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/ApiVersionParameterBindingTest.cs @@ -50,7 +50,7 @@ private static HttpActionContext NewActionContext( ApiVersion apiVersion ) var actionContext = new HttpActionContext() { ControllerContext = controllerContext }; request.SetConfiguration( configuration ); - request.ApiVersionProperties().RequestedApiVersion = apiVersion; + request.ApiVersionProperties.RequestedApiVersion = apiVersion; return actionContext; } 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 0b9ba137..16545b0e 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 @@ -4,7 +4,6 @@ namespace Asp.Versioning.Controllers; -using System.Collections.ObjectModel; using System.Net.Http; using System.Web.Http; using System.Web.Http.Controllers; @@ -53,12 +52,12 @@ public void get_custom_attributes_should_aggregate_attributes() var configuration = new HttpConfiguration(); descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() { new( "1.0" ) } ); + .Returns( () => [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( "2.0" ) } ); + .Returns( () => [new( "2.0" )] ); descriptor2.Object.Configuration = configuration; descriptor2.Object.Properties[typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 2, 0 ) ); @@ -82,19 +81,19 @@ public void get_filters_should_aggregate_filters() var descriptor2 = new Mock() { CallBase = true }; var configuration = new HttpConfiguration(); - descriptor1.Setup( d => d.GetFilters() ).Returns( () => new Collection() { filter1 } ); + descriptor1.Setup( d => d.GetFilters() ).Returns( () => [filter1] ); descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor1.Object.Configuration = configuration; descriptor1.Object.Properties[typeof( ApiVersionModel )] = ApiVersionModel.Neutral; - descriptor2.Setup( d => d.GetFilters() ).Returns( () => new Collection() { filter2 } ); + descriptor2.Setup( d => d.GetFilters() ).Returns( () => [filter2] ); descriptor2.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor2.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor2.Object.Configuration = configuration; descriptor2.Object.Properties[typeof( ApiVersionModel )] = ApiVersionModel.Neutral; @@ -104,7 +103,7 @@ public void get_filters_should_aggregate_filters() var filters = group.GetFilters(); // assert - filters.Should().BeEquivalentTo( new[] { filter1, filter2 } ); + filters.Should().BeEquivalentTo( [filter1, filter2] ); } [Fact] @@ -119,7 +118,7 @@ public void create_controller_should_return_expected_instance_when_count_eq_1() var group = new HttpControllerDescriptorGroup( descriptor.Object ); var request = new HttpRequestMessage(); - request.ApiVersionProperties().SelectedController = descriptor.Object; + request.ApiVersionProperties.SelectedController = descriptor.Object; // act var controller = group.CreateController( request ); @@ -140,24 +139,24 @@ public void create_controller_should_return_first_instance_when_version_is_unspe descriptor1.Setup( d => d.CreateController( It.IsAny() ) ).Returns( expected ); descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor1.Object.Configuration = configuration; descriptor1.Object.Properties[typeof( ApiVersionModel )] = ApiVersionModel.Neutral; descriptor2.Setup( d => d.CreateController( It.IsAny() ) ).Returns( controller2 ); descriptor2.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor2.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor2.Object.Configuration = configuration; descriptor2.Object.Properties[typeof( ApiVersionModel )] = ApiVersionModel.Neutral; var group = new HttpControllerDescriptorGroup( descriptor1.Object, descriptor2.Object ); var request = new HttpRequestMessage(); - request.ApiVersionProperties().SelectedController = descriptor1.Object; + request.ApiVersionProperties.SelectedController = descriptor1.Object; // act var controller = group.CreateController( request ); @@ -179,17 +178,17 @@ public void create_controller_should_return_versioned_controller_instance() var descriptor2 = new Mock() { CallBase = true }; descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() { new ApiVersionAttribute( "2.0" ) } ); + .Returns( () => [new ApiVersionAttribute( "2.0" )] ); descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor1.Setup( d => d.CreateController( It.IsAny() ) ).Returns( controller1 ); descriptor1.Object.Configuration = configuration; descriptor1.Object.Properties[typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 2, 0 ) ); descriptor2.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() { new ApiVersionAttribute( "1.0" ) } ); + .Returns( () => [new ApiVersionAttribute( "1.0" )] ); descriptor2.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor2.Setup( d => d.CreateController( It.IsAny() ) ).Returns( expected ); descriptor2.Object.Configuration = configuration; descriptor2.Object.Properties[typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 1, 0 ) ); @@ -197,7 +196,7 @@ public void create_controller_should_return_versioned_controller_instance() var group = new HttpControllerDescriptorGroup( descriptor1.Object, descriptor2.Object ) { Configuration = configuration }; var request = new HttpRequestMessage( HttpMethod.Get, "http://localhost/api/test?api-version=1.0" ); - request.ApiVersionProperties().SelectedController = descriptor2.Object; + request.ApiVersionProperties.SelectedController = descriptor2.Object; // act var controller = group.CreateController( request ); @@ -219,17 +218,17 @@ public void create_controller_should_return_default_instance_when_versioned_cont var descriptor2 = new Mock() { CallBase = true }; descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() { new ApiVersionAttribute( "1.0" ) } ); + .Returns( () => [new ApiVersionAttribute( "1.0" )] ); descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor1.Setup( d => d.CreateController( It.IsAny() ) ).Returns( expected ); 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 ApiVersionAttribute( "2.0" )] ); descriptor2.Setup( d => d.GetCustomAttributes( It.IsAny() ) ) - .Returns( () => new Collection() ); + .Returns( () => [] ); descriptor2.Setup( d => d.CreateController( It.IsAny() ) ).Returns( controller2 ); descriptor2.Object.Configuration = configuration; descriptor2.Object.Properties[typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 2, 0 ) ); @@ -237,7 +236,7 @@ public void create_controller_should_return_default_instance_when_versioned_cont var group = new HttpControllerDescriptorGroup( descriptor1.Object, descriptor2.Object ) { Configuration = configuration }; var request = new HttpRequestMessage( HttpMethod.Get, "http://localhost/api/test?api-version=3.0" ); - request.ApiVersionProperties().SelectedController = descriptor1.Object; + request.ApiVersionProperties.SelectedController = descriptor1.Object; // act var controller = group.CreateController( request ); 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 f99b03cf..7779fe27 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 @@ -23,7 +23,7 @@ public void apply_to_should_assign_empty_model_without_api_versions_from_mapped_ actionBuilder.ApplyTo( actionDescriptor.Object ); // assert - actionDescriptor.Object.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.Object.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, @@ -50,7 +50,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_ actionBuilder.ApplyTo( actionDescriptor.Object ); // assert - actionDescriptor.Object.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.Object.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, @@ -78,7 +78,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_ actionBuilder.ApplyTo( actionDescriptor ); // assert - actionDescriptor.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, 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 e7626764..9d3efded 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 @@ -23,7 +23,7 @@ public void apply_to_should_assign_empty_model_without_api_versions_from_mapped_ actionBuilder.ApplyTo( actionDescriptor.Object ); // assert - actionDescriptor.Object.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.Object.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, @@ -50,7 +50,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_ actionBuilder.ApplyTo( actionDescriptor.Object ); // assert - actionDescriptor.Object.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.Object.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, @@ -78,7 +78,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_ actionBuilder.ApplyTo( actionDescriptor ); // assert - actionDescriptor.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ApiVersionConventionBuilderTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ApiVersionConventionBuilderTest.cs index 2f553a2f..d9aac1b5 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ApiVersionConventionBuilderTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ApiVersionConventionBuilderTest.cs @@ -25,6 +25,6 @@ public void apply_should_apply_configured_conventions() conventionBuilder.ApplyTo( controllerDescriptor ); // assert - actionDescriptor.GetApiVersionMetadata().MappingTo( new ApiVersion( 2, 0 ) ).Should().Be( Implicit ); + actionDescriptor.ApiVersionMetadata.MappingTo( new ApiVersion( 2, 0 ) ).Should().Be( Implicit ); } } \ No newline at end of file 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 82164eb5..1290582a 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 @@ -34,7 +34,7 @@ public void apply_to_should_assign_conventions_to_controller() controllerBuilder.ApplyTo( controllerDescriptor ); // assert - actionDescriptor.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, @@ -70,7 +70,7 @@ public void apply_to_should_assign_empty_conventions_to_api_version_neutral_cont controllerBuilder.ApplyTo( controllerDescriptor ); // assert - actionDescriptor.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = true, @@ -88,7 +88,7 @@ public void apply_to_should_assign_model_to_controller_from_conventions_and_attr var configuration = new HttpConfiguration(); var mock = new Mock() { CallBase = true }; var controllerDescriptor = mock.Object; - var attributes = new Collection( typeof( DecoratedController ).GetCustomAttributes().OfType().ToList() ); + var attributes = new Collection( [.. typeof( DecoratedController ).GetCustomAttributes().OfType()] ); var controllerBuilder = default( IControllerConventionBuilder ); mock.Setup( cd => cd.GetCustomAttributes() ).Returns( attributes ); @@ -104,7 +104,7 @@ public void apply_to_should_assign_model_to_controller_from_conventions_and_attr controllerBuilder.ApplyTo( controllerDescriptor ); // assert - actionDescriptor.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, 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 eedc943c..4a8dd8d9 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 @@ -35,7 +35,7 @@ public void apply_to_should_assign_conventions_to_controller() controllerBuilder.ApplyTo( controllerDescriptor ); // assert - actionDescriptor.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, @@ -71,7 +71,7 @@ public void apply_to_should_assign_empty_conventions_to_api_version_neutral_cont controllerBuilder.ApplyTo( controllerDescriptor ); // assert - actionDescriptor.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = true, @@ -89,7 +89,7 @@ public void apply_to_should_assign_model_to_controller_from_conventions_and_attr var configuration = new HttpConfiguration(); var mock = new Mock() { CallBase = true }; var controllerDescriptor = mock.Object; - var attributes = new Collection( typeof( DecoratedController ).GetCustomAttributes().OfType().ToList() ); + var attributes = new Collection( [.. typeof( DecoratedController ).GetCustomAttributes().OfType()] ); var controllerBuilder = default( IControllerConventionBuilder ); mock.Setup( cd => cd.GetCustomAttributes() ).Returns( attributes ); @@ -105,7 +105,7 @@ public void apply_to_should_assign_model_to_controller_from_conventions_and_attr controllerBuilder.ApplyTo( controllerDescriptor ); // assert - actionDescriptor.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + actionDescriptor.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/VersionByNamespaceConventionTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/VersionByNamespaceConventionTest.cs index 6ea26613..dac06bef 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/VersionByNamespaceConventionTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/VersionByNamespaceConventionTest.cs @@ -101,6 +101,6 @@ internal TestHttpControllerDescriptor( Type controllerType, IReadOnlyList GetCustomAttributes( bool inherit ) => new( attributes.OfType().ToArray() ); + public override Collection GetCustomAttributes( bool inherit ) => new( [.. attributes.OfType()] ); } } \ No newline at end of file diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/CurrentImplementationApiVersionSelectorTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/CurrentImplementationApiVersionSelectorTest.cs index 31d39014..6ceb94c5 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/CurrentImplementationApiVersionSelectorTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/CurrentImplementationApiVersionSelectorTest.cs @@ -8,7 +8,7 @@ public class CurrentImplementationApiVersionSelectorTest { [Theory] [ClassData( typeof( MaxSelectVersionData ) )] - public void select_version_should_return_max_api_version( IEnumerable supportedVersions, IEnumerable deprecatedVersions, ApiVersion expectedVersion ) + public void select_version_should_return_max_api_version( ApiVersion[] supportedVersions, ApiVersion[] deprecatedVersions, ApiVersion expectedVersion ) { // arrange var options = new ApiVersioningOptions() { DefaultApiVersion = new ApiVersion( 42, 0 ) }; diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/DefaultApiVersionReporterTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/DefaultApiVersionReporterTest.cs index 130a4e7d..81f82851 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/DefaultApiVersionReporterTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/DefaultApiVersionReporterTest.cs @@ -20,21 +20,21 @@ public void report_should_add_expected_headers() var request = new HttpRequestMessage(); var response = new HttpResponseMessage( OK ) { RequestMessage = request }; var apiModel = new ApiVersionModel( - declaredVersions: new ApiVersion[] { new( 0.9 ), new( 1.0 ), new( 2.0 ) }, - supportedVersions: new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - deprecatedVersions: new[] { new ApiVersion( 0.9 ) }, - advertisedVersions: Enumerable.Empty(), - deprecatedAdvertisedVersions: Enumerable.Empty() ); + declaredVersions: [new( 0.9 ), new( 1.0 ), new( 2.0 )], + supportedVersions: [new( 1.0 ), new( 2.0 )], + deprecatedVersions: [new ApiVersion( 0.9 )], + advertisedVersions: [], + deprecatedAdvertisedVersions: [] ); var endpointModel = new ApiVersionModel( - declaredVersions: new ApiVersion[] { new( 1.0 ) }, - supportedVersions: new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - deprecatedVersions: new[] { new ApiVersion( 0.9 ) }, - advertisedVersions: Enumerable.Empty(), - deprecatedAdvertisedVersions: Enumerable.Empty() ); + declaredVersions: [new( 1.0 )], + supportedVersions: [new( 1.0 ), new( 2.0 )], + deprecatedVersions: [new ApiVersion( 0.9 )], + advertisedVersions: [], + deprecatedAdvertisedVersions: [] ); var metadata = new ApiVersionMetadata( apiModel, endpointModel, "Test" ); request.SetConfiguration( configuration ); - request.ApiVersionProperties().RequestedApiVersion = new ApiVersion( 1.0 ); + request.ApiVersionProperties.RequestedApiVersion = new ApiVersion( 1.0 ); request.Properties["MS_HttpActionDescriptor"] = new ReflectedHttpActionDescriptor( new HttpControllerDescriptor( configuration, "Test", typeof( TestController ) ), 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 e4c523fb..2bce5d4e 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 @@ -24,11 +24,17 @@ namespace Asp.Versioning.Dispatcher; public partial class ApiVersionControllerSelectorTest { - [Theory] - [MemberData( nameof( ControllerNameData ) )] - public void get_controller_name_should_return_expected_value( HttpRequestMessage request, string expected ) + [Fact] + public void get_controller_name_should_return_expected_value() { // arrange + var expected = "Test"; + var request = new HttpRequestMessage(); + var routeData = new HttpRouteData( new HttpRoute() ); + + routeData.Values.Add( "controller", expected ); + request.SetRouteData( routeData ); + var configuration = new HttpConfiguration(); var options = new ApiVersioningOptions(); var selector = new ApiVersionControllerSelector( configuration, options ); @@ -190,7 +196,7 @@ public async Task select_controller_should_return_400_for_unmatchedX2C_attribute // act var response = selectController.Should().Throw().Subject.Single().Response; - var content = await response.Content.ReadAsProblemDetailsAsync(); + var content = await response.Content.ReadAsProblemDetailsAsync( TestContext.Current.CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -237,7 +243,7 @@ public async Task select_controller_should_return_400_for_attributeX2Dbased_cont // act var response = selectController.Should().Throw().Subject.Single().Response; - var content = await response.Content.ReadAsProblemDetailsAsync(); + var content = await response.Content.ReadAsProblemDetailsAsync( TestContext.Current.CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -283,7 +289,7 @@ public async Task select_controller_should_return_400_for_unmatchedX2C_conventio // act var response = selectController.Should().Throw().Subject.Single().Response; - var content = await response.Content.ReadAsProblemDetailsAsync(); + var content = await response.Content.ReadAsProblemDetailsAsync( TestContext.Current.CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -331,7 +337,7 @@ public async Task select_controller_should_return_400_for_conventionX2Dbased_con // act var response = selectController.Should().Throw().Subject.Single().Response; - var content = await response.Content.ReadAsProblemDetailsAsync(); + var content = await response.Content.ReadAsProblemDetailsAsync( TestContext.Current.CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -379,7 +385,7 @@ public async Task select_controller_should_return_404_for_unmatched_controller( // act var response = selectController.Should().Throw().Subject.Single().Response; - var content = await response.Content.ReadAsAsync(); + var content = await response.Content.ReadAsAsync( TestContext.Current.CancellationToken ); // assert response.StatusCode.Should().Be( NotFound ); @@ -862,7 +868,7 @@ public void select_controller_should_assume_current_version_for_attributeX2Dbase // assert controller.ControllerType.Should().Be( controllerType ); - request.GetRequestedApiVersion().Should().Be( currentVersion ); + request.RequestedApiVersion.Should().Be( currentVersion ); } [Fact] @@ -904,7 +910,7 @@ public void select_controller_should_assume_current_version_for_conventionX2Dbas // assert controller.ControllerType.Should().Be( controllerType ); - request.GetRequestedApiVersion().Should().Be( currentVersion ); + request.RequestedApiVersion.Should().Be( currentVersion ); } [Theory] @@ -917,7 +923,7 @@ public void select_controller_should_assume_current_version_for_conventionX2Dbas public void select_controller_should_return_correct_controller_for_versioned_url( string versionSegment, Type controllerType, string actionName, string declaredVersionsValue, ApiVersionMapping mapping ) { // arrange - var declared = string.IsNullOrEmpty( declaredVersionsValue ) ? Array.Empty() : declaredVersionsValue.Split( ',' ).Select( v => ApiVersionParser.Default.Parse( v ) ); + var declared = string.IsNullOrEmpty( declaredVersionsValue ) ? [] : declaredVersionsValue.Split( ',' ).Select( v => ApiVersionParser.Default.Parse( v ) ); var supported = new[] { new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0 ), new ApiVersion( 5, 0 ) }; var deprecated = new[] { new ApiVersion( 4, 0 ) }; var implemented = supported.Union( deprecated ).OrderBy( v => v ).ToArray(); @@ -951,7 +957,7 @@ public void select_controller_should_return_correct_controller_for_versioned_url // assert controller.ControllerType.Should().Be( controllerType ); action.ActionName.Should().Be( actionName ); - action.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + action.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, @@ -960,7 +966,7 @@ public void select_controller_should_return_correct_controller_for_versioned_url SupportedApiVersions = supported, DeprecatedApiVersions = deprecated, } ); - action.GetApiVersionMetadata().MappingTo( request.ApiVersionProperties().RequestedApiVersion ).Should().Be( mapping ); + action.ApiVersionMetadata.MappingTo( request.ApiVersionProperties.RequestedApiVersion ).Should().Be( mapping ); } [Fact] @@ -1040,7 +1046,7 @@ public async Task select_controller_should_resolve_controller_with_api_versionX2 var server = new HttpServer( configuration ); var client = new HttpClient( server ); - var response = await client.SendAsync( request ); + var response = await client.SendAsync( request, TestContext.Current.CancellationToken ); response.StatusCode.Should().Be( OK ); } @@ -1158,7 +1164,7 @@ public void select_controller_should_report_correct_api_versions_using_conventio var action = configuration.Services.GetActionSelector().SelectAction( context ); // assert - action.GetApiVersionMetadata().Map( Explicit ).Should().BeEquivalentTo( + action.ApiVersionMetadata.Map( Explicit ).Should().BeEquivalentTo( new { IsApiVersionNeutral = false, @@ -1169,22 +1175,6 @@ public void select_controller_should_report_correct_api_versions_using_conventio } ); } - public static IEnumerable ControllerNameData - { - get - { - yield return new object[] { new HttpRequestMessage(), null }; - - var request = new HttpRequestMessage(); - var routeData = new HttpRouteData( new HttpRoute() ); - - routeData.Values.Add( "controller", "Test" ); - request.SetRouteData( routeData ); - - yield return new object[] { request, "Test" }; - } - } - private static HttpConfiguration AttributeRoutingEnabledConfiguration { get diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/HeaderApiVersionReaderTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/HeaderApiVersionReaderTest.cs index de592aa9..28b76925 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/HeaderApiVersionReaderTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/HeaderApiVersionReaderTest.cs @@ -32,7 +32,7 @@ public void read_should_return_ambiguous_api_versions() var request = new HttpRequestMessage(); var reader = new HeaderApiVersionReader( "api-version" ); - request.Headers.TryAddWithoutValidation( "api-version", new[] { "1.0", "2.0" } ); + request.Headers.TryAddWithoutValidation( "api-version", ["1.0", "2.0"] ); // act var versions = reader.Read( request ); diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/HttpContentExtensions.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/HttpContentExtensions.cs index a06403bf..2c0b0337 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/HttpContentExtensions.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/HttpContentExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Net.Http; using Asp.Versioning; @@ -11,49 +13,45 @@ internal static class HttpContentExtensions { SupportedMediaTypes = { new( ProblemDetailsDefaults.MediaType.Json ) }, }; - private static readonly IEnumerable MediaTypeFormatters = new[] { ProblemDetailsMediaTypeFormatter }; + private static readonly IEnumerable MediaTypeFormatters = [ProblemDetailsMediaTypeFormatter]; - public static Task ReadAsProblemDetailsAsync( - this HttpContent content, - CancellationToken cancellationToken = default ) => - content.SimumateOverTheWireAsync( cancellationToken ); + extension( HttpContent content ) + { + public Task ReadAsProblemDetailsAsync( CancellationToken cancellationToken = default ) => + content.SimumateOverTheWireAsync( cancellationToken ); #pragma warning disable IDE0060 // Remove unused parameter - public static Task ReadAsExampleAsync( - this HttpContent content, - T example, - CancellationToken cancellationToken = default ) => - content.SimumateOverTheWireAsync( cancellationToken ); + public Task ReadAsExampleAsync( T example, CancellationToken cancellationToken = default ) => + content.SimumateOverTheWireAsync( cancellationToken ); #pragma warning restore IDE0060 // Remove unused parameter - private static async Task SimumateOverTheWireAsync( - this HttpContent content, - CancellationToken cancellationToken = default ) - { - if ( content is not ObjectContent server ) - { - return await content.ReadAsAsync( MediaTypeFormatters, cancellationToken ).ConfigureAwait( false ); - } - - using var stream = new MemoryStream(); - - await server.Formatter.WriteToStreamAsync( - server.ObjectType, - server.Value, - stream, - content, - Mock.Of(), - cancellationToken ).ConfigureAwait( false ); - await stream.FlushAsync( cancellationToken ).ConfigureAwait( false ); - stream.Position = 0L; - - using var client = new StreamContent( stream ); - - foreach ( var header in content.Headers ) + private async Task SimumateOverTheWireAsync( CancellationToken cancellationToken = default ) { - client.Headers.TryAddWithoutValidation( header.Key, header.Value ); + if ( content is not ObjectContent server ) + { + return await content.ReadAsAsync( MediaTypeFormatters, cancellationToken ).ConfigureAwait( false ); + } + + using var stream = new MemoryStream(); + + await server.Formatter.WriteToStreamAsync( + server.ObjectType, + server.Value, + stream, + content, + Mock.Of(), + cancellationToken ).ConfigureAwait( false ); + await stream.FlushAsync( cancellationToken ).ConfigureAwait( false ); + stream.Position = 0L; + + using var client = new StreamContent( stream ); + + foreach ( var header in content.Headers ) + { + client.Headers.TryAddWithoutValidation( header.Key, header.Value ); + } + + return await client.ReadAsAsync( MediaTypeFormatters, cancellationToken ).ConfigureAwait( false ); } - - return await client.ReadAsAsync( MediaTypeFormatters, cancellationToken ).ConfigureAwait( false ); } } \ No newline at end of file diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/LowestImplementedApiVersionSelectorTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/LowestImplementedApiVersionSelectorTest.cs index 69ef1ef6..e3e323a3 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/LowestImplementedApiVersionSelectorTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/LowestImplementedApiVersionSelectorTest.cs @@ -8,7 +8,7 @@ public class LowestImplementedApiVersionSelectorTest { [Theory] [ClassData( typeof( MinSelectVersionData ) )] - public void select_version_should_return_min_api_version( IEnumerable supportedVersions, IEnumerable deprecatedVersions, ApiVersion expectedVersion ) + public void select_version_should_return_min_api_version( ApiVersion[] supportedVersions, ApiVersion[] deprecatedVersions, ApiVersion expectedVersion ) { // arrange var options = new ApiVersioningOptions() { DefaultApiVersion = new ApiVersion( 42, 0 ) }; 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 499d07fe..a590d839 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderBuilderTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderBuilderTest.cs @@ -80,7 +80,7 @@ public void read_should_retrieve_version_from_accept_with_quality( string[] medi var reader = new MediaTypeApiVersionReaderBuilder() .Parameter( "v" ) .Parameter( "api.ver" ) - .Select( ( request, versions ) => versions.Count == 0 ? versions : new[] { versions[versions.Count - 1] } ) + .Select( ( request, versions ) => versions.Count == 0 ? versions : [versions[versions.Count - 1]] ) .Build(); var request = new HttpRequestMessage( Get, "http://tempuri.org" ); 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 ec30300a..56c11cd9 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/ReportApiVersionsAttributeTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/ReportApiVersionsAttributeTest.cs @@ -42,14 +42,14 @@ public void on_action_executed_should_add_version_headers() controllerDescriptor.Setup( cd => cd.GetCustomAttributes( It.IsAny() ) ).Returns( attributes ); actionDescriptor.Properties[typeof( ApiVersionMetadata )] = new ApiVersionMetadata( new ApiVersionModel( - declaredVersions: new ApiVersion[] { new( 0, 5 ), new( 1, 0 ), new( 2, 0 ) }, - supportedVersions: new ApiVersion[] { new( 1, 0 ), new( 2, 0 ) }, - deprecatedVersions: new ApiVersion[] { new( 0, 5 ), new( 1, 0 ), new( 2, 0 ) }, + declaredVersions: [new( 0, 5 ), new( 1, 0 ), new( 2, 0 )], + supportedVersions: [new( 1, 0 ), new( 2, 0 )], + deprecatedVersions: [new( 0, 5 ), new( 1, 0 ), new( 2, 0 )], advertisedVersions: Empty(), deprecatedAdvertisedVersions: Empty() ), new ApiVersionModel( - supportedVersions: new[] { new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ) }, - deprecatedVersions: new[] { new ApiVersion( 0, 5 ) }, + supportedVersions: [new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 )], + deprecatedVersions: [new ApiVersion( 0, 5 )], advertisedVersions: Empty(), deprecatedAdvertisedVersions: Empty() ) ); diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Simulators/Conventions2Controller.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Simulators/Conventions2Controller.cs index 78b30a4f..8863b16b 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Simulators/Conventions2Controller.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Simulators/Conventions2Controller.cs @@ -9,8 +9,8 @@ namespace Asp.Versioning.Simulators; public sealed class Conventions2Controller : ApiController { [Route] - public Task Get() => Task.FromResult( Ok( $"Test ({Request.GetRequestedApiVersion()})" ) ); + public Task Get() => Task.FromResult( Ok( $"Test ({Request.RequestedApiVersion})" ) ); [Route( "{id:int}" )] - public Task Get( int id ) => Task.FromResult( Ok( $"Test {id} ({Request.GetRequestedApiVersion()})" ) ); + public Task Get( int id ) => Task.FromResult( Ok( $"Test {id} ({Request.RequestedApiVersion})" ) ); } \ No newline at end of file diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Net.Http/HttpRequestMessageExtensionsTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Net.Http/HttpRequestMessageExtensionsTest.cs index d7d74d54..abe33c0a 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Net.Http/HttpRequestMessageExtensionsTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Net.Http/HttpRequestMessageExtensionsTest.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Net.Http; using Asp.Versioning; @@ -22,7 +24,7 @@ public void get_requested_api_version_should_return_null_when_query_parameter_is request.SetConfiguration( configuration ); // act - var version = request.GetRequestedApiVersion(); + var version = request.RequestedApiVersion; // assert version.Should().BeNull(); @@ -53,7 +55,7 @@ public void get_requested_api_version_should_return_null_when_header_is_nullX2C_ } // act - var version = request.GetRequestedApiVersion(); + var version = request.RequestedApiVersion; // assert version.Should().BeNull(); @@ -71,11 +73,11 @@ public void get_requested_api_version_should_return_expected_value_from_query_pa request.SetConfiguration( configuration ); // act - var version = request.GetRequestedApiVersion(); + var version = request.RequestedApiVersion; // assert version.Should().Be( requestedVersion ); - request.ApiVersionProperties().RequestedApiVersion.Should().Be( requestedVersion ); + request.ApiVersionProperties.RequestedApiVersion.Should().Be( requestedVersion ); } [Theory] @@ -96,10 +98,10 @@ public void get_requested_api_version_should_return_expected_value_from_header( request.Headers.Add( headerName, requestedVersion.ToString() ); // act - var version = request.GetRequestedApiVersion(); + var version = request.RequestedApiVersion; // assert version.Should().Be( requestedVersion ); - request.ApiVersionProperties().RequestedApiVersion.Should().Be( requestedVersion ); + request.ApiVersionProperties.RequestedApiVersion.Should().Be( requestedVersion ); } } \ No newline at end of file diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpActionDescriptorExtensionsTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpActionDescriptorExtensionsTest.cs index 08c3c5ea..61216184 100644 --- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpActionDescriptorExtensionsTest.cs +++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpActionDescriptorExtensionsTest.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning; @@ -20,7 +22,7 @@ public void get_api_version_metadata_should_return_new_instance_for_action_descr actionDescriptor.Properties.Clear(); // act - var model = actionDescriptor.GetApiVersionMetadata(); + var model = actionDescriptor.ApiVersionMetadata; // assert model.Should().NotBeNull(); @@ -39,7 +41,7 @@ public void get_api_version_metadata_should_return_existing_instance_for_action_ actionDescriptor.Properties[typeof( ApiVersionMetadata )] = new ApiVersionMetadata( ApiVersionModel.Empty, endpointModel ); // act - var model = actionDescriptor.GetApiVersionMetadata().Map( Explicit ); + var model = actionDescriptor.ApiVersionMetadata.Map( Explicit ); // assert model.Should().Be( endpointModel ); @@ -54,7 +56,7 @@ public void is_api_neutral_should_return_false_for_undecorated_action_descriptor var actionDescriptor = new Mock( controllerDescriptor ) { CallBase = true }.Object; // act - var versionNeutral = actionDescriptor.GetApiVersionMetadata().IsApiVersionNeutral; + var versionNeutral = actionDescriptor.ApiVersionMetadata.IsApiVersionNeutral; // assert versionNeutral.Should().BeFalse(); @@ -71,7 +73,7 @@ public void is_api_neutral_should_return_true_for_decorated_action_descriptor() actionDescriptor.Properties[typeof( ApiVersionMetadata )] = ApiVersionMetadata.Neutral; // act - var versionNeutral = actionDescriptor.GetApiVersionMetadata().IsApiVersionNeutral; + var versionNeutral = actionDescriptor.ApiVersionMetadata.IsApiVersionNeutral; // assert versionNeutral.Should().BeTrue(); @@ -79,50 +81,35 @@ public void is_api_neutral_should_return_true_for_decorated_action_descriptor() [Theory] [MemberData( nameof( ApiVersionData ) )] - public void get_api_versions_should_return_expected_action_descriptor_results( HttpActionDescriptor actionDescriptor, IEnumerable expectedVersions ) + public void get_api_versions_should_return_expected_action_descriptor_results( Type controllerType, string actionName, ApiVersion[] expectedVersions ) { // arrange + var actionDescriptor = NewAction( controllerType, actionName, expectedVersions ); // act - var declaredVersions = actionDescriptor.GetApiVersionMetadata().Map( Explicit ).DeclaredApiVersions; + var declaredVersions = actionDescriptor.ApiVersionMetadata.Map( Explicit ).DeclaredApiVersions; // assert declaredVersions.Should().BeEquivalentTo( expectedVersions ); } - public static IEnumerable ApiVersionData + private static HttpActionDescriptor NewAction( Type controllerType, string methodName, ApiVersion[] expected ) { - get + var method = controllerType.GetMethod( methodName ); + var metadata = new ApiVersionMetadata( + ApiVersionModel.Empty, + new ApiVersionModel( expected, [], [], [] ) ); + var controllerDescriptor = new HttpControllerDescriptor( new HttpConfiguration(), "Tests", controllerType ); + + return new ReflectedHttpActionDescriptor( controllerDescriptor, method ) { - var runs = new[] - { - Tuple.Create( typeof( TestController ), nameof( TestController.Get ), Array.Empty() ), - Tuple.Create( typeof( TestVersion2Controller ), nameof( TestVersion2Controller.Get3 ), new[] { new ApiVersion( 3, 0 ) } ), - }; - return CreateActionDescriptorData( runs ); - } + Properties = { [typeof( ApiVersionMetadata )] = metadata }, + }; } - private static IEnumerable CreateActionDescriptorData( Tuple[] runs ) + public static TheoryData ApiVersionData => new() { - foreach ( var run in runs ) - { - var controllerType = run.Item1; - var method = controllerType.GetMethod( run.Item2 ); - var expected = run.Item3; - var metadata = new ApiVersionMetadata( - ApiVersionModel.Empty, - new ApiVersionModel( - expected, - Enumerable.Empty(), - Enumerable.Empty(), - Enumerable.Empty() ) ); - var controllerDescriptor = new HttpControllerDescriptor( new HttpConfiguration(), "Tests", controllerType ); - var actionDescriptor = new ReflectedHttpActionDescriptor( controllerDescriptor, method ) - { - Properties = { [typeof( ApiVersionMetadata )] = metadata }, - }; - yield return new object[] { actionDescriptor, expected }; - } - } + { typeof( TestController ), nameof( TestController.Get ), [] }, + { typeof( TestVersion2Controller ), nameof( TestVersion2Controller.Get3 ), [new(3, 0)] }, + }; } \ No newline at end of file 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 22cec40e..482f347e 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 @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Web.Http; using Asp.Versioning; 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 eb271b38..0d42faed 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 @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 #pragma warning disable SA1402 // File may only contain a single type #pragma warning disable SA1403 // File may only contain a single namespace 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 d891b0a2..fd42b0c1 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 @@ -6,8 +6,8 @@ - - + + 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 index ee292d3d..aa66094d 100644 --- 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 @@ -22,8 +22,8 @@ public async Task problem_details_should_be_returned_for_accept_header_with_unsu }; // act - var response = await Client.SendAsync( request ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var response = await Client.SendAsync( request, CancellationToken ); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( UnsupportedMediaType ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a query string.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a query string.cs index 85016516..1bbf06b8 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a query string.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a query string.cs @@ -19,7 +19,7 @@ public async Task then_get_should_return_200( int version ) // act var response = await GetAsync( $"api/values?api-version={version}.0" ); - var result = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync( CancellationToken ); // assert result.Should().Be( "Value " + version ); @@ -47,7 +47,7 @@ public async Task then_get_should_return_400_for_an_unsupported_version() // act var response = await GetAsync( "api/values?api-version=3.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -63,7 +63,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/values" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -79,7 +79,7 @@ public async Task then_get_should_return_400_for_a_malformed_version() // act var response = await GetAsync( "api/values?api-version=abc" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a url segment.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a url segment.cs index f305bae0..756eb16e 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a url segment.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a url segment.cs @@ -19,7 +19,7 @@ public async Task then_get_should_map_to_api_version( string apiVersion, string // act var response = await GetAsync( $"api/{apiVersion}/hello" ); - var result = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync( CancellationToken ); // assert result.Should().Be( expected ); @@ -35,7 +35,7 @@ public async Task hello_world_get_with_key_should_map_to_api_version( string api // act var response = await GetAsync( $"api/{apiVersion}/hello/Hi" ); - var result = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync( CancellationToken ); // assert result.Should().Be( expected ); @@ -50,7 +50,7 @@ public async Task then_post_should_map_to_api_version( string apiVersion ) using var request = new HttpRequestMessage( Post, $"api/{apiVersion}/hello" ); // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); // assert response.IsSuccessStatusCode.Should().BeTrue(); @@ -63,7 +63,7 @@ public async Task then_post_should_report_api_versions() using var request = new HttpRequestMessage( Post, "api/v1/hello" ); // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Should().Equal( "1.0, 2.0" ); 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 57c872e1..b2f09ce5 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/HttpServerFixture.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/HttpServerFixture.cs @@ -12,6 +12,7 @@ namespace Asp.Versioning; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using System.IO; using System.Reflection; using System.Text; @@ -59,7 +60,7 @@ private static string GenerateEndpointDirectedGraph( IServiceProvider services ) { if ( i < count ) { - fragment.Append( Uri.EscapeDataString( graph.Substring( MaxUriLength * i, MaxUriLength ) ) ); + fragment.Append( Uri.EscapeDataString( graph.AsSpan( MaxUriLength * i, MaxUriLength ) ) ); } else { @@ -72,12 +73,17 @@ private static string GenerateEndpointDirectedGraph( IServiceProvider services ) private TestServer CreateServer() { - var builder = new WebHostBuilder() - .ConfigureServices( OnDefaultConfigureServices ) - .Configure( OnBuildApplication ) - .UseContentRoot( GetContentRoot() ); + var host = Host.CreateDefaultBuilder() + .ConfigureWebHostDefaults( + builder => builder.ConfigureServices( OnDefaultConfigureServices ) + .Configure( OnBuildApplication ) + .UseContentRoot( GetContentRoot() ) + .UseTestServer() ) + .Build(); - return new TestServer( builder ); + host.Start(); + + return host.GetTestServer(); } private void OnDefaultConfigureServices( IServiceCollection services ) diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/Controllers/Values2Controller.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/Controllers/Values2Controller.cs index 1f26a093..e05fbd97 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/Controllers/Values2Controller.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/Controllers/Values2Controller.cs @@ -11,11 +11,11 @@ namespace Asp.Versioning.Mvc.UsingAttributes.Controllers; public class Values2Controller : ControllerBase { [HttpGet] - public IActionResult Get() => Ok( new { Controller = nameof( Values2Controller ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get() => Ok( new { Controller = nameof( Values2Controller ), Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet( "{id:int}" )] - public IActionResult Get( int id ) => Ok( new { Controller = nameof( Values2Controller ), Id = id, Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get( int id ) => Ok( new { Controller = nameof( Values2Controller ), Id = id, Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet( "search" )] - public IActionResult Search( string query ) => Ok( new { Controller = nameof( Values2Controller ), Query = query, Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Search( string query ) => Ok( new { Controller = nameof( Values2Controller ), Query = query, Version = HttpContext.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/Controllers/ValuesController.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/Controllers/ValuesController.cs index a8ad4f55..e2ea9175 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/Controllers/ValuesController.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/Controllers/ValuesController.cs @@ -11,11 +11,11 @@ namespace Asp.Versioning.Mvc.UsingAttributes.Controllers; public class ValuesController : ControllerBase { [HttpGet] - public IActionResult Get() => Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get() => Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet( "{id}" )] - public IActionResult Get( string id ) => Ok( new { Controller = nameof( ValuesController ), Id = id, Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get( string id ) => Ok( new { Controller = nameof( ValuesController ), Id = id, Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet( "search" )] - public IActionResult Search( string query ) => Ok( new { Controller = nameof( ValuesController ), Query = query, Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Search( string query ) => Ok( new { Controller = nameof( ValuesController ), Query = query, Version = HttpContext.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a version-neutral UI Controller/when accessing a view using attribute routing.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a version-neutral UI Controller/when accessing a view using attribute routing.cs index e1e63a28..6cb6b2ac 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a version-neutral UI Controller/when accessing a view using attribute routing.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a version-neutral UI Controller/when accessing a view using attribute routing.cs @@ -24,7 +24,7 @@ public async Task then_get_should_return_200( string requestUrl ) }; // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); var mediaType = response.EnsureSuccessStatusCode().Content.Headers.ContentType.MediaType; // assert diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a version-neutral UI Controller/when accessing a view using convention routing.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a version-neutral UI Controller/when accessing a view using convention routing.cs index 37ac8114..eb051879 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a version-neutral UI Controller/when accessing a view using convention routing.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a version-neutral UI Controller/when accessing a view using convention routing.cs @@ -25,7 +25,7 @@ public async Task then_get_should_return_200( string requestUrl ) }; // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); var mediaType = response.EnsureSuccessStatusCode().Content.Headers.ContentType.MediaType; // assert diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when two route templates overlap.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when two route templates overlap.cs index 52daa217..cf084b0c 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when two route templates overlap.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when two route templates overlap.cs @@ -14,11 +14,11 @@ public async Task then_the_higher_precedence_route_should_be_selected_during_the { // arrange var response = await GetAsync( "api/v1/values/42/children" ); - var result1 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result1 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync( CancellationToken ); // act response = await GetAsync( "api/v1/values/42/abc" ); - var result2 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result2 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync( CancellationToken ); // assert result1.Should().Be( "{\"id\":42}" ); @@ -30,11 +30,11 @@ public async Task then_the_higher_precedence_route_should_be_selected_during_the { // arrange var response = await GetAsync( "api/v1/values/42/abc" ); - var result1 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result1 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync( CancellationToken ); // act response = await GetAsync( "api/v1/values/42/children" ); - var result2 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result2 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync( CancellationToken ); // assert result1.Should().Be( "{\"id\":42,\"childId\":\"abc\"}" ); @@ -46,7 +46,7 @@ public async Task then_the_higher_precedence_route_should_result_in_ambiguous_ac { // arrange var response = await GetAsync( "api/v1/values/42/abc" ); - var result1 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result1 = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync( CancellationToken ); // act Func act = async () => await GetAsync( "api/v1/values/42/ambiguous" ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when using a query string and split into two types.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when using a query string and split into two types.cs index 7b1a868c..1140601a 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when using a query string and split into two types.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when using a query string and split into two types.cs @@ -21,7 +21,7 @@ public async Task then_get_should_return_200( string controller, string apiVersi // act var response = await GetAsync( $"api/values?api-version={apiVersion}" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); @@ -36,7 +36,7 @@ public async Task then_get_with_string_id_should_return_200() // act var response = await GetAsync( $"api/values/42?api-version=1.0" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); @@ -51,7 +51,7 @@ public async Task then_get_with_integer_id_should_return_200() // act var response = await GetAsync( $"api/values/42?api-version=2.0" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); @@ -94,7 +94,7 @@ public async Task then_get_should_return_400_for_an_unsupported_version() // act var response = await GetAsync( "api/values?api-version=3.0" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -110,7 +110,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/values" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -128,7 +128,7 @@ public async Task then_action_segment_should_not_be_ambiguous_with_route_paramet // act var response = await GetAsync( $"api/values/search?query=Foo&api-version={apiVersion}" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when using a url segment.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when using a url segment.cs index 8ec3aa05..b0fc19bc 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when using a url segment.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingAttributes/given a versioned Controller/when using a url segment.cs @@ -20,7 +20,7 @@ public async Task then_get_should_return_200( string requestUrl, string controll // act var response = await GetAsync( requestUrl ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); @@ -37,7 +37,7 @@ public async Task then_get_by_id_should_return_200( string requestUrl, string co // act var response = await GetAsync( requestUrl ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); @@ -110,7 +110,7 @@ public async Task then_action_segment_should_not_be_ambiguous_with_route_paramet // act var response = await GetAsync( $"api/v{apiVersion}/helloworld/search?query=Foo" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/HelloWorld2Controller.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/HelloWorld2Controller.cs index ad3f1cdd..7b397b58 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/HelloWorld2Controller.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/HelloWorld2Controller.cs @@ -10,14 +10,14 @@ namespace Asp.Versioning.Mvc.UsingConventions.Controllers; public class HelloWorld2Controller : ControllerBase { [HttpGet] - public IActionResult Get() => Ok( new { Controller = nameof( HelloWorld2Controller ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get() => Ok( new { Controller = nameof( HelloWorld2Controller ), Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet( "{id:int}" )] - public IActionResult Get( int id ) => Ok( new { Controller = nameof( HelloWorld2Controller ), Id = id, Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get( int id ) => Ok( new { Controller = nameof( HelloWorld2Controller ), Id = id, Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet] - public IActionResult GetV3() => Ok( new { Controller = nameof( HelloWorld2Controller ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult GetV3() => Ok( new { Controller = nameof( HelloWorld2Controller ), Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet( "{id:int}" )] - public IActionResult GetV3( int id ) => Ok( new { Controller = nameof( HelloWorld2Controller ), Id = id, Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult GetV3( int id ) => Ok( new { Controller = nameof( HelloWorld2Controller ), Id = id, Version = HttpContext.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/HelloWorldController.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/HelloWorldController.cs index d13bc4ea..7f5cb30d 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/HelloWorldController.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/HelloWorldController.cs @@ -10,8 +10,8 @@ namespace Asp.Versioning.Mvc.UsingConventions.Controllers; public class HelloWorldController : ControllerBase { [HttpGet] - public IActionResult Get() => Ok( new { Controller = nameof( HelloWorldController ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get() => Ok( new { Controller = nameof( HelloWorldController ), Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet( "{id:int}" )] - public IActionResult Get( int id ) => Ok( new { Controller = nameof( HelloWorldController ), Id = id, Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get( int id ) => Ok( new { Controller = nameof( HelloWorldController ), Id = id, Version = HttpContext.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/ValuesController.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/ValuesController.cs index ad539071..83c5022c 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/ValuesController.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/Controllers/ValuesController.cs @@ -10,8 +10,8 @@ namespace Asp.Versioning.Mvc.UsingConventions.Controllers; public class ValuesController : ControllerBase { [HttpGet] - public IActionResult Get() => Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get() => Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet( "{id:int}" )] - public IActionResult Get( int id ) => Ok( new { Controller = nameof( ValuesController ), Id = id, Version = HttpContext.GetRequestedApiVersion().ToString() } ); + public IActionResult Get( int id ) => Ok( new { Controller = nameof( ValuesController ), Id = id, Version = HttpContext.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/given a versioned Controller using conventions/when using a query string and split into two types.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/given a versioned Controller using conventions/when using a query string and split into two types.cs index 081fae3c..cc273b34 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/given a versioned Controller using conventions/when using a query string and split into two types.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/given a versioned Controller using conventions/when using a query string and split into two types.cs @@ -21,7 +21,7 @@ public async Task then_get_should_return_200( string controller, string apiVersi // act var response = await GetAsync( $"api/values?api-version={apiVersion}" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0, 3.0" ); @@ -49,7 +49,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/values" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/given a versioned Controller using conventions/when using a url segment.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/given a versioned Controller using conventions/when using a url segment.cs index 6aa13cd1..cd41fe01 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/given a versioned Controller using conventions/when using a url segment.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingConventions/given a versioned Controller using conventions/when using a url segment.cs @@ -21,7 +21,7 @@ public async Task then_get_should_return_200( string requestUrl, string controll // act var response = await GetAsync( requestUrl ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "2.0, 3.0, 4.0" ); @@ -40,7 +40,7 @@ public async Task then_get_with_id_should_return_200( string requestUrl, string // act var response = await GetAsync( requestUrl ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "2.0, 3.0, 4.0" ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/Controllers/ValuesController.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/Controllers/ValuesController.cs index 989c96c9..50648eba 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/Controllers/ValuesController.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/Controllers/ValuesController.cs @@ -12,9 +12,9 @@ public class ValuesController : ControllerBase { [HttpGet] public IActionResult Get() => - Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); + Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.RequestedApiVersion.ToString() } ); [HttpGet( "{id}" )] public IActionResult Get( string id ) => - Ok( new { Controller = nameof( ValuesController ), Id = id, Version = HttpContext.GetRequestedApiVersion().ToString() } ); + Ok( new { Controller = nameof( ValuesController ), Id = id, Version = HttpContext.RequestedApiVersion.ToString() } ); } \ No newline at end of file diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/given a versioned Controller/when using media type negotiation.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/given a versioned Controller/when using media type negotiation.cs index ac7fda5b..efd0c1b5 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/given a versioned Controller/when using media type negotiation.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/given a versioned Controller/when using media type negotiation.cs @@ -27,9 +27,9 @@ public async Task then_get_should_return_200( string controller, string apiVersi }; // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); var body = response.EnsureSuccessStatusCode().Content; - var content = await body.ReadAsExampleAsync( example ); + var content = await body.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" ); @@ -54,8 +54,8 @@ public async Task then_get_should_return_406_for_an_unsupported_version() }; // act - var response = await Client.SendAsync( request ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var response = await Client.SendAsync( request, CancellationToken ); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( NotAcceptable ); @@ -72,8 +72,8 @@ public async Task then_post_should_return_415_for_an_unsupported_version() }; // act - var response = await Client.SendAsync( request ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var response = await Client.SendAsync( request, CancellationToken ); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( UnsupportedMediaType ); @@ -90,7 +90,7 @@ public async Task then_patch_should_return_415_for_a_supported_version_and_unsup }; // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); // assert response.StatusCode.Should().Be( UnsupportedMediaType ); @@ -107,7 +107,7 @@ public async Task then_get_should_return_current_version_for_an_unspecified_vers // act var response = await GetAsync( requestUrl ); var body = response.EnsureSuccessStatusCode().Content; - var content = await body.ReadAsExampleAsync( example ); + var content = await body.ReadAsExampleAsync( example, CancellationToken ); // assert body.Headers.ContentType.Parameters.Single( p => p.Name == "v" ).Value.Should().Be( apiVersion ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingNamespace/given a versioned Controller per namespace/when using a query string.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingNamespace/given a versioned Controller per namespace/when using a query string.cs index 7a1f29a4..8ac81a04 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingNamespace/given a versioned Controller per namespace/when using a query string.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingNamespace/given a versioned Controller per namespace/when using a query string.cs @@ -5,7 +5,6 @@ namespace given_a_versioned_Controller_per_namespace; using Asp.Versioning; using Asp.Versioning.Mvc.UsingNamespace; using Microsoft.AspNetCore.Mvc; -using System.Net.Http; using static System.Net.HttpStatusCode; using AgreementsControllerV1 = Asp.Versioning.Mvc.UsingNamespace.Controllers.V1.AgreementsController; using AgreementsControllerV2 = Asp.Versioning.Mvc.UsingNamespace.Controllers.V2.AgreementsController; @@ -26,7 +25,7 @@ public async Task then_get_should_return_200( Type controllerType, string apiVer // act var response = await GetAsync( $"api/agreements/42?api-version={apiVersion}" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1, 2, 3" ); @@ -54,7 +53,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/agreements/42" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingNamespace/given a versioned Controller per namespace/when using a url segment.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingNamespace/given a versioned Controller per namespace/when using a url segment.cs index 48da64b7..96152a19 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingNamespace/given a versioned Controller per namespace/when using a url segment.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingNamespace/given a versioned Controller per namespace/when using a url segment.cs @@ -5,7 +5,6 @@ namespace given_a_versioned_Controller_per_namespace; using Asp.Versioning; using Asp.Versioning.Mvc.UsingNamespace; using Microsoft.AspNetCore.Mvc; -using System.Net.Http; using static System.Net.HttpStatusCode; using AgreementsControllerV1 = Asp.Versioning.Mvc.UsingNamespace.Controllers.V1.AgreementsController; using AgreementsControllerV2 = Asp.Versioning.Mvc.UsingNamespace.Controllers.V2.AgreementsController; @@ -26,7 +25,7 @@ public async Task then_get_should_return_200( Type controllerType, string apiVer // act var response = await GetAsync( $"v{apiVersion}/agreements/42" ); - var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var content = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1, 2, 3" ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ControllerBase mixed with OData controllers/when orders is v1.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ControllerBase mixed with OData controllers/when orders is v1.cs index eb500eb6..33591a11 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ControllerBase mixed with OData controllers/when orders is v1.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ControllerBase mixed with OData controllers/when orders is v1.cs @@ -15,10 +15,10 @@ public async Task then_get_should_return_200_for_an_unspecified_version() // act var response = await GetAsync( "api/orders" ); - var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert - orders.Should().BeEquivalentTo( new[] { new { Id = 1, Customer = "Customer v1.0" } } ); + orders.Should().BeEquivalentTo( [new { Id = 1, Customer = "Customer v1.0" }] ); } [Fact] @@ -29,10 +29,10 @@ public async Task then_get_should_return_200() // act var response = await GetAsync( "api/orders?api-version=1.0" ); - var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert - orders.Should().BeEquivalentTo( new[] { new { Id = 1, Customer = "Customer v1.0" } } ); + orders.Should().BeEquivalentTo( [new { Id = 1, Customer = "Customer v1.0" }] ); } [Fact] @@ -43,7 +43,7 @@ public async Task then_get_with_key_should_return_200_for_an_unspecified_version // act var response = await GetAsync( "api/orders/42" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( new { Id = 42, Customer = "Customer v1.0" } ); @@ -57,7 +57,7 @@ public async Task then_get_with_key_should_return_200() // act var response = await GetAsync( "api/orders/42?api-version=1.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( new { Id = 42, Customer = "Customer v1.0" } ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ControllerBase mixed with OData controllers/when orders is v3.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ControllerBase mixed with OData controllers/when orders is v3.cs index 8b89c5db..1ad0b37a 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ControllerBase mixed with OData controllers/when orders is v3.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ControllerBase mixed with OData controllers/when orders is v3.cs @@ -15,10 +15,10 @@ public async Task then_get_should_return_200() // act var response = await GetAsync( "api/orders?api-version=3.0" ); - var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert - orders.Should().BeEquivalentTo( new[] { new { Id = 1, Customer = "Customer v3.0" } } ); + orders.Should().BeEquivalentTo( [new { Id = 1, Customer = "Customer v3.0" }] ); } [Fact] @@ -29,7 +29,7 @@ public async Task then_get_with_key_should_return_200_for_an_unspecified_version // act var response = await GetAsync( "api/orders/42?api-version=3.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( new { Id = 42, Customer = "Customer v3.0" } ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when orders is v2.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when orders is v2.cs index 94655109..a0a9320c 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when orders is v2.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when orders is v2.cs @@ -15,11 +15,11 @@ public async Task then_get_should_return_200() // act var response = await GetAsync( "api/orders?api-version=2.0" ); - var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var orders = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert orders.value.Should().BeEquivalentTo( - new[] { new { id = 1, customer = "Customer v2.0" } }, + [new { id = 1, customer = "Customer v2.0" }], options => options.ExcludingMissingMembers() ); } @@ -31,7 +31,7 @@ public async Task then_get_with_key_should_return_200() // act var response = await GetAsync( "api/orders/42?api-version=2.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v1.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v1.cs index 000b1ab3..e193a47d 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v1.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v1.cs @@ -18,11 +18,11 @@ public async Task then_get_should_return_200( string requestUrl ) // act var response = await GetAsync( requestUrl ); - var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert people.value.Should().BeEquivalentTo( - new[] { new { id = 1, firstName = "Bill", lastName = "Mei" } }, + [new { id = 1, firstName = "Bill", lastName = "Mei" }], options => options.ExcludingMissingMembers() ); } @@ -36,7 +36,7 @@ public async Task then_get_with_key_should_return_200( string requestUrl ) // act var response = await GetAsync( requestUrl ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( @@ -52,7 +52,7 @@ public async Task then_patch_should_return_400() // act var response = await PatchAsync( $"api/people/42?api-version=1.0", person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v2.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v2.cs index 0ad88a10..dffd95bc 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v2.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v2.cs @@ -16,11 +16,11 @@ public async Task then_get_should_return_200() // act var response = await GetAsync( "api/people?api-version=2.0" ); - var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert people.value.Should().BeEquivalentTo( - new[] { new { id = 1, firstName = "Bill", lastName = "Mei", email = "bill.mei@somewhere.com" } }, + [new { id = 1, firstName = "Bill", lastName = "Mei", email = "bill.mei@somewhere.com" }], options => options.ExcludingMissingMembers() ); } @@ -32,7 +32,7 @@ public async Task then_get_with_key_should_return_200() // act var response = await GetAsync( "api/people/42?api-version=2.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v3.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v3.cs index e2f31862..ce097063 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v3.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Advanced/given a versioned ODataController mixed with base controllers/when people is v3.cs @@ -16,11 +16,11 @@ public async Task then_get_should_return_200() // act var response = await GetAsync( "api/people?api-version=3.0" ); - var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var people = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert people.value.Should().BeEquivalentTo( - new[] { new { id = 1, firstName = "Bill", lastName = "Mei", email = "bill.mei@somewhere.com", phone = "555-555-5555" } }, + [new { id = 1, firstName = "Bill", lastName = "Mei", email = "bill.mei@somewhere.com", phone = "555-555-5555" }], options => options.ExcludingMissingMembers() ); } @@ -32,7 +32,7 @@ public async Task then_get_with_key_should_return_200() // act var response = await GetAsync( "api/people/42?api-version=3.0" ); - var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example ); + var order = await response.EnsureSuccessStatusCode().Content.ReadAsExampleAsync( example, CancellationToken ); // assert order.Should().BeEquivalentTo( @@ -48,7 +48,7 @@ public async Task then_patch_should_return_400_if_supported_in_any_version() // act var response = await PatchAsync( $"api/people/42?api-version=3.0", person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string and split into two types.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string and split into two types.cs index e8c60654..8bd04e11 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string and split into two types.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string and split into two types.cs @@ -63,7 +63,7 @@ public async Task then_patch_should_return_400_if_supported_in_any_version( stri // act var response = await PatchAsync( requestUrl, person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -104,7 +104,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/people" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string.cs index 3e52096b..9af1b985 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a query string.cs @@ -44,7 +44,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/orders" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment and split into two types.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment and split into two types.cs index 3676e2d1..833893d8 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment and split into two types.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment and split into two types.cs @@ -50,7 +50,7 @@ public async Task then_patch_should_return_400_if_supported_in_any_version( stri // act var response = await PatchAsync( requestUrl, person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment.cs index 34fc0b20..434d734b 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given a versioned ODataController/when using a url segment.cs @@ -16,7 +16,7 @@ public async Task then_get_should_return_200( string requestUrl ) // act - var response = (await GetAsync( requestUrl )).EnsureSuccessStatusCode(); + var response = ( await GetAsync( requestUrl ) ).EnsureSuccessStatusCode(); // assert response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0" ); 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 e85c1b9b..bb549030 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 @@ -19,12 +19,12 @@ public async Task then_2_different_versions_should_return_200() NewGet( "api/people/42?api-version=2.0" ) ); // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); // assert response.IsSuccessStatusCode.Should().BeTrue(); - var multipart = await response.Content.ReadAsMultipartAsync(); + var multipart = await response.Content.ReadAsMultipartAsync( CancellationToken ); var contents = multipart.Contents; contents.Should().HaveCount( 2 ); @@ -70,12 +70,12 @@ public async Task then_2_different_entity_sets_should_return_200() NewGet( "api/orders/42?api-version=1.0" ) ); // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); // assert response.IsSuccessStatusCode.Should().BeTrue(); - var multipart = await response.Content.ReadAsMultipartAsync(); + var multipart = await response.Content.ReadAsMultipartAsync( CancellationToken ); var contents = multipart.Contents; contents.Should().HaveCount( 2 ); @@ -105,7 +105,7 @@ public async Task then_explicit_versions_should_succeed() NewDelete( "api/customers/42" ) ); // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); // assert response.IsSuccessStatusCode.Should().BeTrue(); 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 8d873512..34c0406d 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 @@ -20,12 +20,12 @@ public async Task then_2_different_versions_should_return_200() NewGet( "v2/people/42" ) ); // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); // assert response.IsSuccessStatusCode.Should().BeTrue(); - var multipart = await response.Content.ReadAsMultipartAsync(); + var multipart = await response.Content.ReadAsMultipartAsync( CancellationToken ); var contents = multipart.Contents; contents.Should().HaveCount( 2 ); @@ -71,12 +71,12 @@ public async Task then_2_different_entity_sets_should_return_200() NewGet( "v1/orders/42" ) ); // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); // assert response.IsSuccessStatusCode.Should().BeTrue(); - var multipart = await response.Content.ReadAsMultipartAsync(); + var multipart = await response.Content.ReadAsMultipartAsync( CancellationToken ); var contents = multipart.Contents; contents.Should().HaveCount( 2 ); @@ -106,7 +106,7 @@ public async Task then_explicit_versions_should_succeed() NewDelete( "v3/customers/42" ) ); // act - var response = await Client.SendAsync( request ); + var response = await Client.SendAsync( request, CancellationToken ); // assert response.IsSuccessStatusCode.Should().BeTrue(); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string and split into two types.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string and split into two types.cs index cf15f390..3371f363 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string and split into two types.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string and split into two types.cs @@ -63,7 +63,7 @@ public async Task then_patch_should_return_400_if_supported_in_any_version( stri // act var response = await PatchAsync( requestUrl, person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); @@ -90,7 +90,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/people" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string.cs index 9ae9f4c0..6417feee 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a query string.cs @@ -44,7 +44,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version() // act var response = await GetAsync( "api/orders" ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment and split into two types.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment and split into two types.cs index 05727010..b16fdf46 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment and split into two types.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/UsingConventions/given a versioned ODataController using conventions/when using a url segment and split into two types.cs @@ -50,7 +50,7 @@ public async Task then_patch_should_return_400_if_supported_in_any_version( stri // act var response = await PatchAsync( requestUrl, person ); - var problem = await response.Content.ReadAsProblemDetailsAsync(); + var problem = await response.Content.ReadAsProblemDetailsAsync( CancellationToken ); // assert response.StatusCode.Should().Be( BadRequest ); 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 03c8f591..f503a31b 100644 --- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/TestApplicationPart.cs +++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/TestApplicationPart.cs @@ -10,7 +10,7 @@ namespace Asp.Versioning; internal sealed class TestApplicationPart : ApplicationPart, IApplicationPartTypeProvider { - public TestApplicationPart() => Types = Enumerable.Empty(); + public TestApplicationPart() => Types = []; public TestApplicationPart( params TypeInfo[] types ) => Types = types; diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ApiDescriptionExtensions.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ApiDescriptionExtensions.cs index 29520295..9bb02cc8 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ApiDescriptionExtensions.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ApiDescriptionExtensions.cs @@ -1,21 +1,29 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Mvc.ApiExplorer; internal static class ApiDescriptionExtensions { - internal static bool IsODataLike( this ApiDescription description ) + extension( ApiDescription description ) { - var parameters = description.ActionDescriptor.Parameters; - - for ( var i = 0; i < parameters.Count; i++ ) + internal bool IsODataLike { - if ( parameters[i].ParameterType.IsODataQueryOptions() ) + get { - return true; + var parameters = description.ActionDescriptor.Parameters; + + for ( var i = 0; i < parameters.Count; i++ ) + { + if ( parameters[i].ParameterType.IsODataQueryOptions ) + { + return true; + } + } + + return false; } } - - return false; } } \ No newline at end of file diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ModelMetadataExtensions.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ModelMetadataExtensions.cs index 81e33eb4..33109f92 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ModelMetadataExtensions.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ModelMetadataExtensions.cs @@ -3,18 +3,19 @@ namespace Asp.Versioning.ApiExplorer; using Microsoft.AspNetCore.Mvc.ModelBinding; -using System.Runtime.CompilerServices; internal static class ModelMetadataExtensions { - [MethodImpl( MethodImplOptions.AggressiveInlining )] - internal static ModelMetadata SubstituteIfNecessary( this ModelMetadata modelMetadata, Type type ) + extension( ModelMetadata modelMetadata ) { - if ( type.Equals( modelMetadata.ModelType ) ) + internal ModelMetadata SubstituteIfNecessary( Type type ) { - return modelMetadata; - } + if ( type.Equals( modelMetadata.ModelType ) ) + { + return modelMetadata; + } - return new SubstitutedModelMetadata( modelMetadata, type ); + return new SubstitutedModelMetadata( modelMetadata, type ); + } } } \ 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 9850a820..238bd11e 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 @@ -97,6 +97,12 @@ public virtual void OnProvidersExecuted( ApiDescriptionProviderContext context ) ArgumentNullException.ThrowIfNull( context ); var results = context.Results; + + if ( results.Count == 0 ) + { + return; + } + var visited = new HashSet( capacity: results.Count, new ApiDescriptionComparer() ); for ( var i = results.Count - 1; i >= 0; i-- ) @@ -167,6 +173,7 @@ public virtual void OnProvidersExecuting( ApiDescriptionProviderContext context /// Explores the OData query options for the specified API descriptions. /// /// The sequence of API descriptions to explore. + [UnconditionalSuppressMessage( "ILLink", "IL2026" )] protected virtual void ExploreQueryOptions( IEnumerable apiDescriptions ) { var localODataOptions = ODataOptions; @@ -175,7 +182,7 @@ protected virtual void ExploreQueryOptions( IEnumerable apiDescr { NoDollarPrefix = localODataOptions.EnableNoDollarQueryOptions, DescriptionProvider = localQueryOptions.DescriptionProvider, - DefaultQuerySettings = localODataOptions.QuerySettings, + QueryConfigurations = localODataOptions.QueryConfigurations, ModelMetadataProvider = ModelMetadataProvider, }; @@ -207,7 +214,7 @@ private static bool TryMatchModelVersion( IODataRoutingMetadata[] items, [NotNullWhen( true )] out IODataRoutingMetadata? metadata ) { - if ( description.GetApiVersion() is not ApiVersion apiVersion ) + if ( description.ApiVersion is not ApiVersion apiVersion ) { // this should only happen if an odata endpoint is registered outside of api versioning: // @@ -224,7 +231,7 @@ private static bool TryMatchModelVersion( for ( var i = 0; i < items.Length; i++ ) { var item = items[i]; - var otherApiVersion = item.Model.GetApiVersion(); + var otherApiVersion = item.Model.ApiVersion; if ( apiVersion.Equals( otherApiVersion ) ) { @@ -287,7 +294,7 @@ private static void RemoveODataOptions( ApiDescription description ) for ( var i = 0; i < parameters.Count; i++ ) { - if ( parameters[i].Type.IsODataQueryOptions() ) + if ( parameters[i].Type.IsODataQueryOptions ) { parameters.RemoveAt( i ); break; @@ -314,10 +321,10 @@ private void ExpandNavigationPropertyLinks( switch ( template[i] ) { case EntitySetSegmentTemplate segment: - entity = segment.EntitySet.EntityType(); + entity = segment.EntitySet.EntityType; break; case SingletonSegmentTemplate segment: - entity = segment.Singleton.EntityType(); + entity = segment.Singleton.EntityType; break; } } @@ -417,14 +424,14 @@ private void UpdateModelTypes( ApiDescription description, IODataRoutingMetadata continue; } - if ( type.IsODataQueryOptions() || type.IsODataPath() ) + if ( type.IsODataQueryOptions || type.IsODataPath ) { // don't explore ODataQueryOptions or ODataPath parameters.RemoveAt( i ); continue; } - if ( type.IsODataActionParameters() ) + if ( type.IsODataActionParameters ) { var action = metadata.Template[^1] switch { @@ -432,7 +439,7 @@ private void UpdateModelTypes( ApiDescription description, IODataRoutingMetadata ActionImportSegmentTemplate segment => segment.ActionImport.Action, _ => default, }; - var apiVersion = description.GetApiVersion()!; + var apiVersion = description.ApiVersion!; var controllerName = ( (ControllerActionDescriptor) description.ActionDescriptor ).ControllerName; type = ModelTypeBuilder.NewActionParameters( metadata.Model, action!, controllerName, apiVersion ); 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 5e494688..b9a90465 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 @@ -36,7 +36,7 @@ public ODataApiExplorerOptionsFactory( IEnumerable> postConfigures ) : base( options, setups, postConfigures ) { - this.providers = ( providers ?? throw new ArgumentNullException( nameof( providers ) ) ).ToArray(); + this.providers = [.. providers ?? throw new ArgumentNullException( nameof( providers ) )]; this.modelConfigurations = modelConfigurations ?? throw new ArgumentNullException( nameof( modelConfigurations ) ); } @@ -64,7 +64,7 @@ public ODataApiExplorerOptionsFactory( IEnumerable> validations ) : base( options, setups, postConfigures, validations ) { - this.providers = ( providers ?? throw new ArgumentNullException( nameof( providers ) ) ).ToArray(); + this.providers = [.. providers ?? throw new ArgumentNullException( nameof( providers ) )]; this.modelConfigurations = modelConfigurations ?? throw new ArgumentNullException( nameof( modelConfigurations ) ); } @@ -106,7 +106,7 @@ private static ODataApiVersionCollectionProvider CollateApiVersions( versions.Add( options.DefaultApiVersion ); } - return new() { ApiVersions = versions.ToArray() }; + return new() { ApiVersions = [.. versions] }; } private sealed class ODataApiVersionCollectionProvider : IODataApiVersionCollectionProvider diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApplicationModelProvider.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApplicationModelProvider.cs new file mode 100644 index 00000000..014e52a2 --- /dev/null +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApplicationModelProvider.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +#pragma warning disable CA1812 + +namespace Asp.Versioning.ApiExplorer; + +using Asp.Versioning.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +internal sealed class ODataApplicationModelProvider : IApplicationModelProvider +{ + public int Order => 0; + + public void OnProvidersExecuted( ApplicationModelProviderContext context ) { } + + public void OnProvidersExecuting( ApplicationModelProviderContext context ) + { + ArgumentNullException.ThrowIfNull( context ); + + var application = context.Result; + var controllers = application.Controllers; + var odata = new ODataControllerSpecification(); + var convention = new ApiVisibilityConvention(); + + for ( var i = 0; i < controllers.Count; i++ ) + { + var controller = controllers[i]; + + if ( !odata.IsSatisfiedBy( controller ) ) + { + continue; + } + + var actions = controller.Actions; + + for ( var j = 0; j < actions.Count; j++ ) + { + convention.Apply( actions[j] ); + } + } + } +} \ No newline at end of file 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 4099958b..bb709911 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 @@ -76,7 +76,7 @@ protected ODataApiExplorerOptions Options /// A read-only list of /// OData query option conventions. protected IReadOnlyList Conventions => - conventions ??= Options.AdHocModelBuilder.ModelConfigurations.OfType().ToArray(); + conventions ??= [.. Options.AdHocModelBuilder.ModelConfigurations.OfType()]; /// /// Gets or sets the order precedence of the current API description provider. @@ -85,6 +85,7 @@ protected ODataApiExplorerOptions Options public int Order { get; protected set; } = BeforeOData; /// + [UnconditionalSuppressMessage( "ILLink", "IL2026" )] public virtual void OnProvidersExecuting( ApiDescriptionProviderContext context ) { ArgumentNullException.ThrowIfNull( context ); @@ -101,7 +102,7 @@ public virtual void OnProvidersExecuting( ApiDescriptionProviderContext context for ( var i = 0; i < models.Count; i++ ) { var model = models[i]; - var version = model.GetApiVersion(); + var version = model.ApiVersion; var odata = odataOptionsFactory.Create( Opts.DefaultName ); odata.AddRouteComponents( model ); @@ -109,7 +110,7 @@ public virtual void OnProvidersExecuting( ApiDescriptionProviderContext context for ( var j = 0; j < results.Length; j++ ) { var result = results[j]; - var metadata = result.ActionDescriptor.GetApiVersionMetadata(); + var metadata = result.ActionDescriptor.ApiVersionMetadata; if ( metadata.IsMappedTo( version ) ) { @@ -132,7 +133,7 @@ public virtual void OnProvidersExecuted( ApiDescriptionProviderContext context ) for ( var j = metadata.Count - 1; j >= 0; j-- ) { - if ( metadata[j] is IODataRoutingMetadata routing && routing.Model.IsAdHoc() ) + if ( metadata[j] is IODataRoutingMetadata routing && routing.Model.IsAdHoc ) { metadata.Remove( j ); } @@ -154,6 +155,7 @@ private static int ODataOrder() => private static void MarkAsAdHoc( ODataModelBuilder builder, IEdmModel model ) => model.SetAnnotationValue( model, AdHocAnnotation.Instance ); + [RequiresUnreferencedCode( "MVC does not currently support trimming or native AOT. https://aka.ms/aspnet/trimming" )] private static ApiDescription[] FilterResults( IList results, IReadOnlyList conventions ) @@ -175,7 +177,7 @@ private static ApiDescription[] FilterResults( } } - if ( odata || !result.IsODataLike() ) + if ( odata || !result.IsODataLike ) { continue; } 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 7a2bb5cc..fa2300ab 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,8 +1,8 @@  - 8.2.1 - 8.2.0.0 + 10.0.0 + 10.0.0.0 $(DefaultTargetFramework) Asp.Versioning ASP.NET Core API Versioning API Explorer for 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 6ca75048..51624411 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 @@ -3,6 +3,7 @@ namespace Asp.Versioning.Conventions; using Microsoft.AspNetCore.Mvc.ApiExplorer; +using System.Diagnostics.CodeAnalysis; /// /// Provides additional implementation specific to ASP.NET Core. @@ -11,6 +12,7 @@ namespace Asp.Versioning.Conventions; public partial class ImplicitModelBoundSettingsConvention { /// + [RequiresUnreferencedCode( "MVC does not currently support trimming or native AOT. https://aka.ms/aspnet/trimming" )] public void ApplyTo( ApiDescription apiDescription ) { ArgumentNullException.ThrowIfNull( apiDescription ); diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionDescriptionContext.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionDescriptionContext.cs index ae3ad97b..1225f2be 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionDescriptionContext.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionDescriptionContext.cs @@ -15,7 +15,7 @@ public partial class ODataQueryOptionDescriptionContext { private static IEdmModel? ResolveModel( ApiDescription description ) { - var version = description.GetApiVersion(); + var version = description.ApiVersion; if ( version == null ) { @@ -34,7 +34,7 @@ public partial class ODataQueryOptionDescriptionContext foreach ( var item in items ) { var model = item.Model; - var otherVersion = model.GetApiVersion(); + var otherVersion = model.ApiVersion; if ( version.Equals( otherVersion ) ) { diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs index c66d040a..fa07627d 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs @@ -3,6 +3,7 @@ namespace Asp.Versioning.Conventions; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.OData.Query; /// /// Provides additional implementation specific to Microsoft ASP.NET Core. @@ -10,9 +11,21 @@ namespace Asp.Versioning.Conventions; [CLSCompliant( false )] public partial class ODataQueryOptionSettings { + private DefaultQueryConfigurations? queryConfig; + /// /// Gets or sets the configured model metadata provider. /// /// The configured model metadata provider. public IModelMetadataProvider? ModelMetadataProvider { get; set; } + + /// + /// Gets or sets the OData query configurations. + /// + /// The default OData query configurations. + public DefaultQueryConfigurations QueryConfigurations + { + get => queryConfig ??= new(); + set => queryConfig = value; + } } \ 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 bfab96ce..6c0ac4a6 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 @@ -14,6 +14,7 @@ namespace Asp.Versioning.Conventions; public partial class ODataValidationSettingsConvention { /// + [RequiresUnreferencedCode( "MVC does not currently support trimming or native AOT. https://aka.ms/aspnet/trimming" )] public virtual void ApplyTo( ApiDescription apiDescription ) { ArgumentNullException.ThrowIfNull( apiDescription ); @@ -24,7 +25,7 @@ public virtual void ApplyTo( ApiDescription apiDescription ) } var context = new ODataQueryOptionDescriptionContext( apiDescription, ValidationSettings ); - var queryOptions = GetQueryOptions( Settings.DefaultQuerySettings!, context ); + var queryOptions = GetQueryOptions( Settings.QueryConfigurations, context ); var visitor = new ODataAttributeVisitor( context, queryOptions ); visitor.Visit( apiDescription ); 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 9304fe5d..5d582372 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 @@ -1,12 +1,16 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.Extensions.DependencyInjection; using Asp.Versioning; using Asp.Versioning.ApiExplorer; using Asp.Versioning.Conventions; using Asp.Versioning.OData; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor; @@ -17,32 +21,38 @@ namespace Microsoft.Extensions.DependencyInjection; [CLSCompliant( false )] public static class IApiVersioningBuilderExtensions { - /// - /// Adds the API versioning extensions for the API Explorer with OData. - /// - /// The extended API versioning builder. - /// The original . - public static IApiVersioningBuilder AddODataApiExplorer( this IApiVersioningBuilder builder ) - { - ArgumentNullException.ThrowIfNull( builder ); - AddApiExplorerServices( builder ); - return builder; - } + private const string TrimmingMessage = "MVC does not currently support trimming or native AOT. https://aka.ms/aspnet/trimming"; - /// - /// Adds the API versioning extensions for the API Explorer with OData. - /// /// The extended API versioning builder. - /// An action used to configure the provided options. /// The original . - public static IApiVersioningBuilder AddODataApiExplorer( this IApiVersioningBuilder builder, Action setupAction ) + extension( IApiVersioningBuilder builder ) { - ArgumentNullException.ThrowIfNull( builder ); - AddApiExplorerServices( builder ); - builder.Services.Configure( setupAction ); - return builder; + /// + /// Adds the API versioning extensions for the API Explorer with OData. + /// + [RequiresUnreferencedCode( TrimmingMessage )] + public IApiVersioningBuilder AddODataApiExplorer() + { + ArgumentNullException.ThrowIfNull( builder ); + AddApiExplorerServices( builder ); + return builder; + } + + /// + /// Adds the API versioning extensions for the API Explorer with OData. + /// + /// An action used to configure the provided options. + [RequiresUnreferencedCode( TrimmingMessage )] + public IApiVersioningBuilder AddODataApiExplorer( Action setupAction ) + { + ArgumentNullException.ThrowIfNull( builder ); + AddApiExplorerServices( builder ); + builder.Services.Configure( setupAction ); + return builder; + } } + [RequiresUnreferencedCode( TrimmingMessage )] private static void AddApiExplorerServices( IApiVersioningBuilder builder ) { var services = builder.Services; @@ -51,6 +61,7 @@ private static void AddApiExplorerServices( IApiVersioningBuilder builder ) builder.Services.AddModelConfigurationsAsServices(); services.TryAddSingleton(); services.TryAddSingleton, ODataApiExplorerOptionsFactory>(); + services.TryAddEnumerable( Transient() ); services.TryAddEnumerable( Transient() ); services.TryAddEnumerable( Transient() ); services.TryAddEnumerable( Transient() ); 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 c262ce18..5f282702 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ReleaseNotes.txt +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ReleaseNotes.txt @@ -1,3 +1 @@ -Enable trimming support ([#1094](https://github.com/dotnet/aspnet-api-versioning/issues/1094)) -Fixed invalid property setter on substituted type ([#1104](https://github.com/dotnet/aspnet-api-versioning/issues/1104)) -Fixed single-quote OData strings within parentheses syntax ([#1152](https://github.com/dotnet/aspnet-api-versioning/issues/1152)) \ No newline at end of file + \ 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 7f554e9b..f5262346 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/ApplicationModels/ODataControllerSpecification.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/ApplicationModels/ODataControllerSpecification.cs @@ -3,7 +3,6 @@ namespace Asp.Versioning.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.OData.Edm; using Microsoft.AspNetCore.OData.Routing.Attributes; /// 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 55ceec65..3171b88d 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,8 +1,8 @@  - 8.2.1 - 8.2.0.0 + 10.0.0 + 10.0.0.0 $(DefaultTargetFramework) Asp.Versioning ASP.NET Core API Versioning with OData v4.0 @@ -16,7 +16,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 c6248429..1eed580f 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/Builder/IApplicationBuilderExtensions.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Builder/IApplicationBuilderExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Builder; using Asp.Versioning.OData.Batch; @@ -10,14 +12,17 @@ namespace Microsoft.AspNetCore.Builder; [CLSCompliant( false )] public static class IApplicationBuilderExtensions { - /// - /// Uses API versioned OData batch middleware. - /// /// The current . /// The original . - public static IApplicationBuilder UseVersionedODataBatching( this IApplicationBuilder app ) + extension( IApplicationBuilder app ) { - ArgumentNullException.ThrowIfNull( app ); - return app.UseMiddleware(); + /// + /// Uses API versioned OData batch middleware. + /// + public IApplicationBuilder UseVersionedODataBatching() + { + ArgumentNullException.ThrowIfNull( app ); + return app.UseMiddleware(); + } } } \ No newline at end of file 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 8d90b695..52e589e1 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IApiVersioningBuilderExtensions.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IApiVersioningBuilderExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.Extensions.DependencyInjection; using Asp.Versioning; @@ -15,7 +17,6 @@ namespace Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using System.Globalization; -using System.Runtime.CompilerServices; using static Asp.Versioning.OData.ODataMultiModelApplicationModelProvider; using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor; @@ -24,141 +25,141 @@ namespace Microsoft.Extensions.DependencyInjection; /// public static class IApiVersioningBuilderExtensions { - /// - /// Adds ASP.NET Core OData support for API versioning. - /// - /// The extended API versioning builder. - /// The original . - public static IApiVersioningBuilder AddOData( this IApiVersioningBuilder builder ) - { - ArgumentNullException.ThrowIfNull( builder ); - AddServices( builder.AddMvc().Services ); - return builder; - } - - /// - /// Adds ASP.NET Core OData support for API versioning. - /// /// The extended API versioning builder. - /// An action used to configure the provided options. /// The original . - [CLSCompliant( false )] - public static IApiVersioningBuilder AddOData( this IApiVersioningBuilder builder, Action setupAction ) - { - ArgumentNullException.ThrowIfNull( builder ); - - var services = builder.AddMvc().Services; - AddServices( services ); - services.Configure( setupAction ); - return builder; - } - - private static void AddServices( IServiceCollection services ) + extension( IApiVersioningBuilder builder ) { - services.TryRemoveODataService( typeof( IApplicationModelProvider ), ODataRoutingApplicationModelProviderType ); - - var partManager = services.GetOrCreateApplicationPartManager(); - var configured = partManager.ConfigureDefaultFeatureProviders(); - - services.AddHttpContextAccessor(); - services.TryAddSingleton(); - services.TryReplaceODataService( - Singleton(), - "Microsoft.AspNetCore.OData.Routing.Template.DefaultODataTemplateTranslator" ); - services.Replace( Singleton>( sp => sp.GetRequiredService() ) ); - services.Replace( WithHttpContextFactoryDecorator( services ) ); - services.TryAddTransient(); - services.TryAddSingleton, ODataApiVersioningOptionsFactory>(); - services.TryAddSingleton(); - services.TryAddEnumerable( Transient() ); - services.TryAddEnumerable( Transient, ODataOptionsPostSetup>() ); - services.TryAddEnumerable( Singleton() ); - services.TryAddEnumerable( Transient() ); - services.TryAddEnumerable( Transient() ); - - if ( configured ) + /// + /// Adds ASP.NET Core OData support for API versioning. + /// + public IApiVersioningBuilder AddOData() { - services.AddModelConfigurationsAsServices( partManager ); + ArgumentNullException.ThrowIfNull( builder ); + builder.AddMvc().Services.AddODataServices(); + return builder; } - } - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static Type GetODataType( string typeName ) - { - var assemblyName = typeof( ODataOptions ).Assembly.GetName().Name; - return Type.GetType( $"{typeName}, {assemblyName}", throwOnError: true, ignoreCase: false )!; + /// + /// Adds ASP.NET Core OData support for API versioning. + /// + /// An action used to configure the provided options. + [CLSCompliant( false )] + public IApiVersioningBuilder AddOData( Action setupAction ) + { + ArgumentNullException.ThrowIfNull( builder ); + + var services = builder.AddMvc().Services; + services.AddODataServices(); + services.Configure( setupAction ); + return builder; + } } - private static void TryRemoveODataService( this IServiceCollection services, Type serviceType, Type implementationType ) + extension( IServiceCollection services ) { - for ( var i = 0; i < services.Count; i++ ) + private void AddODataServices() { - var service = services[i]; - - if ( service.ServiceType == serviceType && service.ImplementationType == implementationType ) + const string DefaultODataTemplateTranslator = "Microsoft.AspNetCore.OData.Routing.Template.DefaultODataTemplateTranslator, Microsoft.AspNetCore.OData"; + + services.TryRemoveODataService( typeof( IApplicationModelProvider ), ODataRoutingApplicationModelProviderType ); + + var partManager = services.GetOrCreateApplicationPartManager(); + var configured = partManager.ConfigureDefaultFeatureProviders(); + + services.AddHttpContextAccessor(); + services.TryAddSingleton(); + services.TryReplaceODataService( + Singleton(), + Type.GetType( DefaultODataTemplateTranslator, throwOnError: true, ignoreCase: false )! ); + services.Replace( Singleton>( sp => sp.GetRequiredService() ) ); + services.Replace( services.WithHttpContextFactoryDecorator() ); + services.TryAddTransient(); + services.TryAddSingleton, ODataApiVersioningOptionsFactory>(); + services.TryAddSingleton(); + services.TryAddEnumerable( Transient() ); + services.TryAddEnumerable( Transient, ODataOptionsPostSetup>() ); + services.TryAddEnumerable( Singleton() ); + services.TryAddEnumerable( Transient() ); + services.TryAddEnumerable( Transient() ); + + if ( configured ) { - services.RemoveAt( i ); - return; + services.AddModelConfigurationsAsServices( partManager ); } } - var message = string.Format( - CultureInfo.CurrentCulture, - Format.UnableToFindServices, - nameof( IMvcBuilder ), - "AddOData", - "ConfigureServices(...)" ); + private void TryRemoveODataService( Type serviceType, Type implementationType ) + { + for ( var i = 0; i < services.Count; i++ ) + { + var service = services[i]; - throw new InvalidOperationException( message ); - } + if ( service.ServiceType == serviceType && service.ImplementationType == implementationType ) + { + services.RemoveAt( i ); + return; + } + } - private static void TryReplaceODataService( - this IServiceCollection services, - ServiceDescriptor replacement, - string implementationTypeName ) - { - var serviceType = replacement.ServiceType; - var implementationType = GetODataType( implementationTypeName ); + var message = string.Format( + CultureInfo.CurrentCulture, + Format.UnableToFindServices, + nameof( IMvcBuilder ), + "AddOData", + "ConfigureServices(...)" ); + + throw new InvalidOperationException( message ); + } - for ( var i = 0; i < services.Count; i++ ) + private void TryReplaceODataService( + ServiceDescriptor replacement, + [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.None )] Type implementationType ) { - var service = services[i]; + var serviceType = replacement.ServiceType; - if ( service.ServiceType == serviceType && service.ImplementationType == implementationType ) + for ( var i = 0; i < services.Count; i++ ) { - services[i] = replacement; - break; + var service = services[i]; + + if ( service.ServiceType == serviceType && service.ImplementationType == implementationType ) + { + services[i] = replacement; + break; + } } } - } - private static object CreateInstance( this IServiceProvider services, ServiceDescriptor descriptor ) - { - if ( descriptor.ImplementationInstance != null ) + private ServiceDescriptor WithHttpContextFactoryDecorator() { - return descriptor.ImplementationInstance; - } + var descriptor = services.First( sd => sd.ServiceType == typeof( IHttpContextFactory ) ); + var lifetime = descriptor.Lifetime; - if ( descriptor.ImplementationFactory != null ) - { - return descriptor.ImplementationFactory( services ); - } + IHttpContextFactory NewFactory( IServiceProvider serviceProvider ) + { + var decorated = (IHttpContextFactory) serviceProvider.CreateInstance( descriptor ); + return new HttpContextFactoryDecorator( decorated ); + } - return ActivatorUtilities.GetServiceOrCreateInstance( services, descriptor.ImplementationType! ); + return Describe( typeof( IHttpContextFactory ), NewFactory, lifetime ); + } } - private static ServiceDescriptor WithHttpContextFactoryDecorator( IServiceCollection services ) + extension( IServiceProvider services ) { - var descriptor = services.First( sd => sd.ServiceType == typeof( IHttpContextFactory ) ); - var lifetime = descriptor.Lifetime; - - IHttpContextFactory NewFactory( IServiceProvider serviceProvider ) + private object CreateInstance( ServiceDescriptor descriptor ) { - var decorated = (IHttpContextFactory) serviceProvider.CreateInstance( descriptor ); - return new HttpContextFactoryDecorator( decorated ); - } + if ( descriptor.ImplementationInstance != null ) + { + return descriptor.ImplementationInstance; + } - return Describe( typeof( IHttpContextFactory ), NewFactory, lifetime ); + if ( descriptor.ImplementationFactory != null ) + { + return descriptor.ImplementationFactory( services ); + } + + return ActivatorUtilities.GetServiceOrCreateInstance( services, descriptor.ImplementationType! ); + } } private sealed class HttpContextFactoryDecorator( IHttpContextFactory decorated ) : IHttpContextFactory 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 0f17e2eb..8cb87029 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IServiceCollectionExtensions.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IServiceCollectionExtensions.cs @@ -1,11 +1,12 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.Extensions.DependencyInjection; using Asp.Versioning.OData; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.DependencyInjection.Extensions; -using System.Runtime.CompilerServices; using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor; /// @@ -13,62 +14,65 @@ namespace Microsoft.Extensions.DependencyInjection; /// public static class IServiceCollectionExtensions { - [MethodImpl( MethodImplOptions.AggressiveInlining )] - internal static T GetService( this IServiceCollection services ) => - (T) services.LastOrDefault( d => d.ServiceType == typeof( T ) )?.ImplementationInstance!; - - internal static ApplicationPartManager GetOrCreateApplicationPartManager( this IServiceCollection services ) + extension( IServiceCollection services ) { - var partManager = services.GetService(); + internal T GetService() => (T) services.LastOrDefault( d => d.ServiceType == typeof( T ) )?.ImplementationInstance!; - if ( partManager == null ) + internal ApplicationPartManager GetOrCreateApplicationPartManager() { - partManager = new ApplicationPartManager(); - services.TryAddSingleton( partManager ); - } + var partManager = services.GetService(); - partManager.ApplicationParts.Add( new AssemblyPart( typeof( ODataApiVersioningOptions ).Assembly ) ); - return partManager; - } + if ( partManager == null ) + { + partManager = new ApplicationPartManager(); + services.TryAddSingleton( partManager ); + } - [UnconditionalSuppressMessage( "ILLink", "IL2072", Justification = "Model configuration types are never trimmed" )] - internal static void AddModelConfigurationsAsServices( this IServiceCollection services, ApplicationPartManager partManager ) - { - var feature = new ModelConfigurationFeature(); - var modelConfigurationType = typeof( IModelConfiguration ); - - partManager.PopulateFeature( feature ); + partManager.ApplicationParts.Add( new AssemblyPart( typeof( ODataApiVersioningOptions ).Assembly ) ); + return partManager; + } - foreach ( var modelConfiguration in feature.ModelConfigurations ) + [UnconditionalSuppressMessage( "ILLink", "IL2072", Justification = "Model configuration types are never trimmed" )] + internal void AddModelConfigurationsAsServices( ApplicationPartManager partManager ) { - services.TryAddEnumerable( Transient( modelConfigurationType, modelConfiguration ) ); + var feature = new ModelConfigurationFeature(); + var modelConfigurationType = typeof( IModelConfiguration ); + + partManager.PopulateFeature( feature ); + + foreach ( var modelConfiguration in feature.ModelConfigurations ) + { + services.TryAddEnumerable( Transient( modelConfigurationType, modelConfiguration ) ); + } } - } - internal static bool ConfigureDefaultFeatureProviders( this ApplicationPartManager partManager ) - { - if ( partManager.FeatureProviders.OfType().Any() ) + /// + /// Registers discovered model configurations as services in the . + /// + public void AddModelConfigurationsAsServices() { - return false; - } + ArgumentNullException.ThrowIfNull( services ); + + var partManager = services.GetOrCreateApplicationPartManager(); - partManager.FeatureProviders.Add( new ModelConfigurationFeatureProvider() ); - return true; + if ( partManager.ConfigureDefaultFeatureProviders() ) + { + services.AddModelConfigurationsAsServices( partManager ); + } + } } - /// - /// Registers discovered model configurations as services in the . - /// - /// The extended . - public static void AddModelConfigurationsAsServices( this IServiceCollection services ) + extension( ApplicationPartManager partManager ) { - ArgumentNullException.ThrowIfNull( services ); - - var partManager = services.GetOrCreateApplicationPartManager(); - - if ( ConfigureDefaultFeatureProviders( partManager ) ) + internal bool ConfigureDefaultFeatureProviders() { - services.AddModelConfigurationsAsServices( partManager ); + if ( partManager.FeatureProviders.OfType().Any() ) + { + return false; + } + + partManager.FeatureProviders.Add( new ModelConfigurationFeatureProvider() ); + return true; } } } \ 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 8cf589be..1fe5835c 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 @@ -88,7 +88,7 @@ private static void MergeRouteData( HttpContext context, RouteValueDictionary ro Dictionary candidates ) { var path = context.Request.Path; - var feature = context.ApiVersioningFeature(); + var feature = context.ApiVersioningFeature; var unspecified = feature.RawRequestedApiVersions.Count == 0; for ( var i = 0; i < count; i++ ) @@ -144,7 +144,7 @@ private static void MergeRouteData( HttpContext context, RouteValueDictionary ro // 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; + context.ApiVersioningFeature.RequestedApiVersion = version; return handler; } @@ -163,7 +163,7 @@ private static void MergeRouteData( HttpContext context, RouteValueDictionary ro // 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 model = new ApiVersionModel( candidates.Keys, [] ); var version = selector.SelectVersion( context.Request, model ); return SelectBestCandidate( context, candidates, routeData, version ); @@ -184,7 +184,7 @@ private static void MergeRouteData( HttpContext context, RouteValueDictionary ro // 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 model = new ApiVersionModel( candidates.Keys, [] ); var version = await selector.SelectVersionAsync( context.Request, model, cancellationToken ).ConfigureAwait( false ); return SelectBestCandidate( context, candidates, routeData, version ); 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 0aa16562..b29413b3 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApiVersionCollectionProvider.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApiVersionCollectionProvider.cs @@ -11,7 +11,7 @@ internal sealed class ODataApiVersionCollectionProvider : IODataApiVersionCollec public IReadOnlyList ApiVersions { - get => apiVersions ?? Array.Empty(); + get => apiVersions ?? []; set => apiVersions = value; } } \ No newline at end of file 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 3dfaa9af..62e3a65b 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApplicationModelProvider.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApplicationModelProvider.cs @@ -88,7 +88,7 @@ private static { var controller = controllers[i]; - if ( controller.ControllerType.IsMetadataController() ) + if ( controller.ControllerType.IsMetadataController ) { metadataControllers ??= []; metadataControllers.Add( controller ); @@ -239,6 +239,6 @@ private ApiVersion[] MergeApiVersions( return [.. deprecated]; } - return supported.Union( deprecated ).ToArray(); + return [.. supported.Union( deprecated )]; } } \ No newline at end of file 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 5676e28f..376c37a5 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataMultiModelApplicationModelProvider.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataMultiModelApplicationModelProvider.cs @@ -16,6 +16,7 @@ namespace Asp.Versioning.OData; internal sealed class ODataMultiModelApplicationModelProvider : IApplicationModelProvider { + [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicConstructors )] internal static readonly Type ODataRoutingApplicationModelProviderType = GetDefaultApplicationModelProviderType(); private static readonly Func, IApplicationModelProvider> NewODataApplicationModelProvider = CreateActivator( ODataRoutingApplicationModelProviderType ); private readonly IODataApiVersionCollectionProvider apiVersionCollectionProvider; @@ -128,11 +129,11 @@ static void NoConfig( IServiceCollection sc ) } [MethodImpl( MethodImplOptions.AggressiveInlining )] + [return: DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicConstructors )] private static Type GetDefaultApplicationModelProviderType() { - const string TypeName = "Microsoft.AspNetCore.OData.Routing.ODataRoutingApplicationModelProvider"; - var assemblyName = typeof( ODataOptions ).Assembly.GetName().Name; - return Type.GetType( $"{TypeName}, {assemblyName}", throwOnError: true, ignoreCase: false )!; + const string TypeName = "Microsoft.AspNetCore.OData.Routing.ODataRoutingApplicationModelProvider, Microsoft.AspNetCore.OData"; + return Type.GetType( TypeName, throwOnError: true, ignoreCase: false )!; } private static Func, IApplicationModelProvider> CreateActivator( @@ -155,7 +156,7 @@ private void AddRouteComponents( { var model = models[i]; - if ( model.GetApiVersion() is not ApiVersion version ) + if ( model.ApiVersion is not ApiVersion version ) { continue; } 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 5c08429e..829e311d 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataOptions.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataOptions.cs @@ -135,11 +135,11 @@ public virtual bool TryGetValue( HttpContext? context, [NotNullWhen( true )] out return false; } - var apiVersion = context.GetRequestedApiVersion(); + var apiVersion = context.RequestedApiVersion; if ( apiVersion == null ) { - var model = new ApiVersionModel( mapping.Keys, Array.Empty() ); + var model = new ApiVersionModel( mapping.Keys, [] ); apiVersion = ApiVersionSelector.SelectVersion( context.Request, model ); if ( apiVersion == null ) @@ -166,11 +166,11 @@ public virtual bool TryGetValue( HttpContext? context, [NotNullWhen( true )] out return default; } - var apiVersion = context.GetRequestedApiVersion(); + var apiVersion = context.RequestedApiVersion; if ( apiVersion == null ) { - var model = new ApiVersionModel( mapping.Keys, Array.Empty() ); + var model = new ApiVersionModel( mapping.Keys, [] ); apiVersion = await ApiVersionSelector.SelectVersionAsync( context.Request, model, cancellationToken ).ConfigureAwait( false ); if ( apiVersion == null ) 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 93d92b9c..c006e32e 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataTemplateTranslator.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataTemplateTranslator.cs @@ -20,7 +20,7 @@ public sealed class VersionedODataTemplateTranslator : IODataTemplateTranslator ArgumentNullException.ThrowIfNull( path ); ArgumentNullException.ThrowIfNull( context ); - var apiVersion = context.HttpContext.GetRequestedApiVersion(); + var apiVersion = context.HttpContext.RequestedApiVersion; if ( apiVersion == null ) { @@ -32,7 +32,7 @@ public sealed class VersionedODataTemplateTranslator : IODataTemplateTranslator else { var model = context.Model; - var otherApiVersion = model.GetApiVersion(); + var otherApiVersion = model.ApiVersion; // HACK: a version-neutral endpoint can fail to match here because odata tries to match the // first endpoint metadata when there could be multiple. such an endpoint is expected to be diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/ReleaseNotes.txt b/src/AspNetCore/OData/src/Asp.Versioning.OData/ReleaseNotes.txt index 2bf10e3a..5f282702 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/ReleaseNotes.txt +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/ReleaseNotes.txt @@ -1,2 +1 @@ -Enable trimming support ([#1094](https://github.com/dotnet/aspnet-api-versioning/issues/1094)) -Fixed OData matches entire controllers or individual actions ([#1118](https://github.com/dotnet/aspnet-api-versioning/issues/1118)) \ No newline at end of file + \ 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 a4b7bfd3..394a94cc 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/DefaultMetadataMatcherPolicy.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/DefaultMetadataMatcherPolicy.cs @@ -118,11 +118,11 @@ public IReadOnlyList GetEdges( IReadOnlyList endpoints if ( edges is null || lowestApiVersion is null ) { - return Array.Empty(); + return []; } var state = (lowestApiVersion, routePatterns?.ToArray() ?? []); - return new PolicyNodeEdge[] { new( state, edges ) }; + return [new( state, edges )]; } /// @@ -147,7 +147,7 @@ public PolicyJumpTable BuildJumpTable( int exitDestination, IReadOnlyList new ApiVersionMatcherPolicy( ApiVersionParser.Default, - Enumerable.Empty(), + [], Options.Create( new ApiVersioningOptions() ), new NullLogger() ).Order; @@ -196,7 +196,7 @@ public override int GetDestination( HttpContext httpContext ) // we don't want to set an implicit api version if it exists in the path // because the normal routing process will handle it. it isn't available // from the feature because route constraints haven't been evaluated yet - var feature = httpContext.ApiVersioningFeature(); + var feature = httpContext.ApiVersioningFeature; var needsImplicitApiVersion = feature.RawRequestedApiVersions.Count == 0 && ( !versionsByUrl || 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 96e643e8..45cf92d5 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedAttributeRoutingConvention.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedAttributeRoutingConvention.cs @@ -51,7 +51,7 @@ public override bool AppliesToAction( ODataControllerActionContext context ) return false; } - var apiVersion = edm.GetApiVersion(); + var apiVersion = edm.ApiVersion; if ( apiVersion == null || !metadata.IsMappedTo( apiVersion ) ) { diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/ServiceProviderExtensions.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/ServiceProviderExtensions.cs index fb6de669..c6cad45f 100644 --- a/src/AspNetCore/OData/src/Asp.Versioning.OData/ServiceProviderExtensions.cs +++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/ServiceProviderExtensions.cs @@ -1,17 +1,18 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System; internal static class ServiceProviderExtensions { - internal static IServiceProvider WithParent( this IServiceProvider serviceProvider, IServiceProvider parent ) => - new CompositeServiceProvider( serviceProvider, parent ); + extension( IServiceProvider serviceProvider ) + { + internal IServiceProvider WithParent( IServiceProvider parent ) => new CompositeServiceProvider( serviceProvider, parent ); - internal static TService WithParent( - this IServiceProvider serviceProvider, - IServiceProvider parent, - Func implementationFactory ) => - implementationFactory( serviceProvider.WithParent( parent ) ); + internal TService WithParent( IServiceProvider parent, Func implementationFactory ) => + implementationFactory( serviceProvider.WithParent( parent ) ); + } private sealed class CompositeServiceProvider : IServiceProvider { 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 96dc6ef8..6e631922 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 @@ -9,6 +9,7 @@ namespace Asp.Versioning.ApiExplorer; using Microsoft.AspNetCore.OData; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using System.Buffers; public class ODataApiDescriptionProviderTest @@ -17,28 +18,31 @@ public class ODataApiDescriptionProviderTest public void odata_api_explorer_should_group_and_order_descriptions_on_providers_executed() { // arrange - var builder = new WebHostBuilder() - .ConfigureServices( - services => - { - services.AddControllers() - .AddOData( - options => - { - options.Count().Select().OrderBy(); - options.RouteOptions.EnableKeyInParenthesis = false; - options.RouteOptions.EnableNonParenthesisForEmptyParameterFunction = true; - options.RouteOptions.EnableQualifiedOperationCall = false; - options.RouteOptions.EnableUnqualifiedOperationCall = true; - } ); - - services.AddApiVersioning() - .AddOData( options => options.AddRouteComponents( "api" ) ) - .AddODataApiExplorer( options => options.GroupNameFormat = "'v'VVV" ); - - services.TryAddEnumerable( ServiceDescriptor.Transient() ); - } ) - .Configure( app => app.UseRouting().UseEndpoints( endpoints => endpoints.MapControllers() ) ); + var builder = Host.CreateDefaultBuilder() + .ConfigureWebHostDefaults( server => + { + server.ConfigureServices( + services => + { + services.AddControllers() + .AddOData( + options => + { + options.Count().Select().OrderBy(); + options.RouteOptions.EnableKeyInParenthesis = false; + options.RouteOptions.EnableNonParenthesisForEmptyParameterFunction = true; + options.RouteOptions.EnableQualifiedOperationCall = false; + options.RouteOptions.EnableUnqualifiedOperationCall = true; + } ); + + services.AddApiVersioning() + .AddOData( options => options.AddRouteComponents( "api" ) ) + .AddODataApiExplorer( options => options.GroupNameFormat = "'v'VVV" ); + + services.TryAddEnumerable( ServiceDescriptor.Transient() ); + } ) + .Configure( app => app.UseRouting().UseEndpoints( endpoints => endpoints.MapControllers() ) ); + } ); var host = builder.Build(); var serviceProvider = host.Services; @@ -64,28 +68,31 @@ public void odata_api_explorer_should_group_and_order_descriptions_on_providers_ public void odata_api_explorer_should_explore_metadata_routes( ODataMetadataOptions metadataOptions ) { // arrange - var builder = new WebHostBuilder() - .ConfigureServices( - services => - { - services.AddControllers() - .AddOData( - options => - { - options.Count().Select().OrderBy(); - options.RouteOptions.EnableKeyInParenthesis = false; - options.RouteOptions.EnableNonParenthesisForEmptyParameterFunction = true; - options.RouteOptions.EnableQualifiedOperationCall = false; - options.RouteOptions.EnableUnqualifiedOperationCall = true; - } ); - - services.AddApiVersioning() - .AddOData( options => options.AddRouteComponents( "api" ) ) - .AddODataApiExplorer( options => options.MetadataOptions = metadataOptions ); - - services.TryAddEnumerable( ServiceDescriptor.Transient() ); - } ) - .Configure( app => app.UseRouting().UseEndpoints( endpoints => endpoints.MapControllers() ) ); + var builder = Host.CreateDefaultBuilder() + .ConfigureWebHostDefaults( server => + { + server.ConfigureServices( + services => + { + services.AddControllers() + .AddOData( + options => + { + options.Count().Select().OrderBy(); + options.RouteOptions.EnableKeyInParenthesis = false; + options.RouteOptions.EnableNonParenthesisForEmptyParameterFunction = true; + options.RouteOptions.EnableQualifiedOperationCall = false; + options.RouteOptions.EnableUnqualifiedOperationCall = true; + } ); + + services.AddApiVersioning() + .AddOData( options => options.AddRouteComponents( "api" ) ) + .AddODataApiExplorer( options => options.MetadataOptions = metadataOptions ); + + services.TryAddEnumerable( ServiceDescriptor.Transient() ); + } ) + .Configure( app => app.UseRouting().UseEndpoints( endpoints => endpoints.MapControllers() ) ); + } ); var host = builder.Build(); var serviceProvider = host.Services; diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Conventions/ODataQueryOptionsConventionBuilderTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Conventions/ODataQueryOptionsConventionBuilderTest.cs index cd72258f..1e06de90 100644 --- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Conventions/ODataQueryOptionsConventionBuilderTest.cs +++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Conventions/ODataQueryOptionsConventionBuilderTest.cs @@ -5,7 +5,7 @@ namespace Asp.Versioning.Conventions; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.OData.ModelBuilder.Config; +using Microsoft.AspNetCore.OData.Query; using System.Reflection; public partial class ODataQueryOptionsConventionBuilderTest @@ -30,7 +30,7 @@ public void apply_should_apply_configured_conventions() var settings = new ODataQueryOptionSettings() { DescriptionProvider = builder.DescriptionProvider, - DefaultQuerySettings = new DefaultQuerySettings(), + QueryConfigurations = new DefaultQueryConfigurations(), ModelMetadataProvider = Mock.Of(), }; var convention = new Mock(); @@ -39,7 +39,7 @@ public void apply_should_apply_configured_conventions() builder.Add( convention.Object ); // act - builder.ApplyTo( new[] { description }, settings ); + builder.ApplyTo( [description], settings ); // assert convention.Verify( c => c.ApplyTo( description ), Times.Once() ); 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 fd3717ba..85fd4cce 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 @@ -22,7 +22,6 @@ namespace Asp.Versioning.Conventions; using Microsoft.AspNetCore.OData.Routing.Controllers; using Microsoft.OData.Edm; using Microsoft.OData.ModelBuilder; -using Microsoft.OData.ModelBuilder.Config; using System.Reflection; using Xunit; using static Microsoft.AspNetCore.Http.StatusCodes; @@ -301,7 +300,7 @@ public void apply_to_should_use_default_query_settings() { // arrange var description = NewApiDescription(); - var defaultQuerySettings = new DefaultQuerySettings() + var queryConfigurations = new DefaultQueryConfigurations() { EnableCount = true, EnableExpand = true, @@ -310,7 +309,7 @@ public void apply_to_should_use_default_query_settings() EnableSelect = true, }; var validationSettings = new ODataValidationSettings() { AllowedQueryOptions = AllowedQueryOptions.None }; - var settings = new TestODataQueryOptionSettings( typeof( object ), defaultQuerySettings ); + var settings = new TestODataQueryOptionSettings( typeof( object ), queryConfigurations ); var convention = new ODataValidationSettingsConvention( validationSettings, settings ); // act @@ -322,9 +321,10 @@ public void apply_to_should_use_default_query_settings() [Theory] [MemberData( nameof( EnableQueryAttributeData ) )] - public void apply_to_should_use_enable_query_attribute( ApiDescription description ) + public void apply_to_should_use_enable_query_attribute( Type controllerType ) { // arrange + var description = NewApiDescription( controllerType ); var validationSettings = new ODataValidationSettings() { AllowedQueryOptions = AllowedQueryOptions.None, @@ -340,8 +340,7 @@ public void apply_to_should_use_enable_query_attribute( ApiDescription descripti // assert description.ParameterDescriptions.Should().BeEquivalentTo( - new[] - { + [ new { Name = "$select", @@ -384,7 +383,7 @@ public void apply_to_should_use_enable_query_attribute( ApiDescription descripti ParameterType = typeof( string ), }, }, - }, + ], options => options.ExcludingMissingMembers() ); } @@ -413,8 +412,7 @@ public void apply_to_should_use_model_bound_query_attributes() // assert description.ParameterDescriptions.Should().BeEquivalentTo( - new[] - { + [ new { Name = "$select", @@ -471,7 +469,7 @@ public void apply_to_should_use_model_bound_query_attributes() ParameterType = typeof( bool ), }, }, - }, + ], options => options.ExcludingMissingMembers() ); } @@ -533,14 +531,14 @@ public void apply_to_should_process_odataX2Dlike_api_description() { ControllerTypeInfo = controllerType.GetTypeInfo(), MethodInfo = action, - Parameters = new ParameterDescriptor[] - { + Parameters = + [ new() { Name = parameter.Name, ParameterType = parameter.ParameterType, }, - }, + ], }, HttpMethod = "GET", SupportedResponseTypes = @@ -557,7 +555,6 @@ public void apply_to_should_process_odataX2Dlike_api_description() var settings = new ODataQueryOptionSettings() { DescriptionProvider = builder.DescriptionProvider, - DefaultQuerySettings = new(), ModelMetadataProvider = Mock.Of(), }; @@ -567,12 +564,11 @@ public void apply_to_should_process_odataX2Dlike_api_description() .AllowOrderBy( "title", "published" ); // act - builder.ApplyTo( new[] { description }, settings ); + builder.ApplyTo( [description], settings ); // assert description.ParameterDescriptions.Should().BeEquivalentTo( - new[] - { + [ new { Name = "$select", @@ -612,18 +608,12 @@ public void apply_to_should_process_odataX2Dlike_api_description() ParameterType = typeof( bool ), }, }, - }, + ], options => options.ExcludingMissingMembers() ); } - public static IEnumerable EnableQueryAttributeData - { - get - { - yield return new object[] { NewApiDescription( typeof( SinglePartController ) ) }; - yield return new object[] { NewApiDescription( typeof( MultipartController ) ) }; - } - } + public static TheoryData EnableQueryAttributeData => + new( typeof( SinglePartController ), typeof( MultipartController ) ); private static ApiDescription NewApiDescription( string method = "GET", bool singleResult = default ) { @@ -637,10 +627,10 @@ public static IEnumerable EnableQueryAttributeData { ControllerTypeInfo = typeof( ControllerBase ).GetTypeInfo(), MethodInfo = typeof( ControllerBase ).GetRuntimeMethod( nameof( ControllerBase.Ok ), Type.EmptyTypes ), - EndpointMetadata = new object[] - { + EndpointMetadata = + [ new ODataRoutingMetadata( string.Empty, model, [] ), - }, + ], }, HttpMethod = method, SupportedResponseTypes = @@ -668,10 +658,10 @@ private static ApiDescription NewApiDescription( Type controllerType, Type respo { ControllerTypeInfo = controllerType.GetTypeInfo(), MethodInfo = controllerType.GetRuntimeMethods().Single( m => m.Name == "Get" ), - EndpointMetadata = new object[] - { + EndpointMetadata = + [ new ODataRoutingMetadata( string.Empty, model, [] ), - }, + ], }, HttpMethod = "GET", SupportedResponseTypes = @@ -759,12 +749,12 @@ public class Customer private sealed class TestODataQueryOptionSettings : ODataQueryOptionSettings { internal TestODataQueryOptionSettings( Type type, bool dollarPrefix = true ) : - this( type, new DefaultQuerySettings(), dollarPrefix ) + this( type, new(), dollarPrefix ) { } internal TestODataQueryOptionSettings( Type type, - DefaultQuerySettings defaultQuerySettings, + DefaultQueryConfigurations queryConfigurations, bool dollarPrefix = true ) { MockDescriptionProvider = new Mock(); @@ -775,7 +765,7 @@ internal TestODataQueryOptionSettings( NoDollarPrefix = !dollarPrefix; DescriptionProvider = MockDescriptionProvider.Object; ModelMetadataProvider = NewModelMetadataProvider( type ); - DefaultQuerySettings = defaultQuerySettings; + QueryConfigurations = queryConfigurations; } internal Mock MockDescriptionProvider { get; } 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 e363fa4c..14efb7a2 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 @@ -8,6 +8,7 @@ namespace Asp.Versioning.Controllers; using Microsoft.AspNetCore.OData; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using System.Net.Http; public class VersionedMetadataControllerTest @@ -17,15 +18,16 @@ public async Task options_should_return_expected_headers() { // arrange var request = new HttpRequestMessage( new HttpMethod( "OPTIONS" ), "http://localhost/$metadata" ); - var builder = new WebHostBuilder().UseStartup(); - - using var server = new TestServer( builder ); - using var client = server.CreateClient(); + var builder = Host.CreateDefaultBuilder() + .ConfigureWebHostDefaults( builder => builder.UseTestServer().UseStartup() ); + using var server = await builder.StartAsync( TestContext.Current.CancellationToken ); + using var client = server.GetTestClient(); client.BaseAddress = new Uri( "http://localhost" ); // act - var response = ( await client.SendAsync( request ) ).EnsureSuccessStatusCode(); + var response = await client.SendAsync( request, TestContext.Current.CancellationToken ); + response = response.EnsureSuccessStatusCode(); // assert response.Headers.GetValues( "OData-Version" ).Single().Should().Be( "4.0" ); diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/ODataApiVersioningOptionsTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/ODataApiVersioningOptionsTest.cs index a26e1e61..e5fa69c9 100644 --- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/ODataApiVersioningOptionsTest.cs +++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/ODataApiVersioningOptionsTest.cs @@ -13,7 +13,7 @@ public void has_configurations_should_be_false_when_empty() // arrange var builder = new VersionedODataModelBuilder( Mock.Of(), - Enumerable.Empty() ); + [] ); // act var options = new ODataApiVersioningOptions( builder ); @@ -28,7 +28,7 @@ public void has_configurations_should_be_true_when_nonempty() // arrange var builder = new VersionedODataModelBuilder( Mock.Of(), - Enumerable.Empty() ); + [] ); // act var options = new ODataApiVersioningOptions( builder ).AddRouteComponents(); @@ -43,7 +43,7 @@ public void add_route_components_should_add_prefix_only() // arrange var builder = new VersionedODataModelBuilder( Mock.Of(), - Enumerable.Empty() ); + [] ); var options = new ODataApiVersioningOptions( builder ); // act @@ -59,7 +59,7 @@ public void add_route_components_should_add_configure_action_only() // arrange var builder = new VersionedODataModelBuilder( Mock.Of(), - Enumerable.Empty() ); + [] ); var options = new ODataApiVersioningOptions( builder ); var configureAction = Mock.Of>(); var services = new ServiceCollection(); @@ -78,7 +78,7 @@ public void add_route_components_should_add_batch_handler() // arrange var builder = new VersionedODataModelBuilder( Mock.Of(), - Enumerable.Empty() ); + [] ); var options = new ODataApiVersioningOptions( builder ); var handler = new DefaultODataBatchHandler(); var services = new ServiceCollection(); diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/VersionedODataModelBuilderTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/VersionedODataModelBuilderTest.cs index b40917a9..70661ef9 100644 --- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/VersionedODataModelBuilderTest.cs +++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/VersionedODataModelBuilderTest.cs @@ -16,7 +16,7 @@ public void get_edm_models_should_return_expected_results() var apiVersion = new ApiVersion( 1, 0 ); var apiVersionCollectionProvider = Mock.Of(); - apiVersionCollectionProvider.ApiVersions = new[] { apiVersion }; + apiVersionCollectionProvider.ApiVersions = [apiVersion]; var modelConfigurations = Enumerable.Empty(); var builder = new VersionedODataModelBuilder( apiVersionCollectionProvider, modelConfigurations ) @@ -29,7 +29,7 @@ public void get_edm_models_should_return_expected_results() var model = builder.GetEdmModels().Single(); // assert - model.GetApiVersion().Should().Be( apiVersion ); + model.ApiVersion.Should().Be( apiVersion ); modelCreated.Verify( f => f( It.IsAny(), model ), Once() ); } @@ -41,7 +41,7 @@ public void get_edm_models_should_split_models_between_routes() var apiVersion = new ApiVersion( 1, 0 ); var apiVersionCollectionProvider = Mock.Of(); - apiVersionCollectionProvider.ApiVersions = new[] { apiVersion, new ApiVersion( 2, 0 ) }; + apiVersionCollectionProvider.ApiVersions = [apiVersion, new ApiVersion( 2, 0 )]; var modelConfigurations = Enumerable.Empty(); var builder = new VersionedODataModelBuilder( apiVersionCollectionProvider, modelConfigurations ) diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/VersionedODataOptionsTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/VersionedODataOptionsTest.cs index 29f1cebc..db987491 100644 --- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/VersionedODataOptionsTest.cs +++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/VersionedODataOptionsTest.cs @@ -25,7 +25,7 @@ public void value_should_return_options_for_current_request() var accessor = Mock.Of(); features.Set( feature ); - Mock.Get( reader ).Setup( r => r.Read( It.IsAny() ) ).Returns( new[] { "2.0" } ); + Mock.Get( reader ).Setup( r => r.Read( It.IsAny() ) ).Returns( ["2.0"] ); Mock.Get( serviceProvider ).Setup( sp => sp.GetService( typeof( IApiVersionReader ) ) ).Returns( reader ); Mock.Get( serviceProvider ).Setup( sp => sp.GetService( typeof( IApiVersionParser ) ) ).Returns( ApiVersionParser.Default ); Mock.Get( context ).SetupGet( c => c.Features ).Returns( features ); @@ -64,7 +64,7 @@ public void value_should_return_select_version_and_options_for_current_request() var accessor = Mock.Of(); features.Set( feature ); - Mock.Get( reader ).Setup( r => r.Read( It.IsAny() ) ).Returns( new[] { "2.0" } ); + Mock.Get( reader ).Setup( r => r.Read( It.IsAny() ) ).Returns( ["2.0"] ); Mock.Get( serviceProvider ).Setup( sp => sp.GetService( typeof( IApiVersionReader ) ) ).Returns( reader ); Mock.Get( serviceProvider ).Setup( sp => sp.GetService( typeof( IApiVersionParser ) ) ).Returns( ApiVersionParser.Default ); Mock.Get( context ).SetupGet( c => c.Features ).Returns( features ); 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 9181574e..190107d1 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 @@ -27,7 +27,7 @@ public void applied_to_action_should_return_true_for_versionX2Dneutral_action() var convention = new VersionedAttributeRoutingConvention( logger, parser ); var action = new ActionModel( Mock.Of(), - Array.Empty() ) + [] ) { Selectors = { @@ -39,7 +39,7 @@ public void applied_to_action_should_return_true_for_versionX2Dneutral_action() }; var controller = new ControllerModel( typeof( ODataController ).GetTypeInfo(), - Array.Empty() ) + [] ) { Actions = { action }, }; @@ -68,7 +68,7 @@ public void applies_to_action_should_return_false_if_api_version_matches_edm_ann var convention = new VersionedAttributeRoutingConvention( logger, parser ); var action = new ActionModel( Mock.Of(), - Array.Empty() ) + [] ) { Selectors = { @@ -86,7 +86,7 @@ public void applies_to_action_should_return_false_if_api_version_matches_edm_ann }; var controller = new ControllerModel( typeof( ODataController ).GetTypeInfo(), - new object[] { new ODataAttributeRoutingAttribute() } ) + [new ODataAttributeRoutingAttribute()] ) { Actions = { action }, }; @@ -120,7 +120,7 @@ public void applies_to_action_should_return_false_if_api_version_is_different_fr var convention = new VersionedAttributeRoutingConvention( logger, parser ); var action = new ActionModel( Mock.Of(), - Array.Empty() ) + [] ) { Selectors = { @@ -138,7 +138,7 @@ public void applies_to_action_should_return_false_if_api_version_is_different_fr }; var controller = new ControllerModel( typeof( ODataController ).GetTypeInfo(), - new object[] { new ODataAttributeRoutingAttribute() } ) + [new ODataAttributeRoutingAttribute()] ) { Actions = { action }, }; 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 0346cac2..b8d7293d 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 @@ -21,7 +21,7 @@ public void applies_to_controller_should_return_expected_result( Type controller var context = new ODataControllerActionContext( string.Empty, EdmCoreModel.Instance, - new ControllerModel( controllerType.GetTypeInfo(), Array.Empty() ) ); + new ControllerModel( controllerType.GetTypeInfo(), [] ) ); var convention = new VersionedMetadataRoutingConvention(); // act @@ -35,9 +35,9 @@ public void applies_to_controller_should_return_expected_result( Type controller public void applied_to_action_should_return_true() { // arrange - var controller = new ControllerModel( typeof( VersionedMetadataController ).GetTypeInfo(), Array.Empty() ); + var controller = new ControllerModel( typeof( VersionedMetadataController ).GetTypeInfo(), [] ); var method = controller.ControllerType.GetRuntimeMethod( nameof( VersionedMetadataController.GetOptions ), Type.EmptyTypes ); - var action = new ActionModel( method, Array.Empty() ) { Controller = controller }; + var action = new ActionModel( method, [] ) { Controller = controller }; controller.Actions.Add( action ); diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/TestApplicationPart.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/TestApplicationPart.cs index 4d989234..5a46131f 100644 --- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/TestApplicationPart.cs +++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/TestApplicationPart.cs @@ -7,7 +7,7 @@ namespace Asp.Versioning; internal sealed class TestApplicationPart : ApplicationPart, IApplicationPartTypeProvider { - public TestApplicationPart() => Types = Enumerable.Empty(); + public TestApplicationPart() => Types = []; public TestApplicationPart( params TypeInfo[] types ) => Types = types; diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/DefaultEndpointInspector.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/DefaultEndpointInspector.cs index 7109d87f..da08f3e2 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/DefaultEndpointInspector.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/DefaultEndpointInspector.cs @@ -7,7 +7,7 @@ namespace Asp.Versioning.ApiExplorer; /// /// Represents the default endpoint inspector. /// -[CLSCompliant(false)] +[CLSCompliant( false )] public sealed class DefaultEndpointInspector : IEndpointInspector { /// 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 e5ce20fb..a458c7ee 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/EndpointApiVersionMetadataCollationProvider.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/EndpointApiVersionMetadataCollationProvider.cs @@ -15,14 +15,6 @@ public sealed class EndpointApiVersionMetadataCollationProvider : IApiVersionMet 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. /// diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IApiVersionDescriptionProviderFactoryExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IApiVersionDescriptionProviderFactoryExtensions.cs index 4a20e9c1..d3658eea 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IApiVersionDescriptionProviderFactoryExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IApiVersionDescriptionProviderFactoryExtensions.cs @@ -12,15 +12,18 @@ namespace Asp.Versioning.ApiExplorer; [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 ) + extension( IApiVersionDescriptionProviderFactory factory ) { - ArgumentNullException.ThrowIfNull( factory ); - return factory.Create( new EmptyEndpointDataSource() ); + /// + /// Creates and returns an API version description provider. + /// + /// A new API version description provider. + public IApiVersionDescriptionProvider Create() + { + ArgumentNullException.ThrowIfNull( factory ); + return factory.Create( new EmptyEndpointDataSource() ); + } } private sealed class EmptyEndpointDataSource : EndpointDataSource 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 7c1794f6..7e0cff50 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,21 +1,28 @@  - 8.1.1 - 8.1.0.0 + 10.0.0 + 10.0.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 + + + $(NoWarn);NU1903 - + 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 250083cd..56f08f66 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/EndpointBuilderFinalizer.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/EndpointBuilderFinalizer.cs @@ -10,7 +10,6 @@ namespace Asp.Versioning.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System.Globalization; -using System.Runtime.CompilerServices; using static Asp.Versioning.ApiVersionParameterLocation; using static Asp.Versioning.ApiVersionProviderOptions; @@ -47,16 +46,20 @@ private static void Finialize( EndpointBuilder endpointBuilder, ApiVersionSet? v endpointBuilder.Metadata.Add( metadata ); - var requestDelegate = default( RequestDelegate ); + var requestDelegate = + endpointBuilder.RequestDelegate + ?? throw new InvalidOperationException( + string.Format( + CultureInfo.CurrentCulture, + Format.UnsetRequestDelegate, + nameof( RequestDelegate ), + nameof( RouteEndpoint ) ) ); if ( reportApiVersions ) { - requestDelegate = EnsureRequestDelegate( requestDelegate, endpointBuilder.RequestDelegate ); - var reporter = services.GetRequiredService(); requestDelegate = new ReportApiVersionsDecorator( requestDelegate, reporter, metadata ); - endpointBuilder.RequestDelegate = requestDelegate; } var parameterSource = services.GetRequiredService(); @@ -67,11 +70,15 @@ private static void Finialize( EndpointBuilder endpointBuilder, ApiVersionSet? v if ( !string.IsNullOrEmpty( parameterName ) ) { - requestDelegate = EnsureRequestDelegate( requestDelegate, endpointBuilder.RequestDelegate ); requestDelegate = new ContentTypeApiVersionDecorator( requestDelegate, parameterName ); - endpointBuilder.RequestDelegate = requestDelegate; } } + + endpointBuilder.RequestDelegate = context => + { + context.RequestServices = new InjectApiVersion( context ); + return requestDelegate( context ); + }; } private static bool IsApiVersionNeutral( IList metadata ) @@ -261,16 +268,6 @@ private static ApiVersionMetadata Build( IList metadata, ApiVersionSet v return new( apiModel, endpointModel, name ); } - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static RequestDelegate EnsureRequestDelegate( RequestDelegate? current, RequestDelegate? original ) => - ( current ?? original ) ?? - throw new InvalidOperationException( - string.Format( - CultureInfo.CurrentCulture, - Format.UnsetRequestDelegate, - nameof( RequestDelegate ), - nameof( RouteEndpoint ) ) ); - private record struct ApiVersionBuckets( IReadOnlyList Mapped, IReadOnlyList Supported, @@ -284,4 +281,28 @@ private record struct ApiVersionBuckets( && Advertised.Count == 0 && AdvertisedDeprecated.Count == 0; } + + private sealed class InjectApiVersion : IServiceProvider + { + private static readonly Type ApiVersionType = typeof( ApiVersion ); + private readonly IServiceProvider provider; + private readonly HttpContext context; + + public InjectApiVersion( HttpContext context ) + { + this.context = context; + provider = context.RequestServices; + context.RequestServices = this; + } + + public object? GetService( Type serviceType ) + { + if ( serviceType.IsAssignableFrom( ApiVersionType ) ) + { + return context.RequestedApiVersion; + } + + return provider.GetService( serviceType ); + } + } } \ 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 15525d66..6e48e515 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointConventionBuilderExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointConventionBuilderExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Builder; using Asp.Versioning; @@ -7,7 +9,6 @@ namespace Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using System.Collections; using System.Globalization; -using System.Runtime.Serialization; using static Asp.Versioning.ApiVersionProviderOptions; /// @@ -16,379 +17,291 @@ namespace Microsoft.AspNetCore.Builder; [CLSCompliant( false )] public static class IEndpointConventionBuilderExtensions { - /// - /// Applies the specified API version set to the endpoint. - /// /// The type of builder. - /// The extended builder. - /// The API version set the endpoint will use. /// The original . - public static TBuilder WithApiVersionSet( - this TBuilder builder, - ApiVersionSet apiVersionSet ) - where TBuilder : notnull, IEndpointConventionBuilder + extension( TBuilder builder ) where TBuilder : notnull, IEndpointConventionBuilder { - ArgumentNullException.ThrowIfNull( apiVersionSet ); - - builder.Add( endpoint => AddMetadata( endpoint, apiVersionSet ) ); - builder.Finally( EndpointBuilderFinalizer.FinalizeEndpoints ); - - return builder; - } - - /// - /// Indicates that the specified API version is mapped to the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The original . - public static TBuilder MapToApiVersion( this TBuilder builder, int majorVersion, int? minorVersion = default, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.MapToApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - - /// - /// Indicates that the specified API version is mapped to the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version number. - /// The optional version status. - /// The original . - public static TBuilder MapToApiVersion( this TBuilder builder, double version, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.MapToApiVersion( new ApiVersion( version, status ) ); - - /// - /// Indicates that the specified API version is mapped to the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// The original . - public static TBuilder MapToApiVersion( this TBuilder builder, int year, int month, int day, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.MapToApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - - /// - /// Indicates that the specified API version is mapped to the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The group version. - /// The optional version status. - /// The original . - public static TBuilder MapToApiVersion( this TBuilder builder, DateOnly groupVersion, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.MapToApiVersion( new ApiVersion( groupVersion, status ) ); - - /// - /// Maps the specified API version to the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The API version to map to the endpoint. - /// The original . - public static TBuilder MapToApiVersion( this TBuilder builder, ApiVersion apiVersion ) - where TBuilder : notnull, IEndpointConventionBuilder - { - builder.Add( endpoint => AddMetadata( endpoint, Convention.MapToApiVersion( apiVersion ) ) ); - return builder; - } + /// + /// Applies the specified API version set to the endpoint. + /// + /// The API version set the endpoint will use. + public TBuilder WithApiVersionSet( ApiVersionSet apiVersionSet ) + { + ArgumentNullException.ThrowIfNull( apiVersionSet ); - /// - /// Indicates that the endpoint is API version-neutral. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The original . - public static TBuilder IsApiVersionNeutral( this TBuilder builder ) - where TBuilder : notnull, IEndpointConventionBuilder - { - builder.Add( endpoint => AddMetadata( endpoint, new ApiVersionNeutralAttribute() ) ); - return builder; - } + builder.Add( endpoint => AddMetadata( endpoint, apiVersionSet ) ); + builder.Finally( EndpointBuilderFinalizer.FinalizeEndpoints ); - /// - /// Indicates that the specified API version is supported by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The original . - public static TBuilder HasApiVersion( this TBuilder builder, int majorVersion, int? minorVersion = default, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.HasApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - - /// - /// Indicates that the specified API version is supported by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version number. - /// The optional version status. - /// The original . - public static TBuilder HasApiVersion( this TBuilder builder, double version, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.HasApiVersion( new ApiVersion( version, status ) ); - - /// - /// Indicates that the specified API version is supported by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// The original . - public static TBuilder HasApiVersion( this TBuilder builder, int year, int month, int day, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.HasApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - - /// - /// Indicates that the specified API version is supported by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The group version. - /// The optional version status. - /// The original . - public static TBuilder HasApiVersion( this TBuilder builder, DateOnly groupVersion, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.HasApiVersion( new ApiVersion( groupVersion, status ) ); - - /// - /// Indicates that the specified API version is supported by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The supported API version implemented by the endpoint. - /// The original . - public static TBuilder HasApiVersion( this TBuilder builder, ApiVersion apiVersion ) - where TBuilder : notnull, IEndpointConventionBuilder - { - builder.Add( - endpoint => - { - AddMetadata( endpoint, Convention.HasApiVersion( apiVersion ) ); - AdvertiseInApiVersionSet( endpoint.Metadata, apiVersion ); - } ); - - return builder; - } + return builder; + } - /// - /// Indicates that the specified API version is deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The original . - public static TBuilder HasDeprecatedApiVersion( this TBuilder builder, int majorVersion, int? minorVersion = default, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.HasDeprecatedApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - - /// - /// Indicates that the specified API version is deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version number. - /// The optional version status. - /// The original . - public static TBuilder HasDeprecatedApiVersion( this TBuilder builder, double version, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.HasDeprecatedApiVersion( new ApiVersion( version, status ) ); - - /// - /// Indicates that the specified API version is deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// The original . - public static TBuilder HasDeprecatedApiVersion( this TBuilder builder, int year, int month, int day, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.HasDeprecatedApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - - /// - /// Indicates that the specified API version is deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The group version. - /// The optional version status. - /// The original . - public static TBuilder HasDeprecatedApiVersion( this TBuilder builder, DateOnly groupVersion, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.HasDeprecatedApiVersion( new ApiVersion( groupVersion, status ) ); - - /// - /// Indicates that the specified API version is deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The deprecated API version implemented by the endpoint. - /// The original . - public static TBuilder HasDeprecatedApiVersion( this TBuilder builder, ApiVersion apiVersion ) - where TBuilder : notnull, IEndpointConventionBuilder - { - builder.Add( - endpoint => - { - AddMetadata( endpoint, Convention.HasDeprecatedApiVersion( apiVersion ) ); - AdvertiseDeprecatedInApiVersionSet( endpoint.Metadata, apiVersion ); - } ); + /// + /// Indicates that the specified API version is mapped to the configured endpoint. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public TBuilder MapToApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) => + builder.MapToApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + + /// + /// Indicates that the specified API version is mapped to the configured endpoint. + /// + /// The version number. + /// The optional version status. + public TBuilder MapToApiVersion( double version, string? status = default ) => + builder.MapToApiVersion( new ApiVersion( version, status ) ); + + /// + /// Indicates that the specified API version is mapped to the configured endpoint. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + public TBuilder MapToApiVersion( int year, int month, int day, string? status = default ) => + builder.MapToApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + + /// + /// Indicates that the specified API version is mapped to the configured endpoint. + /// + /// The group version. + /// The optional version status. + public TBuilder MapToApiVersion( DateOnly groupVersion, string? status = default ) => + builder.MapToApiVersion( new ApiVersion( groupVersion, status ) ); + + /// + /// Maps the specified API version to the configured endpoint. + /// + /// The API version to map to the endpoint. + public TBuilder MapToApiVersion( ApiVersion apiVersion ) + { + builder.Add( endpoint => AddMetadata( endpoint, Convention.MapToApiVersion( apiVersion ) ) ); + return builder; + } - return builder; - } + /// + /// Indicates that the endpoint is API version-neutral. + /// + public TBuilder IsApiVersionNeutral() + { + builder.Add( endpoint => AddMetadata( endpoint, new ApiVersionNeutralAttribute() ) ); + return builder; + } - /// - /// Indicates that the specified API version is advertised by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The original . - public static TBuilder AdvertisesApiVersion( this TBuilder builder, int majorVersion, int? minorVersion = default, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.AdvertisesApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - - /// - /// Indicates that the specified API version is advertised by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version number. - /// The optional version status. - /// The original . - public static TBuilder AdvertisesApiVersion( this TBuilder builder, double version, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.AdvertisesApiVersion( new ApiVersion( version, status ) ); - - /// - /// Indicates that the specified API version is advertised by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// The original . - public static TBuilder AdvertisesApiVersion( this TBuilder builder, int year, int month, int day, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.AdvertisesApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - - /// - /// Indicates that the specified API version is advertised by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The group version. - /// The optional version status. - /// The original . - public static TBuilder AdvertisesApiVersion( this TBuilder builder, DateOnly groupVersion, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.AdvertisesApiVersion( new ApiVersion( groupVersion, status ) ); - - /// - /// Indicates that the specified API version is advertised by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The advertised API version not directly implemented by the endpoint. - /// The original . - public static TBuilder AdvertisesApiVersion( this TBuilder builder, ApiVersion apiVersion ) - where TBuilder : notnull, IEndpointConventionBuilder - { - builder.Add( - endpoint => - { - AddMetadata( endpoint, Convention.AdvertisesApiVersion( apiVersion ) ); - AdvertiseInApiVersionSet( endpoint.Metadata, apiVersion ); - } ); + /// + /// Indicates that the specified API version is supported by the configured endpoint. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public TBuilder HasApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) => + builder.HasApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + + /// + /// Indicates that the specified API version is supported by the configured endpoint. + /// + /// The version number. + /// The optional version status. + public TBuilder HasApiVersion( double version, string? status = default ) => + builder.HasApiVersion( new ApiVersion( version, status ) ); + + /// + /// Indicates that the specified API version is supported by the configured endpoint. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + public TBuilder HasApiVersion( int year, int month, int day, string? status = default ) => + builder.HasApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + + /// + /// Indicates that the specified API version is supported by the configured endpoint. + /// + /// The group version. + /// The optional version status. + public TBuilder HasApiVersion( DateOnly groupVersion, string? status = default ) => + builder.HasApiVersion( new ApiVersion( groupVersion, status ) ); + + /// + /// Indicates that the specified API version is supported by the configured endpoint. + /// + /// The supported API version implemented by the endpoint. + public TBuilder HasApiVersion( ApiVersion apiVersion ) + { + builder.Add( + endpoint => + { + AddMetadata( endpoint, Convention.HasApiVersion( apiVersion ) ); + AdvertiseInApiVersionSet( endpoint.Metadata, apiVersion ); + } ); + + return builder; + } - return builder; - } + /// + /// Indicates that the specified API version is deprecated by the configured endpoint. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public TBuilder HasDeprecatedApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) => + builder.HasDeprecatedApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + + /// + /// Indicates that the specified API version is deprecated by the configured endpoint. + /// + /// The version number. + /// The optional version status. + public TBuilder HasDeprecatedApiVersion( double version, string? status = default ) => + builder.HasDeprecatedApiVersion( new ApiVersion( version, status ) ); + + /// + /// Indicates that the specified API version is deprecated by the configured endpoint. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + public TBuilder HasDeprecatedApiVersion( int year, int month, int day, string? status = default ) => + builder.HasDeprecatedApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + + /// + /// Indicates that the specified API version is deprecated by the configured endpoint. + /// + /// The group version. + /// The optional version status. + public TBuilder HasDeprecatedApiVersion( DateOnly groupVersion, string? status = default ) => + builder.HasDeprecatedApiVersion( new ApiVersion( groupVersion, status ) ); + + /// + /// Indicates that the specified API version is deprecated by the configured endpoint. + /// + /// The deprecated API version implemented by the endpoint. + public TBuilder HasDeprecatedApiVersion( ApiVersion apiVersion ) + { + builder.Add( + endpoint => + { + AddMetadata( endpoint, Convention.HasDeprecatedApiVersion( apiVersion ) ); + AdvertiseDeprecatedInApiVersionSet( endpoint.Metadata, apiVersion ); + } ); + + return builder; + } - /// - /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The original . - public static TBuilder AdvertisesDeprecatedApiVersion( this TBuilder builder, int majorVersion, int? minorVersion = default, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.AdvertisesDeprecatedApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); - - /// - /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version number. - /// The optional version status. - /// The original . - public static TBuilder AdvertisesDeprecatedApiVersion( this TBuilder builder, double version, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.AdvertisesDeprecatedApiVersion( new ApiVersion( version, status ) ); - - /// - /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The version year. - /// The version month. - /// The version day. - /// The version status. - /// The original . - public static TBuilder AdvertisesDeprecatedApiVersion( this TBuilder builder, int year, int month, int day, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.AdvertisesDeprecatedApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); - - /// - /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The group version. - /// The optional version status. - /// The original . - public static TBuilder AdvertisesDeprecatedApiVersion( this TBuilder builder, DateOnly groupVersion, string? status = default ) - where TBuilder : notnull, IEndpointConventionBuilder => builder.AdvertisesDeprecatedApiVersion( new ApiVersion( groupVersion, status ) ); - - /// - /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The advertised, but deprecated API version not directly implemented by the endpoint. - /// The original . - public static TBuilder AdvertisesDeprecatedApiVersion( this TBuilder builder, ApiVersion apiVersion ) - where TBuilder : notnull, IEndpointConventionBuilder - { - builder.Add( - endpoint => - { - AddMetadata( endpoint, Convention.AdvertisesDeprecatedApiVersion( apiVersion ) ); - AdvertiseDeprecatedInApiVersionSet( endpoint.Metadata, apiVersion ); - } ); + /// + /// Indicates that the specified API version is advertised by the configured endpoint. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public TBuilder AdvertisesApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) => + builder.AdvertisesApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + + /// + /// Indicates that the specified API version is advertised by the configured endpoint. + /// + /// The version number. + /// The optional version status. + public TBuilder AdvertisesApiVersion( double version, string? status = default ) + => builder.AdvertisesApiVersion( new ApiVersion( version, status ) ); + + /// + /// Indicates that the specified API version is advertised by the configured endpoint. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + public TBuilder AdvertisesApiVersion( int year, int month, int day, string? status = default ) => + builder.AdvertisesApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + + /// + /// Indicates that the specified API version is advertised by the configured endpoint. + /// + /// The group version. + /// The optional version status. + public TBuilder AdvertisesApiVersion( DateOnly groupVersion, string? status = default ) => + builder.AdvertisesApiVersion( new ApiVersion( groupVersion, status ) ); + + /// + /// Indicates that the specified API version is advertised by the configured endpoint. + /// + /// The advertised API version not directly implemented by the endpoint. + public TBuilder AdvertisesApiVersion( ApiVersion apiVersion ) + { + builder.Add( + endpoint => + { + AddMetadata( endpoint, Convention.AdvertisesApiVersion( apiVersion ) ); + AdvertiseInApiVersionSet( endpoint.Metadata, apiVersion ); + } ); + + return builder; + } - return builder; - } + /// + /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + public TBuilder AdvertisesDeprecatedApiVersion( int majorVersion, int? minorVersion = default, string? status = default ) => + builder.AdvertisesDeprecatedApiVersion( new ApiVersion( majorVersion, minorVersion, status ) ); + + /// + /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. + /// + /// The version number. + /// The optional version status. + public TBuilder AdvertisesDeprecatedApiVersion( double version, string? status = default ) => + builder.AdvertisesDeprecatedApiVersion( new ApiVersion( version, status ) ); + + /// + /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. + /// + /// The version year. + /// The version month. + /// The version day. + /// The version status. + public TBuilder AdvertisesDeprecatedApiVersion( int year, int month, int day, string? status = default ) => + builder.AdvertisesDeprecatedApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ) ); + + /// + /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. + /// + /// The group version. + /// The optional version status. + public TBuilder AdvertisesDeprecatedApiVersion( DateOnly groupVersion, string? status = default ) => + builder.AdvertisesDeprecatedApiVersion( new ApiVersion( groupVersion, status ) ); + + /// + /// Indicates that the specified API version is advertised and deprecated by the configured endpoint. + /// + /// The advertised, but deprecated API version not + /// directly implemented by the endpoint. + public TBuilder AdvertisesDeprecatedApiVersion( ApiVersion apiVersion ) + { + builder.Add( + endpoint => + { + AddMetadata( endpoint, Convention.AdvertisesDeprecatedApiVersion( apiVersion ) ); + AdvertiseDeprecatedInApiVersionSet( endpoint.Metadata, apiVersion ); + } ); + + return builder; + } - /// - /// Indicates that the endpoint will report its API versions. - /// - /// The extended type. - /// The extended endpoint convention builder. - /// The original . - public static TBuilder ReportApiVersions( this TBuilder builder ) - where TBuilder : notnull, IEndpointConventionBuilder - { - builder.Add( endpoint => AddMetadata( endpoint, Convention.ReportApiVersions ) ); - return builder; + /// + /// Indicates that the endpoint will report its API versions. + /// + public TBuilder ReportApiVersions() + { + builder.Add( endpoint => AddMetadata( endpoint, Convention.ReportApiVersions ) ); + return builder; + } } private static void AddMetadata( EndpointBuilder builder, ApiVersionSet versionSet ) 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 e6054e34..7b0d2ff8 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointRouteBuilderExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointRouteBuilderExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Builder; using Asp.Versioning; @@ -7,7 +9,6 @@ namespace Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; -using System.Runtime.CompilerServices; /// /// Provides extension methods for . @@ -15,94 +16,92 @@ namespace Microsoft.AspNetCore.Builder; [CLSCompliant( false )] public static class IEndpointRouteBuilderExtensions { - /// - /// Creates and returns a new API version set builder for the specified endpoints. - /// - /// The extended . - /// The optional name of the API. - /// A new API version set builder. - public static ApiVersionSetBuilder NewApiVersionSet( this IEndpointRouteBuilder endpoints, string? name = default ) - { - ArgumentNullException.ThrowIfNull( endpoints ); - var create = endpoints.ServiceProvider.GetService(); - return create is null ? new( name ) : create( name ); - } - - /// - /// Applies the specified API version set to the endpoint group. - /// - /// The type of builder. - /// The extended builder. - /// The optional name associated with the builder. - /// A new instance. - public static IVersionedEndpointRouteBuilder WithApiVersionSet( this TBuilder builder, string? name = default ) - where TBuilder : notnull, IEndpointRouteBuilder, IEndpointConventionBuilder + /// The extended . + extension( IEndpointRouteBuilder builder ) { - ArgumentNullException.ThrowIfNull( builder ); - - if ( builder.HasMetadata() ) + /// + /// Creates and returns a new API version set builder for the specified endpoints. + /// + /// The optional name of the API. + /// A new API version set builder. + public ApiVersionSetBuilder NewApiVersionSet( string? name = default ) { - throw new InvalidOperationException( SR.CannotNestVersionSet ); + ArgumentNullException.ThrowIfNull( builder ); + var create = builder.ServiceProvider.GetService(); + return create is null ? new( name ) : create( name ); } - if ( !string.IsNullOrEmpty( name ) ) + /// + /// Creates a route group builder for defining all versioned endpoints in an API. + /// + /// The optional name associated with the builder. + /// A new instance. + public IVersionedEndpointRouteBuilder NewVersionedApi( string? name = default ) { - builder.Add( endpoint => endpoint.Metadata.Insert( 0, new TagsAttribute( name ) ) ); - } + ArgumentNullException.ThrowIfNull( builder ); - builder.Finally( EndpointBuilderFinalizer.FinalizeRoutes ); + if ( builder.IsNestedGroup ) + { + throw new InvalidOperationException( SR.CannotNestApiGroup ); + } - return builder.NewVersionedEndpointRouteBuilder( builder, builder, name ); - } + var group = builder.MapGroup( string.Empty ); + IEndpointConventionBuilder convention = group; - /// - /// Creates a route group builder for defining all versioned endpoints in an API. - /// - /// The extended . - /// The optional name associated with the builder. - /// A new instance. - public static IVersionedEndpointRouteBuilder NewVersionedApi( this IEndpointRouteBuilder builder, string? name = default ) - { - ArgumentNullException.ThrowIfNull( builder ); + if ( !string.IsNullOrEmpty( name ) ) + { + convention.Add( endpoint => endpoint.Metadata.Insert( 0, new TagsAttribute( name ) ) ); + } - if ( builder.IsNestedGroup() ) - { - throw new InvalidOperationException( SR.CannotNestApiGroup ); - } + convention.Finally( EndpointBuilderFinalizer.FinalizeRoutes ); - var group = builder.MapGroup( string.Empty ); - IEndpointConventionBuilder convention = group; + return builder.NewVersionedEndpointRouteBuilder( group, group, name ); + } - if ( !string.IsNullOrEmpty( name ) ) + private IVersionedEndpointRouteBuilder NewVersionedEndpointRouteBuilder( + IEndpointRouteBuilder routeBuilder, + IEndpointConventionBuilder conventionBuilder, + string? name ) { - convention.Add( endpoint => endpoint.Metadata.Insert( 0, new TagsAttribute( name ) ) ); + var create = builder.ServiceProvider.GetService(); + var versionSet = builder.NewApiVersionSet( name ); + + return create is null ? + new VersionedEndpointRouteBuilder( routeBuilder, conventionBuilder, versionSet ) : + create( routeBuilder, conventionBuilder, versionSet ); } - convention.Finally( EndpointBuilderFinalizer.FinalizeRoutes ); + private bool HasMetadata => builder.ServiceProvider.GetService() is not null; - return builder.NewVersionedEndpointRouteBuilder( group, group, name ); + private bool IsNestedGroup => builder is RouteGroupBuilder || builder.HasMetadata; } - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static IVersionedEndpointRouteBuilder NewVersionedEndpointRouteBuilder( - this IEndpointRouteBuilder builder, - IEndpointRouteBuilder routeBuilder, - IEndpointConventionBuilder conventionBuilder, - string? name ) + /// The type of builder. + /// The extended builder. + extension( TBuilder builder ) where TBuilder : notnull, IEndpointRouteBuilder, IEndpointConventionBuilder { - var create = builder.ServiceProvider.GetService(); - var versionSet = builder.NewApiVersionSet( name ); + /// + /// Applies the specified API version set to the endpoint group. + /// + /// The optional name associated with the builder. + /// A new instance. + public IVersionedEndpointRouteBuilder WithApiVersionSet( string? name = default ) + { + ArgumentNullException.ThrowIfNull( builder ); - return create is null ? - new VersionedEndpointRouteBuilder( routeBuilder, conventionBuilder, versionSet ) : - create( routeBuilder, conventionBuilder, versionSet ); - } + if ( builder.HasMetadata ) + { + throw new InvalidOperationException( SR.CannotNestVersionSet ); + } + + if ( !string.IsNullOrEmpty( name ) ) + { + builder.Add( endpoint => endpoint.Metadata.Insert( 0, new TagsAttribute( name ) ) ); + } - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static bool HasMetadata( this IEndpointRouteBuilder builder ) => - builder.ServiceProvider.GetService() is not null; + builder.Finally( EndpointBuilderFinalizer.FinalizeRoutes ); - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static bool IsNestedGroup( this IEndpointRouteBuilder builder ) => - builder is RouteGroupBuilder || builder.HasMetadata(); + return builder.NewVersionedEndpointRouteBuilder( builder, builder, name ); + } + } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IVersionedEndpointRouteBuilder.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IVersionedEndpointRouteBuilder.cs index 70306ae4..83e9d8f0 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IVersionedEndpointRouteBuilder.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IVersionedEndpointRouteBuilder.cs @@ -2,7 +2,6 @@ namespace Asp.Versioning.Builder; -using Asp.Versioning.Conventions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; 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 36af4a92..86e65a0e 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/VersionedEndpointRouteBuilder.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/VersionedEndpointRouteBuilder.cs @@ -2,7 +2,6 @@ namespace Asp.Versioning.Builder; -using Asp.Versioning.Conventions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; @@ -56,8 +55,7 @@ public virtual IApplicationBuilder CreateApplicationBuilder() => public virtual ICollection DataSources => dataSources; /// - public virtual void Add( Action convention ) => - conventionBuilder.Add( convention ); + public virtual void Add( Action convention ) => conventionBuilder.Add( convention ); private sealed class ServiceProviderDecorator( IServiceProvider decorated, diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/ApiVersioningBuilder.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/ApiVersioningBuilder.cs index 58f213c0..6c8f4da8 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/ApiVersioningBuilder.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/ApiVersioningBuilder.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.Extensions.DependencyInjection; using Asp.Versioning; 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 da09790e..29916a74 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/IServiceCollectionExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/IServiceCollectionExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.Extensions.DependencyInjection; using Asp.Versioning; @@ -10,6 +12,7 @@ namespace Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using System; using static ServiceDescriptor; using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; @@ -19,125 +22,119 @@ namespace Microsoft.Extensions.DependencyInjection; [CLSCompliant( false )] public static partial class IServiceCollectionExtensions { - /// - /// Adds service API versioning to the specified services collection. - /// - /// The services available in the application. - /// The builder used to configure API versioning. - public static IApiVersioningBuilder AddApiVersioning( this IServiceCollection services ) - { - AddApiVersioningServices( services ); - return new ApiVersioningBuilder( services ); - } - - /// - /// Adds service API versioning to the specified services collection. - /// /// The services available in the application. - /// An action used to configure the provided options. - /// The builder used to configure API versioning. - public static IApiVersioningBuilder AddApiVersioning( this IServiceCollection services, Action setupAction ) + extension( IServiceCollection services ) { - AddApiVersioningServices( services ); - services.Configure( setupAction ); - return new ApiVersioningBuilder( services ); - } + /// + /// Adds service API versioning to the specified services collection. + /// + /// The builder used to configure API versioning. + public IApiVersioningBuilder AddApiVersioning() + { + AddApiVersioningServices( services ); + return new ApiVersioningBuilder( services ); + } - /// - /// Enables binding the type in Minimal API parameters.. - /// - /// The extended API versioning builder. - /// The original . - public static IApiVersioningBuilder EnableApiVersionBinding( this IApiVersioningBuilder builder ) - { - ArgumentNullException.ThrowIfNull( builder ); + /// + /// Adds service API versioning to the specified services collection. + /// + /// An action used to configure the provided options. + /// The builder used to configure API versioning. + public IApiVersioningBuilder AddApiVersioning( Action setupAction ) + { + AddApiVersioningServices( services ); + services.Configure( setupAction ); + return new ApiVersioningBuilder( services ); + } - // currently required because there is no other hook. - // 1. TryParse does not work because: - // a. Parsing is delegated to IApiVersionParser.TryParse - // b. The result can come from multiple locations - // c. There can be multiple results - // 2. BindAsync does not work because: - // a. It is static and must be on the ApiVersion type - // b. It is specific to ASP.NET Core - builder.Services.AddHttpContextAccessor(); + /// + /// Adds error object support in problem details. + /// + /// The JSON options setup to perform, if any. + /// + /// + /// 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 IServiceCollection AddErrorObjects( Action? setup = default ) => + AddErrorObjects( services, setup ); + + /// + /// Adds error object support in problem details. + /// + /// The type of . + /// The JSON options setup to perform, if any. + /// + /// + /// 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 IServiceCollection AddErrorObjects<[DynamicallyAccessedMembers( PublicConstructors )] TWriter>( + Action? setup = default ) + where TWriter : ErrorObjectWriter + { + ArgumentNullException.ThrowIfNull( services ); - // this registration is 'truthy'. it is possible for the requested API version to be null; however, but the time this is - // resolved for a request delegate it can only be null if the API is version-neutral and no API version was requested. this - // should be a rare and nonsensical scenario. declaring the parameter as ApiVersion? should be expect and solve the issue - // - // it should also be noted that this registration allows resolving the requested API version from virtually any context. - // that is not intended, which is why this extension is not named something more general such as AddApiVersionAsService. - // if/when a better parameter binding mechanism becomes available, this method is expected to become obsolete, no-op, and - // eventually go away. - builder.Services.AddTransient( sp => sp.GetRequiredService().HttpContext?.GetRequestedApiVersion()! ); + services.TryAddEnumerable( Singleton() ); + services.Configure( setup ?? DefaultErrorObjectJsonConfig ); - return builder; + return services; + } } - /// - /// 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 ); + private static void DefaultErrorObjectJsonConfig( JsonOptions options ) => + options.SerializerOptions.TypeInfoResolverChain.Insert( 0, ErrorObjectWriter.ErrorObjectJsonContext.Default ); - /// - /// 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 + // HACK: convince DI that ApiVersion can be resolved as a service. this enables ApiVersion to be used as a + // a parameter without explicitly specifying [FromServices]. DI is not actually expected to resolve ApiVersion + // because it requires the current HttpContext. an interceptor is inserted in EndpointBuilderFinalizer.Finalize. + // resolving ApiVersion from DI allows it to be resolved from nearly any context, which is not intended. by the time + // an endpoint action is invoked, the ApiVersion will be available in the current HttpContext unless the API is + // version-neutral. in those situations, the parameter can be declared as ApiVersion? instead. this function makes + // a best effort to be honor DI by resolving the ApiVersion through IHttpContextAccessor if it's available. + // + // ultimately, this is required because there is no other hook. if/when a better parameter binding mechanism becomes + // available, this is expected to go away. + // + // 1. TryParse does not work because: + // a. Parsing is delegated to IApiVersionParser.TryParse + // b. The result can come from multiple locations + // c. There can be multiple results + // 2. BindAsync does not work because: + // a. It is static and must be on the ApiVersion type + // b. It requires HttpContext, which is specific to ASP.NET Core + // + // REF: https://github.com/dotnet/aspnetcore/issues/35489 + // REF: https://github.com/dotnet/aspnetcore/issues/50672 + private static ApiVersion ApiVersionAsService( IServiceProvider provider ) { - ArgumentNullException.ThrowIfNull( services ); - - services.TryAddEnumerable( Singleton() ); - services.Configure( setup ?? DefaultErrorObjectJsonConfig ); - - // TODO: remove with TryAddErrorObjectJsonOptions in 9.0+ - services.AddTransient(); + if ( provider.GetService() is { } accessor && accessor.HttpContext is { } context ) + { + return context.RequestedApiVersion!; + } - return services; + return default!; } - private static void DefaultErrorObjectJsonConfig( JsonOptions options ) => - options.SerializerOptions.TypeInfoResolverChain.Insert( 0, ErrorObjectWriter.ErrorObjectJsonContext.Default ); - private static void AddApiVersioningServices( IServiceCollection services ) { ArgumentNullException.ThrowIfNull( services ); + services.AddTransient( ApiVersionAsService ); services.TryAddSingleton(); services.AddSingleton( static sp => sp.GetRequiredService>().Value.ApiVersionReader ); services.AddSingleton( static sp => (IApiVersionParameterSource) sp.GetRequiredService>().Value.ApiVersionReader ); @@ -151,10 +148,10 @@ private static void AddApiVersioningServices( IServiceCollection services ) services.TryAddEnumerable( Singleton() ); services.TryAddTransient(); services.Replace( WithLinkGeneratorDecorator( services ) ); - TryAddProblemDetailsRfc7231Compliance( services ); - TryAddErrorObjectJsonOptions( services ); } + // 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 private static ServiceDescriptor WithLinkGeneratorDecorator( IServiceCollection services ) { var descriptor = services.FirstOrDefault( sd => sd.ServiceType == typeof( LinkGenerator ) ); @@ -166,24 +163,12 @@ private static ServiceDescriptor WithLinkGeneratorDecorator( IServiceCollection } var lifetime = descriptor.Lifetime; - var factory = descriptor.ImplementationFactory; - if ( factory == null ) + if ( descriptor.ImplementationFactory is { } factory ) { - // 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 ) ); - LinkGenerator NewFactory( IServiceProvider serviceProvider ) { - var instance = (LinkGenerator) serviceProvider.GetRequiredService( decoratedType! ); + var instance = (LinkGenerator) factory( serviceProvider ); var source = serviceProvider.GetRequiredService(); if ( source.VersionsByUrl() ) @@ -198,91 +183,47 @@ LinkGenerator NewFactory( IServiceProvider serviceProvider ) } else { - LinkGenerator NewFactory( IServiceProvider serviceProvider ) + if ( descriptor.ImplementationType is { } decoratedType ) { - var instance = (LinkGenerator) factory( serviceProvider ); - var source = serviceProvider.GetRequiredService(); + services.Replace( Describe( decoratedType, decoratedType, lifetime ) ); - if ( source.VersionsByUrl() ) + LinkGenerator NewFactory( IServiceProvider serviceProvider ) { - instance = new ApiVersionLinkGenerator( instance ); - } - - return instance; - } - - return Describe( typeof( LinkGenerator ), NewFactory, lifetime ); - } - } - - // TODO: Fixed and released; remove in .NET 10.0 - // BUG: https://github.com/dotnet/aspnetcore/issues/52577 - private static void TryAddProblemDetailsRfc7231Compliance( IServiceCollection services ) - { - var descriptor = services.FirstOrDefault( IsDefaultProblemDetailsWriter ); - - if ( descriptor == null ) - { - return; - } - - var index = services.IndexOf( descriptor ); - var decoratedType = descriptor.ImplementationType!; - var lifetime = descriptor.Lifetime; - - services[index] = Describe( typeof( IProblemDetailsWriter ), sp => NewProblemDetailsWriter( sp, decoratedType ), lifetime ); - services.Add( Describe( decoratedType, decoratedType, lifetime ) ); + var instance = (LinkGenerator) serviceProvider.GetRequiredService( decoratedType ); + var source = serviceProvider.GetRequiredService(); - static bool IsDefaultProblemDetailsWriter( ServiceDescriptor serviceDescriptor ) => - serviceDescriptor.ServiceType == typeof( IProblemDetailsWriter ) && - serviceDescriptor.ImplementationType?.FullName == "Microsoft.AspNetCore.Http.DefaultProblemDetailsWriter"; + if ( source.VersionsByUrl() ) + { + instance = new ApiVersionLinkGenerator( instance ); + } - 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]; + return instance; + } - if ( !hasErrorObjects && - service.ServiceType == serviceType && - implementationType.IsAssignableFrom( service.ImplementationType ) ) + factory = NewFactory; + } + else if ( descriptor.ImplementationInstance is LinkGenerator instance ) { - hasErrorObjects = true; - - if ( hasErrorObjectsJsonConfig ) + LinkGenerator NewFactory( IServiceProvider serviceProvider ) { - break; + var source = serviceProvider.GetRequiredService(); + + if ( source.VersionsByUrl() ) + { + instance = new ApiVersionLinkGenerator( instance ); + } + + return instance; } + + factory = NewFactory; } - else if ( service.ServiceType == markerType ) + else { - hasErrorObjectsJsonConfig = true; - - if ( hasErrorObjects ) - { - break; - } + throw new InvalidOperationException(); } - } - if ( hasErrorObjects && !hasErrorObjectsJsonConfig ) - { - services.Configure( DefaultErrorObjectJsonConfig ); + return Describe( typeof( LinkGenerator ), factory, lifetime ); } } - - // 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/HeaderApiVersionReader.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/HeaderApiVersionReader.cs index 96b90c63..e08fcde1 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/HeaderApiVersionReader.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/HeaderApiVersionReader.cs @@ -20,7 +20,7 @@ public virtual IReadOnlyList Read( HttpRequest request ) if ( count == 0 ) { - return Array.Empty(); + return []; } var version = default( string ); @@ -73,6 +73,6 @@ public virtual IReadOnlyList Read( HttpRequest request ) return version == null ? [] : [version]; } - return versions.ToArray(); + return [.. versions]; } } \ No newline at end of file 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 60167f90..ba335e45 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpContextExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpContextExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Http; using Asp.Versioning; @@ -11,39 +13,43 @@ namespace Microsoft.AspNetCore.Http; [CLSCompliant( false )] public static class HttpContextExtensions { - /// - /// Gets the API versioning feature associated with the current HTTP context. - /// - /// The current HTTP context to get the API feature for. - /// The current API versioning feature. - public static IApiVersioningFeature ApiVersioningFeature( this HttpContext context ) + extension( HttpContext context ) { - ArgumentNullException.ThrowIfNull( context ); + /// + /// Gets the API versioning feature associated with the current HTTP context. + /// + /// The current API versioning feature. + public IApiVersioningFeature ApiVersioningFeature + { + get + { + ArgumentNullException.ThrowIfNull( context ); - var feature = context.Features.Get(); + var feature = context.Features.Get(); - if ( feature == null ) - { - feature = new ApiVersioningFeature( context ); - context.Features.Set( feature ); + if ( feature == null ) + { + feature = new ApiVersioningFeature( context ); + context.Features.Set( feature ); + } + + return feature; + } } - return feature; - } + /// + /// Gets the current API version requested. + /// + /// The requested API version or null. + /// This method will return null no API version was requested or the requested + /// API version is in an invalid format. + /// Multiple, different API versions were requested. + public ApiVersion? RequestedApiVersion => context.ApiVersioningFeature.RequestedApiVersion; - /// - /// Gets the current API version requested. - /// - /// The current HTTP context to get the API version for. - /// The requested API version or null. - /// This method will return null no API version was requested or the requested - /// API version is in an invalid format. - /// Multiple, different API versions were requested. - public static ApiVersion? GetRequestedApiVersion( this HttpContext context ) => context.ApiVersioningFeature().RequestedApiVersion; - - internal static bool TryGetProblemDetailsService( this HttpContext context, [NotNullWhen( true )] out IProblemDetailsService? problemDetailsService ) - { - problemDetailsService = context.RequestServices.GetService(); - return problemDetailsService is not null; + internal bool TryGetProblemDetailsService( [NotNullWhen( true )] out IProblemDetailsService? problemDetailsService ) + { + problemDetailsService = context.RequestServices.GetService(); + return problemDetailsService is not null; + } } } \ No newline at end of file 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 6ce309bd..dd4b7f30 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpRequestExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpRequestExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; @@ -13,73 +15,77 @@ namespace Microsoft.AspNetCore.Http; [CLSCompliant( false )] 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. - /// The name of the API version route constraint. - /// The raw API version, if retrieved. - /// True if the raw API version was retrieved; otherwise, false. - [EditorBrowsable( EditorBrowsableState.Never )] - public static bool TryGetApiVersionFromPath( - this HttpRequest request, - TList routePatterns, - string constraintName, - [NotNullWhen( true )] out string? apiVersion ) - where TList : IReadOnlyList + extension( HttpRequest request ) { - ArgumentNullException.ThrowIfNull( routePatterns ); - - if ( string.IsNullOrEmpty( constraintName ) || routePatterns.Count == 0 ) - { - apiVersion = default; - return false; - } - - var path = ( request ?? throw new ArgumentNullException( nameof( request ) ) ).Path; - var values = new RouteValueDictionary(); - - // this only applies when versioning by url segment. route values have not been processed - // since no candidates exist yet. we do know the name of the route constraint though. there - // is only one constraint that applies to the api version so we can use that to extract - // the api version from any suitable route template. we're not matching the route template, - // just the raw api version since we don't have a collection of route values to work with. - for ( var i = 0; i < routePatterns.Count; i++ ) + /// + /// Attempts to get the API version from current request path using the provided patterns. + /// + /// The type of read-only list. + /// The read-only list of + /// patterns to evaluate. + /// The name of the API version route constraint. + /// The raw API version, if retrieved. + /// True if the raw API version was retrieved; otherwise, false. + [EditorBrowsable( EditorBrowsableState.Never )] + public bool TryGetApiVersionFromPath( + TList routePatterns, + string constraintName, + [NotNullWhen( true )] out string? apiVersion ) + where TList : IReadOnlyList { - var routePattern = routePatterns[i]; - var defaults = new RouteValueDictionary( routePattern.RequiredValues ); - var matcher = new TemplateMatcher( new( routePattern ), defaults ); + ArgumentNullException.ThrowIfNull( routePatterns ); - values.Clear(); - - if ( !matcher.TryMatch( path, values ) ) + if ( string.IsNullOrEmpty( constraintName ) || routePatterns.Count == 0 ) { - continue; + apiVersion = default; + return false; } - var parameters = routePattern.Parameters; +#pragma warning disable CA2208 // Instantiate argument exceptions correctly + var path = ( request ?? throw new ArgumentNullException( nameof( request ) ) ).Path; +#pragma warning restore CA2208 // Instantiate argument exceptions correctly + var values = new RouteValueDictionary(); - for ( var j = 0; j < parameters.Count; j++ ) + // this only applies when versioning by url segment. route values have not been processed + // since no candidates exist yet. we do know the name of the route constraint though. there + // is only one constraint that applies to the api version so we can use that to extract + // the api version from any suitable route template. we're not matching the route template, + // just the raw api version since we don't have a collection of route values to work with. + for ( var i = 0; i < routePatterns.Count; i++ ) { - var parameter = parameters[j]; - var policies = parameter.ParameterPolicies; + var routePattern = routePatterns[i]; + var defaults = new RouteValueDictionary( routePattern.RequiredValues ); + var matcher = new TemplateMatcher( new( routePattern ), defaults ); + + values.Clear(); - for ( var k = 0; k < policies.Count; k++ ) + if ( !matcher.TryMatch( path, values ) ) { - if ( constraintName.Equals( policies[k].Content, StringComparison.Ordinal ) && - values.TryGetValue( parameter.Name, out apiVersion ) && - !string.IsNullOrEmpty( apiVersion ) ) + continue; + } + + var parameters = routePattern.Parameters; + + for ( var j = 0; j < parameters.Count; j++ ) + { + var parameter = parameters[j]; + var policies = parameter.ParameterPolicies; + + for ( var k = 0; k < policies.Count; k++ ) { - return true; + if ( constraintName.Equals( policies[k].Content, StringComparison.Ordinal ) && + values.TryGetValue( parameter.Name, out apiVersion ) && + !string.IsNullOrEmpty( apiVersion ) ) + { + return true; + } } } } - } - apiVersion = default; - return false; + apiVersion = default; + return false; + } } } \ No newline at end of file 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 d638d1fb..7e219891 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpResponseExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpResponseExtensions.cs @@ -1,9 +1,10 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Http; using Asp.Versioning; -using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using System.Globalization; @@ -17,33 +18,90 @@ public static class HttpResponseExtensions private const string Deprecation = nameof( Deprecation ); private const string Link = nameof( Link ); - /// - /// Writes the sunset policy to the specified HTTP response. - /// /// The HTTP response to write to. - /// The sunset policy to write. - [CLSCompliant( false )] - public static void WriteSunsetPolicy( this HttpResponse response, SunsetPolicy sunsetPolicy ) + extension( HttpResponse response ) { - ArgumentNullException.ThrowIfNull( response ); - ArgumentNullException.ThrowIfNull( sunsetPolicy ); + /// + /// Writes the sunset policy to the specified HTTP response. + /// + /// The sunset policy to write. + public void WriteSunsetPolicy( SunsetPolicy sunsetPolicy ) + { + ArgumentNullException.ThrowIfNull( response ); + ArgumentNullException.ThrowIfNull( sunsetPolicy ); - var headers = response.Headers; + var headers = response.Headers; - if ( headers.ContainsKey( Sunset ) ) - { - // the 'Sunset' header is present, assume the headers have been written. - // this can happen when ApiVersioningOptions.ReportApiVersions = true - // and [ReportApiVersions] are both applied - return; + if ( headers.ContainsKey( Sunset ) ) + { + // the 'Sunset' header is present, assume the headers have been written. + // this can happen when ApiVersioningOptions.ReportApiVersions = true + // and [ReportApiVersions] are both applied + return; + } + + if ( sunsetPolicy.Date.HasValue ) + { + headers[Sunset] = sunsetPolicy.Date.Value.ToString( "r" ); + } + + AddLinkHeaders( headers, sunsetPolicy.Links ); } - if ( sunsetPolicy.Date.HasValue ) + /// + /// Attempts to add the requested API version to the response content type. + /// + /// The name of the API version parameter. + /// This method performs no action if the requested API version is unavailable, + /// the parameter is already set, or the response does not indicate success. + public void AddApiVersionToContentType( string name ) { - headers[Sunset] = sunsetPolicy.Date.Value.ToString( "r" ); - } + ArgumentNullException.ThrowIfNull( response ); + + if ( response.StatusCode < 200 && response.StatusCode > 299 ) + { + return; + } + + var headers = response.GetTypedHeaders(); + var contentType = headers.ContentType; + + if ( contentType == null ) + { + return; + } + + var feature = response.HttpContext.ApiVersioningFeature; + + if ( feature.RawRequestedApiVersion is not string apiVersion ) + { + return; + } + + var parameters = contentType.Parameters; + var parameter = default( NameValueHeaderValue ); + + for ( var i = 0; i < parameters.Count; i++ ) + { + if ( parameters[i].Name.Equals( name, StringComparison.OrdinalIgnoreCase ) ) + { + parameter = parameters[i]; + break; + } + } + + if ( parameter == null ) + { + parameter = new( name ); + parameters.Add( parameter ); + } - AddLinkHeaders( headers, sunsetPolicy.Links ); + if ( !parameter.Value.HasValue ) + { + parameter.Value = new( apiVersion ); + headers.ContentType = contentType; + } + } } /// @@ -86,60 +144,4 @@ private static void AddLinkHeaders( IHeaderDictionary headers, IList - /// Attempts to add the requested API version to the response content type. - /// - /// The extended HTTP response. - /// The name of the API version parameter. - /// This method performs no action if the requested API version is unavailable, - /// the parameter is already set, or the response does not indicate success. - public static void AddApiVersionToContentType( this HttpResponse response, string name ) - { - ArgumentNullException.ThrowIfNull( response ); - - if ( response.StatusCode < 200 && response.StatusCode > 299 ) - { - return; - } - - var headers = response.GetTypedHeaders(); - var contentType = headers.ContentType; - - if ( contentType == null ) - { - return; - } - - var feature = response.HttpContext.ApiVersioningFeature(); - - if ( feature.RawRequestedApiVersion is not string apiVersion ) - { - return; - } - - var parameters = contentType.Parameters; - var parameter = default( NameValueHeaderValue ); - - for ( var i = 0; i < parameters.Count; i++ ) - { - if ( parameters[i].Name.Equals( name, StringComparison.OrdinalIgnoreCase ) ) - { - parameter = parameters[i]; - break; - } - } - - if ( parameter == null ) - { - parameter = new( name ); - parameters.Add( parameter ); - } - - if ( !parameter.Value.HasValue ) - { - parameter.Value = new( apiVersion ); - headers.ContentType = contentType; - } - } } \ 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 index a88a8e13..f3ebc666 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/IApiVersionSelectorExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/IApiVersionSelectorExtensions.cs @@ -10,16 +10,19 @@ namespace Asp.Versioning; [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 ) + extension( IApiVersionSelector selector ) { - ArgumentNullException.ThrowIfNull( selector ); - var context = new DefaultHttpContext(); - return selector.SelectVersion( context.Request, model ); + /// + /// Selects an API version given the specified API version information. + /// + /// The model to select the version from. + /// The selected API version. + public ApiVersion SelectVersion( 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/QueryStringApiVersionReader.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/QueryStringApiVersionReader.cs index 144f5b34..900ed70f 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/QueryStringApiVersionReader.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/QueryStringApiVersionReader.cs @@ -20,7 +20,7 @@ public virtual IReadOnlyList Read( HttpRequest request ) if ( count == 0 ) { - return Array.Empty(); + return []; } var version = default( string ); @@ -69,6 +69,6 @@ public virtual IReadOnlyList Read( HttpRequest request ) return version == null ? [] : [version]; } - return versions.ToArray(); + return [.. versions]; } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ReleaseNotes.txt b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ReleaseNotes.txt index 1f7dd5cb..5f282702 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ReleaseNotes.txt +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ReleaseNotes.txt @@ -1,5 +1 @@ -Fixed assuming default version with version-neutral ([#1083](https://github.com/dotnet/aspnet-api-versioning/issues/1083)) -Fixed missing 'code' member on error responses ([#1091](https://github.com/dotnet/aspnet-api-versioning/issues/1091)) -Fixed invalidation of valid routing candidates ([#1101](https://github.com/dotnet/aspnet-api-versioning/issues/1101)) -Fixed reporting versions when request is unspecified or malformed ([#1120](https://github.com/dotnet/aspnet-api-versioning/issues/1120)) -Fixed replacing DI registered IProblemDetailsWriter in-place ([#1135](https://github.com/dotnet/aspnet-api-versioning/issues/1135)) \ No newline at end of file + \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Rfc7231ProblemDetailsWriter.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Rfc7231ProblemDetailsWriter.cs deleted file mode 100644 index 3b380197..00000000 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Rfc7231ProblemDetailsWriter.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. - -namespace Asp.Versioning; - -using Microsoft.AspNetCore.Http; -using Microsoft.Net.Http.Headers; - -// REF: https://github.com/dotnet/aspnetcore/blob/release/7.0/src/Http/Http.Extensions/src/DefaultProblemDetailsWriter.cs -internal sealed class Rfc7231ProblemDetailsWriter : IProblemDetailsWriter -{ - private static readonly MediaTypeHeaderValue jsonMediaType = new( "application/json" ); - private static readonly MediaTypeHeaderValue problemDetailsJsonMediaType = new( ProblemDetailsDefaults.MediaType.Json ); - private readonly IProblemDetailsWriter decorated; - - public Rfc7231ProblemDetailsWriter( IProblemDetailsWriter decorated ) => this.decorated = decorated; - - public bool CanWrite( ProblemDetailsContext context ) - { - if ( decorated.CanWrite( context ) ) - { - return true; - } - - var httpContext = context.HttpContext; - var accept = httpContext.Request.Headers.Accept; - - // REF: https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2 - if ( accept.Count == 0 ) - { - return true; - } - - var acceptHeader = httpContext.Request.GetTypedHeaders().Accept; - - for ( var i = 0; i < acceptHeader.Count; i++ ) - { - var acceptHeaderValue = acceptHeader[i]; - - // 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; - } - } - - return false; - } - - public ValueTask WriteAsync( ProblemDetailsContext context ) => decorated.WriteAsync( context ); -} \ No newline at end of file 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 80eb5456..bb9632b1 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/AmbiguousApiVersionEndpoint.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/AmbiguousApiVersionEndpoint.cs @@ -3,7 +3,6 @@ namespace Asp.Versioning.Routing; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System.Globalization; using static Microsoft.AspNetCore.Http.EndpointMetadataCollection; @@ -17,9 +16,12 @@ internal AmbiguousApiVersionEndpoint( ILogger logger ) private static Task OnExecute( HttpContext context, ILogger logger ) { - var apiVersions = context.ApiVersioningFeature().RawRequestedApiVersions; + var apiVersions = context.ApiVersioningFeature.RawRequestedApiVersions; +#pragma warning disable CA1873 logger.ApiVersionAmbiguous( [.. apiVersions] ); +#pragma warning restore CA1873 + context.Response.StatusCode = StatusCodes.Status400BadRequest; if ( !context.TryGetProblemDetailsService( out var problemDetails ) ) 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 abdaa172..c4d07418 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionLinkGenerator.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionLinkGenerator.cs @@ -76,7 +76,7 @@ private static void AddApiVersionRouteValueIfNecessary( HttpContext httpContext, ArgumentNullException.ThrowIfNull( httpContext ); ArgumentNullException.ThrowIfNull( values ); - var feature = httpContext.ApiVersioningFeature(); + var feature = httpContext.ApiVersioningFeature; var key = feature.RouteParameter; if ( string.IsNullOrEmpty( key ) ) 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 a8a12401..137e598e 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionMatcherPolicy.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionMatcherPolicy.cs @@ -9,7 +9,6 @@ namespace Asp.Versioning.Routing; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Buffers; using System.Collections.Frozen; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; @@ -83,7 +82,7 @@ public async Task ApplyAsync( HttpContext httpContext, CandidateSet candidates ) ArgumentNullException.ThrowIfNull( httpContext ); ArgumentNullException.ThrowIfNull( candidates ); - var feature = httpContext.ApiVersioningFeature(); + var feature = httpContext.ApiVersioningFeature; var apiVersion = feature.RequestedApiVersion; if ( apiVersion == null && Options.AssumeDefaultVersionWhenUnspecified ) @@ -354,11 +353,11 @@ private static void Collate( return default; } - model = new( Enumerable.Empty(), deprecated ); + model = new( [], deprecated ); } else if ( deprecated == null ) { - model = new( supported, Enumerable.Empty() ); + model = new( supported, [] ); } else { @@ -476,8 +475,8 @@ private sealed class ApiVersionCollator( IEnumerable providers, IOptions options ) { - private readonly IApiVersionMetadataCollationProvider[] providers = providers.ToArray(); - private readonly object syncRoot = new(); + private readonly IApiVersionMetadataCollationProvider[] providers = [.. providers]; + private readonly Lock syncRoot = new(); private IReadOnlyList? items; private int version; @@ -490,7 +489,7 @@ public IReadOnlyList Items return items; } - lock ( syncRoot ) + using ( syncRoot.EnterScope() ) { var currentVersion = ComputeVersion(); @@ -525,7 +524,7 @@ public IReadOnlyList Items versions.Add( options.Value.DefaultApiVersion ); } - items = versions.ToArray(); + items = [.. versions]; version = currentVersion; } 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 fc1e8866..29c93e3b 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionPolicyJumpTable.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionPolicyJumpTable.cs @@ -44,7 +44,7 @@ internal ApiVersionPolicyJumpTable( public override int GetDestination( HttpContext httpContext ) { var request = httpContext.Request; - var feature = httpContext.ApiVersioningFeature(); + var feature = httpContext.ApiVersioningFeature; var apiVersions = new List( capacity: feature.RawRequestedApiVersions.Count + 1 ); var addedFromUrl = 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 6b4cfd76..ab7c17f3 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionRouteConstraint.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionRouteConstraint.cs @@ -43,7 +43,7 @@ public bool Match( HttpContext? httpContext, IRouter? route, string routeKey, Ro ArgumentNullException.ThrowIfNull( httpContext ); var parser = httpContext.RequestServices.GetRequiredService(); - var feature = httpContext.ApiVersioningFeature(); + var feature = httpContext.ApiVersioningFeature; feature.RouteParameter = routeKey; feature.RawRequestedApiVersion = value; 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 9aeae37e..430159ba 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeBuilder.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeBuilder.cs @@ -44,7 +44,7 @@ public EdgeBuilder( public IReadOnlyList Build() { routePatterns.TrimExcess(); - return edges.Select( edge => new PolicyNodeEdge( edge.Key, edge.Value ) ).ToArray(); + return [.. edges.Select( edge => new PolicyNodeEdge( edge.Key, edge.Value ) )]; } public void Add( RouteEndpoint endpoint ) diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeKey.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeKey.cs index bdc5c835..4e24067a 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeKey.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeKey.cs @@ -86,6 +86,6 @@ public override string ToString() private static class Set { - public static readonly HashSet Empty = new( capacity: 0 ); + public static readonly HashSet Empty = []; } } \ No newline at end of file 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 61fccfd3..1b1a50ea 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EndpointProblem.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EndpointProblem.cs @@ -63,8 +63,8 @@ internal static Task UnsupportedApiVersion( var detail = string.Format( CultureInfo.CurrentCulture, Format.VersionedResourceNotSupported, - new Uri( context.Request.GetDisplayUrl() ).SafeFullPath(), - context.ApiVersioningFeature().RawRequestedApiVersion ); + new Uri( context.Request.GetDisplayUrl() ).SafePath, + context.ApiVersioningFeature.RawRequestedApiVersion ); return problemDetails.TryWriteAsync( New( context, Unsupported, detail ) ).AsTask(); } 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 5c777eb6..fbafbd94 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/MalformedApiVersionEndpoint.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/MalformedApiVersionEndpoint.cs @@ -17,7 +17,7 @@ internal MalformedApiVersionEndpoint( ILogger logger, ApiVersioningOptions optio private static Task OnExecute( HttpContext context, ApiVersioningOptions options, ILogger logger ) { - var requestedVersion = context.ApiVersioningFeature().RawRequestedApiVersion; + var requestedVersion = context.ApiVersioningFeature.RawRequestedApiVersion; logger.ApiVersionInvalid( requestedVersion ); context.Response.StatusCode = StatusCodes.Status400BadRequest; @@ -32,7 +32,7 @@ private static Task OnExecute( HttpContext context, ApiVersioningOptions options var detail = string.Format( CultureInfo.CurrentCulture, Format.VersionedResourceNotSupported, - new Uri( context.Request.GetDisplayUrl() ).SafeFullPath(), + new Uri( context.Request.GetDisplayUrl() ).SafePath, requestedVersion ); return problemDetails.TryWriteAsync( diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/NotAcceptableEndpoint.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/NotAcceptableEndpoint.cs index 51731b1e..5ec963e6 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/NotAcceptableEndpoint.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/NotAcceptableEndpoint.cs @@ -16,5 +16,6 @@ internal NotAcceptableEndpoint( ApiVersioningOptions options ) options, StatusCodes.Status406NotAcceptable ), Empty, - Name ) { } + Name ) + { } } \ No newline at end of file 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 fa467989..a1689bb0 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternExtensions.cs @@ -12,37 +12,40 @@ namespace Asp.Versioning.Routing; [EditorBrowsable( EditorBrowsableState.Never )] public static class RoutePatternExtensions { - /// - /// Determines whether the route pattern contains the specified constraint name. - /// /// The route pattern to evaluate. - /// The name of the constraint to find. - /// True if the has the ; otherwise, false. - public static bool HasVersionConstraint( this RoutePattern routePattern, string constraintName ) + extension( RoutePattern routePattern ) { - ArgumentNullException.ThrowIfNull( routePattern ); - - if ( string.IsNullOrEmpty( constraintName ) ) + /// + /// Determines whether the route pattern contains the specified constraint name. + /// + /// The name of the constraint to find. + /// True if the route pattern has the ; otherwise, false. + public bool HasVersionConstraint( string constraintName ) { - return false; - } + ArgumentNullException.ThrowIfNull( routePattern ); - var parameters = routePattern.Parameters; + if ( string.IsNullOrEmpty( constraintName ) ) + { + return false; + } - for ( var i = 0; i < parameters.Count; i++ ) - { - var parameter = parameters[i]; - var policies = parameter.ParameterPolicies; + var parameters = routePattern.Parameters; - for ( var j = 0; j < policies.Count; j++ ) + for ( var i = 0; i < parameters.Count; i++ ) { - if ( constraintName.Equals( policies[j].Content, StringComparison.Ordinal ) ) + var parameter = parameters[i]; + var policies = parameter.ParameterPolicies; + + for ( var j = 0; j < policies.Count; j++ ) { - return true; + if ( constraintName.Equals( policies[j].Content, StringComparison.Ordinal ) ) + { + return true; + } } } - } - return false; + return false; + } } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/UnsupportedMediaTypeEndpoint.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/UnsupportedMediaTypeEndpoint.cs index 1e7492e6..22ec254c 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/UnsupportedMediaTypeEndpoint.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/UnsupportedMediaTypeEndpoint.cs @@ -16,5 +16,6 @@ internal UnsupportedMediaTypeEndpoint( ApiVersioningOptions options ) options, StatusCodes.Status415UnsupportedMediaType ), Empty, - Name ) { } + Name ) + { } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/UrlSegmentApiVersionReader.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/UrlSegmentApiVersionReader.cs index ba9eec82..54f4e6e4 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/UrlSegmentApiVersionReader.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/UrlSegmentApiVersionReader.cs @@ -17,11 +17,11 @@ public virtual IReadOnlyList Read( HttpRequest request ) if ( reentrant ) { - return Array.Empty(); + return []; } reentrant = true; - var feature = request.HttpContext.ApiVersioningFeature(); + var feature = request.HttpContext.ApiVersioningFeature; var versions = feature.RawRequestedApiVersions; reentrant = false; 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 7f17661f..243a3122 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiDescriptionExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiDescriptionExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Mvc.ApiExplorer; using Asp.Versioning; @@ -16,208 +18,206 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer; [CLSCompliant( false )] public static class ApiDescriptionExtensions { - /// - /// Gets the API version associated with the API description, if any. - /// /// The API description to get the API version for. - /// The associated API version or null. - public static ApiVersion? GetApiVersion( this ApiDescription apiDescription ) => apiDescription.GetProperty(); - - /// - /// Gets the API sunset policy associated with the API description, if any. - /// - /// The API description to get the sunset policy for. - /// The defined sunset policy defined for the API or null. - public static SunsetPolicy? GetSunsetPolicy( this ApiDescription apiDescription ) => apiDescription.GetProperty(); - - /// - /// Gets the API deprecation policy associated with the API description, if any. - /// - /// The API description to get the deprecation policy for. - /// The defined deprecation policy defined for the API or null. - public static DeprecationPolicy? GetDeprecationPolicy( this ApiDescription apiDescription ) => apiDescription.GetProperty(); - - /// - /// Gets a value indicating whether the associated API description is deprecated. - /// - /// The API description to evaluate. - /// True if the API description is deprecated; otherwise, false. - public static bool IsDeprecated( this ApiDescription apiDescription ) + extension( ApiDescription apiDescription ) { - ArgumentNullException.ThrowIfNull( apiDescription ); + /// + /// Gets or sets the API version associated with the API description, if any. + /// + /// The associated API version or null. + /// Setting this property is meant for infrastructure and should not be used by application code. + public ApiVersion? ApiVersion + { + get => apiDescription.GetProperty(); - var metatadata = apiDescription.ActionDescriptor.GetApiVersionMetadata(); + [EditorBrowsable( EditorBrowsableState.Never )] + set => apiDescription.SetProperty( value ); + } - if ( metatadata.IsApiVersionNeutral ) + /// + /// Gets or sets the API sunset policy associated with the API description, if any. + /// + /// The defined sunset policy defined for the API or null. + /// Setting this property is meant for infrastructure and should not be used by application code. + public SunsetPolicy? SunsetPolicy { - return false; - } + get => apiDescription.GetProperty(); - var apiVersion = apiDescription.GetApiVersion(); - var model = metatadata.Map( Explicit | Implicit ); + [EditorBrowsable( EditorBrowsableState.Never )] + set => apiDescription.SetProperty( value ); + } - return model.DeprecatedApiVersions.Contains( apiVersion ); - } + /// + /// Gets or sets the API deprecation policy associated with the API description, if any. + /// + /// The defined deprecation policy defined for the API or null. + /// Setting this property is meant for infrastructure and should not be used by application code. + public DeprecationPolicy? DeprecationPolicy + { + get => apiDescription.GetProperty(); - /// - /// Sets the API version associated with the API description. - /// - /// The API description to set the API version for. - /// The associated API version. - /// This API is meant for infrastructure and should not be used by application code. - [EditorBrowsable( EditorBrowsableState.Never )] - public static void SetApiVersion( this ApiDescription apiDescription, ApiVersion apiVersion ) => apiDescription.SetProperty( apiVersion ); - - /// - /// Sets the API sunset policy associated with the API description. - /// - /// The API description to set the sunset policy for. - /// The associated sunset policy. - /// This API is meant for infrastructure and should not be used by application code. - [EditorBrowsable( EditorBrowsableState.Never )] - public static void SetSunsetPolicy( this ApiDescription apiDescription, SunsetPolicy sunsetPolicy ) => apiDescription.SetProperty( sunsetPolicy ); - - /// - /// Sets the API deprecation policy associated with the API description. - /// - /// The API description to set the sunset policy for. - /// The associated deprecation policy. - /// This API is meant for infrastructure and should not be used by application code. - [EditorBrowsable( EditorBrowsableState.Never )] - public static void SetDeprecationPolicy( this ApiDescription apiDescription, DeprecationPolicy deprecationPolicy ) => apiDescription.SetProperty( deprecationPolicy ); - - /// - /// Attempts to update the relate path of the specified API description and remove the corresponding parameter according to the specified options. - /// - /// The API description to attempt to update. - /// The current API Explorer options. - /// True if the API description was updated; otherwise, false. - public static bool TryUpdateRelativePathAndRemoveApiVersionParameter( this ApiDescription apiDescription, ApiExplorerOptions options ) - { - ArgumentNullException.ThrowIfNull( apiDescription ); - ArgumentNullException.ThrowIfNull( options ); + [EditorBrowsable( EditorBrowsableState.Never )] + set => apiDescription.SetProperty( value ); + } - if ( !options.SubstituteApiVersionInUrl ) + /// + /// Gets a value indicating whether the associated API description is deprecated. + /// + /// True if the API description is deprecated; otherwise, false. + public bool IsDeprecated { - return false; - } + get + { + ArgumentNullException.ThrowIfNull( apiDescription ); - var relativePath = apiDescription.RelativePath; + var metatadata = apiDescription.ActionDescriptor.ApiVersionMetadata; - if ( string.IsNullOrEmpty( relativePath ) ) - { - return false; + if ( metatadata.IsApiVersionNeutral ) + { + if ( apiDescription.DeprecationPolicy is { } policy ) + { + return policy.IsEffective( DateTimeOffset.Now ); + } + + return false; + } + + var apiVersion = apiDescription.ApiVersion; + var model = metatadata.Map( Explicit | Implicit ); + + return model.DeprecatedApiVersions.Contains( apiVersion ); + } } - if ( apiDescription.GetApiVersion() is not ApiVersion apiVersion ) + /// + /// Attempts to update the relate path of the specified API description and remove the corresponding parameter according to the specified options. + /// + /// The current API Explorer options. + /// True if the API description was updated; otherwise, false. + public bool TryUpdateRelativePathAndRemoveApiVersionParameter( ApiExplorerOptions options ) { - return false; - } + ArgumentNullException.ThrowIfNull( apiDescription ); + ArgumentNullException.ThrowIfNull( options ); - var parameters = apiDescription.ParameterDescriptions; - var parameter = parameters.FirstOrDefault( pd => pd.Source == Path && pd.ModelMetadata?.DataTypeName == nameof( ApiVersion ) ); + if ( !options.SubstituteApiVersionInUrl ) + { + return false; + } - if ( parameter == null ) - { - return false; - } + var relativePath = apiDescription.RelativePath; - Span token = stackalloc char[parameter.Name.Length + 2]; + if ( string.IsNullOrEmpty( relativePath ) ) + { + return false; + } - token[0] = '{'; - token[^1] = '}'; - parameter.Name.AsSpan().CopyTo( token.Slice( 1, parameter.Name.Length ) ); + if ( apiDescription.ApiVersion is not { } apiVersion ) + { + return false; + } - var value = apiVersion.ToString( options.SubstitutionFormat, CultureInfo.InvariantCulture ); - var newRelativePath = relativePath.Replace( token.ToString(), value, StringComparison.Ordinal ); + var parameters = apiDescription.ParameterDescriptions; + var parameter = parameters.FirstOrDefault( pd => pd.Source == Path && pd.ModelMetadata?.DataTypeName == nameof( ApiVersion ) ); - if ( relativePath == newRelativePath ) - { - return false; - } + if ( parameter == null ) + { + return false; + } - apiDescription.RelativePath = newRelativePath; - parameters.Remove( parameter ); - return true; - } + Span token = stackalloc char[parameter.Name.Length + 2]; - /// - /// Creates a shallow copy of the current API description. - /// - /// The API description to create a copy of. - /// A new API description. - public static ApiDescription Clone( this ApiDescription apiDescription ) - { - ArgumentNullException.ThrowIfNull( apiDescription ); + token[0] = '{'; + token[^1] = '}'; + parameter.Name.AsSpan().CopyTo( token.Slice( 1, parameter.Name.Length ) ); - var clone = new ApiDescription() - { - ActionDescriptor = apiDescription.ActionDescriptor, - GroupName = apiDescription.GroupName, - HttpMethod = apiDescription.HttpMethod, - RelativePath = apiDescription.RelativePath, - }; + var value = apiVersion.ToString( options.SubstitutionFormat, CultureInfo.InvariantCulture ); + var newRelativePath = relativePath.Replace( token.ToString(), value, StringComparison.Ordinal ); - foreach ( var property in apiDescription.Properties ) - { - clone.Properties.Add( property ); - } + if ( relativePath == newRelativePath ) + { + return false; + } - CloneList( apiDescription.ParameterDescriptions, clone.ParameterDescriptions, Clone ); - CloneList( apiDescription.SupportedRequestFormats, clone.SupportedRequestFormats, Clone ); - CloneList( apiDescription.SupportedResponseTypes, clone.SupportedResponseTypes, Clone ); + apiDescription.RelativePath = newRelativePath; + parameters.Remove( parameter ); + return true; + } - return clone; + /// + /// Creates a shallow copy of the current API description. + /// + /// A new API description. + public ApiDescription Clone() + { + ArgumentNullException.ThrowIfNull( apiDescription ); + + var clone = new ApiDescription() + { + ActionDescriptor = apiDescription.ActionDescriptor, + GroupName = apiDescription.GroupName, + HttpMethod = apiDescription.HttpMethod, + RelativePath = apiDescription.RelativePath, + }; + + foreach ( var property in apiDescription.Properties ) + { + clone.Properties.Add( property ); + } + + CloneList( apiDescription.ParameterDescriptions, clone.ParameterDescriptions, Clone ); + CloneList( apiDescription.SupportedRequestFormats, clone.SupportedRequestFormats, Clone ); + CloneList( apiDescription.SupportedResponseTypes, clone.SupportedResponseTypes, Clone ); + + return clone; + } } - internal static ApiRequestFormat Clone( this ApiRequestFormat requestFormat ) + extension( ApiRequestFormat requestFormat ) { - return new() + internal ApiRequestFormat Clone() => new() { Formatter = requestFormat.Formatter, MediaType = requestFormat.MediaType, }; } - internal static ApiResponseType Clone( this ApiResponseType responseType ) + extension( ApiResponseType responseType ) { - var clone = new ApiResponseType() + internal ApiResponseType Clone() { - IsDefaultResponse = responseType.IsDefaultResponse, - ModelMetadata = responseType.ModelMetadata, - StatusCode = responseType.StatusCode, - Type = responseType.Type, - }; - - CloneList( responseType.ApiResponseFormats, clone.ApiResponseFormats, Clone ); - - return clone; + var clone = new ApiResponseType() + { + Description = responseType.Description, + IsDefaultResponse = responseType.IsDefaultResponse, + ModelMetadata = responseType.ModelMetadata, + StatusCode = responseType.StatusCode, + Type = responseType.Type, + }; + + CloneList( responseType.ApiResponseFormats, clone.ApiResponseFormats, Clone ); + + return clone; + } } - private static ApiParameterDescription Clone( ApiParameterDescription parameterDescription ) + private static ApiParameterDescription Clone( ApiParameterDescription parameterDescription ) => new() { - return new() - { - BindingInfo = parameterDescription.BindingInfo, - IsRequired = parameterDescription.IsRequired, - DefaultValue = parameterDescription.DefaultValue, - ModelMetadata = parameterDescription.ModelMetadata, - Name = parameterDescription.Name, - ParameterDescriptor = parameterDescription.ParameterDescriptor, - RouteInfo = parameterDescription.RouteInfo, - Source = parameterDescription.Source, - Type = parameterDescription.Type, - }; - } - - private static ApiResponseFormat Clone( ApiResponseFormat responseFormat ) + BindingInfo = parameterDescription.BindingInfo, + IsRequired = parameterDescription.IsRequired, + DefaultValue = parameterDescription.DefaultValue, + ModelMetadata = parameterDescription.ModelMetadata, + Name = parameterDescription.Name, + ParameterDescriptor = parameterDescription.ParameterDescriptor, + RouteInfo = parameterDescription.RouteInfo, + Source = parameterDescription.Source, + Type = parameterDescription.Type, + }; + + private static ApiResponseFormat Clone( ApiResponseFormat responseFormat ) => new() { - return new() - { - Formatter = responseFormat.Formatter, - MediaType = responseFormat.MediaType, - }; - } + Formatter = responseFormat.Formatter, + MediaType = responseFormat.MediaType, + }; private static void CloneList( IList source, IList destintation, Func clone ) { 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 d625fb23..a1685df7 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionDescriptionProviderFactory.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionDescriptionProviderFactory.cs @@ -2,10 +2,9 @@ #pragma warning disable CA1812 // Avoid uninstantiated internal classes -namespace Microsoft.AspNetCore.Builder; +namespace Asp.Versioning.ApiExplorer; using Asp.Versioning; -using Asp.Versioning.ApiExplorer; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; @@ -26,7 +25,7 @@ public ApiVersionDescriptionProviderFactory( { this.sunsetPolicyManager = sunsetPolicyManager; this.deprecationPolicyManager = deprecationPolicyManager; - this.providers = providers.ToArray(); + this.providers = [.. providers]; this.endpointInspector = endpointInspector; this.options = options; } @@ -40,6 +39,6 @@ public IApiVersionDescriptionProvider Create( EndpointDataSource endpointDataSou collators.AddRange( providers ); - return new DefaultApiVersionDescriptionProvider( collators, sunsetPolicyManager, deprecationPolicyManager, options ); + return new GroupedApiVersionDescriptionProvider( collators, sunsetPolicyManager, deprecationPolicyManager, options ); } } \ No newline at end of file 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 8ebe5895..f0fe36b4 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionParameterDescriptionContext.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionParameterDescriptionContext.cs @@ -73,7 +73,7 @@ protected bool IsApiVersionNeutral { if ( !versionNeutral.HasValue ) { - versionNeutral = ApiDescription.ActionDescriptor.GetApiVersionMetadata().IsApiVersionNeutral; + versionNeutral = ApiDescription.ActionDescriptor.ApiVersionMetadata.IsApiVersionNeutral; } return versionNeutral.Value; @@ -458,7 +458,7 @@ private static bool FirstParameterIsOptional( } var mapping = ApiVersionMapping.Explicit | ApiVersionMapping.Implicit; - var model = apiDescription.ActionDescriptor.GetApiVersionMetadata().Map( mapping ); + var model = apiDescription.ActionDescriptor.ApiVersionMetadata.Map( mapping ); var defaultApiVersion = options.ApiVersionSelector.SelectVersion( model ); return apiVersion == defaultApiVersion; 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 a7e5e36c..f57ca019 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,8 +1,8 @@  - 8.1.1 - 8.1.0.0 + 10.0.0 + 10.0.0.0 $(DefaultTargetFramework) Asp.Versioning.ApiExplorer ASP.NET Core API Versioning API Explorer diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs deleted file mode 100644 index e018222e..00000000 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. - -namespace Asp.Versioning.ApiExplorer; - -using Asp.Versioning.ApiExplorer.Internal; -using Microsoft.Extensions.Options; - -/// -/// Represents the default implementation of an object that discovers and describes the API version information within an application. -/// -[CLSCompliant( false )] -public class DefaultApiVersionDescriptionProvider : IApiVersionDescriptionProvider -{ - private readonly ApiVersionDescriptionCollection collection; - private readonly IOptions options; - - /// - /// Initializes a new instance of the class. - /// - /// The sequence of - /// API version metadata collation providers.. - /// The manager used to resolve sunset policies. - /// The manager used to resolve deprecation policies. - /// The container of configured - /// API explorer options. - public DefaultApiVersionDescriptionProvider( - IEnumerable providers, - IPolicyManager sunsetPolicyManager, - IPolicyManager deprecationPolicyManager, - IOptions apiExplorerOptions ) - { - collection = new( Describe, providers ?? throw new ArgumentNullException( nameof( providers ) ) ); - SunsetPolicyManager = sunsetPolicyManager; - DeprecationPolicyManager = deprecationPolicyManager; - options = apiExplorerOptions; - } - - /// - /// Gets the manager used to resolve sunset policies. - /// - /// The associated sunset policy manager. - protected IPolicyManager SunsetPolicyManager { get; } - - /// - /// Gets the manager used to resolve deprecation policies. - /// - /// The associated deprecation policy manager. - protected IPolicyManager DeprecationPolicyManager { get; } - - /// - /// Gets the options associated with the API explorer. - /// - /// The current API explorer options. - protected ApiExplorerOptions Options => options.Value; - - /// - public IReadOnlyList ApiVersionDescriptions => collection.Items; - - /// - /// Provides a list of API version descriptions from a list of application API version metadata. - /// - /// The read-only list of API version metadata - /// within the application. - /// A read-only list of API version descriptions. - protected virtual IReadOnlyList Describe( IReadOnlyList metadata ) - { - 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 ) - { - return DescriptionProvider.Describe( groupedMetadata, SunsetPolicyManager, DeprecationPolicyManager, Options ); - } - - return Array.Empty(); - } - - private sealed class GroupedApiVersionMetadata : - ApiVersionMetadata, - IEquatable, - IGroupedApiVersionMetadata, - IGroupedApiVersionMetadataFactory - { - private GroupedApiVersionMetadata( string? groupName, ApiVersionMetadata metadata ) - : base( metadata ) => GroupName = groupName; - - public string? GroupName { get; } - - static GroupedApiVersionMetadata IGroupedApiVersionMetadataFactory.New( - string? groupName, - ApiVersionMetadata metadata ) => new( groupName, metadata ); - - public bool Equals( GroupedApiVersionMetadata? other ) => - other is not null && other.GetHashCode() == GetHashCode(); - - public override bool Equals( object? obj ) => - obj is not null && - GetType().Equals( obj.GetType() ) && - GetHashCode() == obj.GetHashCode(); - - public override int GetHashCode() - { - var hash = default( HashCode ); - - if ( !string.IsNullOrEmpty( GroupName ) ) - { - hash.Add( GroupName, StringComparer.Ordinal ); - } - - hash.Add( base.GetHashCode() ); - - return hash.ToHashCode(); - } - } -} \ No newline at end of file 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 d8d99d1e..f577ebf4 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 @@ -1,15 +1,17 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.Extensions.DependencyInjection; using Asp.Versioning; using Asp.Versioning.ApiExplorer; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using System.Diagnostics.CodeAnalysis; using static ServiceDescriptor; /// @@ -18,32 +20,38 @@ namespace Microsoft.Extensions.DependencyInjection; [CLSCompliant( false )] public static class IApiVersioningBuilderExtensions { - /// - /// Adds the API versioning extensions for the API Explorer. - /// - /// The extended API versioning builder. - /// The original . - public static IApiVersioningBuilder AddApiExplorer( this IApiVersioningBuilder builder ) - { - ArgumentNullException.ThrowIfNull( builder ); - AddApiExplorerServices( builder ); - return builder; - } + private const string TrimmingMessage = "MVC does not currently support trimming or native AOT. https://aka.ms/aspnet/trimming"; - /// - /// Adds the API versioning extensions for the API Explorer. - /// /// The extended API versioning builder. - /// An action used to configure the provided options. /// The original . - public static IApiVersioningBuilder AddApiExplorer( this IApiVersioningBuilder builder, Action setupAction ) + extension( IApiVersioningBuilder builder ) { - ArgumentNullException.ThrowIfNull( builder ); - AddApiExplorerServices( builder ); - builder.Services.Configure( setupAction ); - return builder; + /// + /// Adds the API versioning extensions for the API Explorer. + /// + [RequiresUnreferencedCode( TrimmingMessage )] + public IApiVersioningBuilder AddApiExplorer() + { + ArgumentNullException.ThrowIfNull( builder ); + AddApiExplorerServices( builder ); + return builder; + } + + /// + /// Adds the API versioning extensions for the API Explorer. + /// + /// An action used to configure the provided options. + [RequiresUnreferencedCode( TrimmingMessage )] + public IApiVersioningBuilder AddApiExplorer( Action setupAction ) + { + ArgumentNullException.ThrowIfNull( builder ); + AddApiExplorerServices( builder ); + builder.Services.Configure( setupAction ); + return builder; + } } + [RequiresUnreferencedCode( TrimmingMessage )] private static void AddApiExplorerServices( IApiVersioningBuilder builder ) { builder.AddMvc(); diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/GroupedApiVersionDescriptionProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/GroupedApiVersionDescriptionProvider.cs index 6e7fa38e..6dff1e00 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/GroupedApiVersionDescriptionProvider.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/GroupedApiVersionDescriptionProvider.cs @@ -2,16 +2,18 @@ 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. +/// Represents the default implementation of an object that discovers and describes the API version information within +/// an application. /// [CLSCompliant( false )] public class GroupedApiVersionDescriptionProvider : IApiVersionDescriptionProvider { - private readonly ApiVersionDescriptionCollection collection; + private readonly ApiVersionDescriptionCollection collection; private readonly IOptions options; /// @@ -72,11 +74,7 @@ protected virtual IReadOnlyList Describe( IReadOnlyList /// Represents the API version metadata applied to an endpoint with an optional group name. /// - protected class GroupedApiVersionMetadata : - ApiVersionMetadata, - IEquatable, - IGroupedApiVersionMetadata, - IGroupedApiVersionMetadataFactory + protected class GroupedApiVersionMetadata : ApiVersionMetadata, IEquatable { /// /// Initializes a new instance of the class. @@ -92,10 +90,6 @@ public GroupedApiVersionMetadata( string? groupName, ApiVersionMetadata metadata /// The associated group name, if any. public string? GroupName { get; } - static GroupedApiVersionMetadata IGroupedApiVersionMetadataFactory.New( - string? groupName, - ApiVersionMetadata metadata ) => new( groupName, metadata ); - /// public bool Equals( GroupedApiVersionMetadata? other ) => other is not null && other.GetHashCode() == GetHashCode(); @@ -121,4 +115,207 @@ public override int GetHashCode() return hash.ToHashCode(); } } + + private record struct GroupedApiVersion( string? GroupName, ApiVersion ApiVersion ); + + private sealed class ApiVersionDescriptionCollection( + Func, IReadOnlyList> describe, + IEnumerable collators ) + { + private readonly Lock syncRoot = new(); + private readonly Func, IReadOnlyList> describe = describe; + private readonly IApiVersionMetadataCollationProvider[] collators = [.. collators]; + private IReadOnlyList? items; + private int version; + + public IReadOnlyList Items + { + get + { + if ( items is not null && version == ComputeVersion() ) + { + return items; + } + + using ( syncRoot.EnterScope() ) + { + var currentVersion = ComputeVersion(); + + if ( items is not null && version == currentVersion ) + { + return items; + } + + var context = new ApiVersionMetadataCollationContext(); + + for ( var i = 0; i < collators.Length; i++ ) + { + collators[i].Execute( context ); + } + + var results = context.Results; + var metadata = new GroupedApiVersionMetadata[results.Count]; + + for ( var i = 0; i < metadata.Length; i++ ) + { + metadata[i] = new( context.Results.GroupName( i ), results[i] ); + } + + items = describe( metadata ); + version = currentVersion; + } + + return items; + } + } + + private int ComputeVersion() => + collators.Length switch + { + 0 => 0, + 1 => collators[0].Version, + _ => ComputeVersion( collators ), + }; + + private static int ComputeVersion( IApiVersionMetadataCollationProvider[] providers ) + { + var hash = default( HashCode ); + + for ( var i = 0; i < providers.Length; i++ ) + { + hash.Add( providers[i].Version ); + } + + return hash.ToHashCode(); + } + } + + private sealed class ApiVersionDescriptionComparer : IComparer + { + public int Compare( ApiVersionDescription? x, ApiVersionDescription? y ) + { + if ( x is null ) + { + return y is null ? 0 : -1; + } + + if ( y is null ) + { + return 1; + } + + var result = x.ApiVersion.CompareTo( y.ApiVersion ); + + if ( result == 0 ) + { + result = StringComparer.Ordinal.Compare( x.GroupName, y.GroupName ); + } + + return result; + } + } + + private static class DescriptionProvider + { + internal static ApiVersionDescription[] Describe( + IReadOnlyList metadata, + IPolicyManager sunsetPolicyManager, + IPolicyManager deprecationPolicyManager, + ApiExplorerOptions options ) + { + const bool Supported = false; + const bool Deprecated = true; + var descriptions = new SortedSet( new ApiVersionDescriptionComparer() ); + var supported = new HashSet(); + var deprecated = new HashSet(); + + BucketizeApiVersions( metadata, supported, deprecated, options ); + AppendDescriptions( descriptions, supported, sunsetPolicyManager, deprecationPolicyManager, options, Supported ); + AppendDescriptions( descriptions, deprecated, sunsetPolicyManager, deprecationPolicyManager, options, Deprecated ); + + return [.. descriptions]; + } + + private static void BucketizeApiVersions( + IReadOnlyList list, + HashSet supported, + HashSet deprecated, + ApiExplorerOptions options ) + { + var declared = new HashSet(); + var advertisedSupported = new HashSet(); + var advertisedDeprecated = new HashSet(); + + for ( var i = 0; i < list.Count; i++ ) + { + var metadata = list[i]; + var groupName = metadata.GroupName; + var model = metadata.Map( Explicit | Implicit ); + var versions = model.DeclaredApiVersions; + + for ( var j = 0; j < versions.Count; j++ ) + { + declared.Add( new( groupName, versions[j] ) ); + } + + versions = model.SupportedApiVersions; + + for ( var j = 0; j < versions.Count; j++ ) + { + var version = versions[j]; + supported.Add( new( groupName, version ) ); + advertisedSupported.Add( new( groupName, version ) ); + } + + versions = model.DeprecatedApiVersions; + + for ( var j = 0; j < versions.Count; j++ ) + { + var version = versions[j]; + deprecated.Add( new( groupName, version ) ); + advertisedDeprecated.Add( new( groupName, version ) ); + } + } + + advertisedSupported.ExceptWith( declared ); + advertisedDeprecated.ExceptWith( declared ); + supported.ExceptWith( advertisedSupported ); + deprecated.ExceptWith( supported.Concat( advertisedDeprecated ) ); + + if ( supported.Count == 0 && deprecated.Count == 0 ) + { + supported.Add( new( default, options.DefaultApiVersion ) ); + } + } + + private static void AppendDescriptions( + SortedSet descriptions, + HashSet versions, + IPolicyManager sunsetPolicyManager, + IPolicyManager deprecationPolicyManager, + ApiExplorerOptions options, + bool deprecated ) + { + var format = options.GroupNameFormat; + var formatGroupName = options.FormatGroupName; + + foreach ( var (groupName, version) in versions ) + { + var formattedGroupName = groupName; + + if ( string.IsNullOrEmpty( formattedGroupName ) ) + { + formattedGroupName = version.ToString( format, CurrentCulture ); + } + else if ( formatGroupName is not null ) + { + formattedGroupName = formatGroupName( formattedGroupName, version.ToString( format, CurrentCulture ) ); + } + + sunsetPolicyManager.TryGetPolicy( version, out var sunsetPolicy ); + deprecationPolicyManager.TryGetPolicy( version, out var deprecationPolicy ); + descriptions.Add( new( version, formattedGroupName, deprecated, sunsetPolicy, deprecationPolicy ) ); + } + } + } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/IEndpointRouteBuilderExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/IEndpointRouteBuilderExtensions.cs index d20148f0..5f9a1a71 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/IEndpointRouteBuilderExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/IEndpointRouteBuilderExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Builder; using Asp.Versioning.ApiExplorer; @@ -12,21 +14,25 @@ namespace Microsoft.AspNetCore.Builder; [CLSCompliant( false )] public static class IEndpointRouteBuilderExtensions { - /// - /// Returns a read-only list of API version descriptions. - /// - /// The endpoints to build the - /// API version descriptions from. - /// A new read-only list ofAPI version descriptions. - public static IReadOnlyList DescribeApiVersions( this IEndpointRouteBuilder endpoints ) + /// The endpoints to build the API version + /// descriptions from. + extension( IEndpointRouteBuilder endpoints ) { - ArgumentNullException.ThrowIfNull( endpoints ); + /// + /// Returns a read-only list of API version descriptions. + /// + /// A new read-only list ofAPI + /// version descriptions. + public IReadOnlyList DescribeApiVersions() + { + ArgumentNullException.ThrowIfNull( endpoints ); - var services = endpoints.ServiceProvider; - var factory = services.GetRequiredService(); - using var source = new CompositeEndpointDataSource( endpoints.DataSources ); - var provider = factory.Create( source ); + var services = endpoints.ServiceProvider; + var factory = services.GetRequiredService(); + using var source = new CompositeEndpointDataSource( endpoints.DataSources ); + var provider = factory.Create( source ); - return provider.ApiVersionDescriptions; + return provider.ApiVersionDescriptions; + } } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionCollection.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionCollection.cs deleted file mode 100644 index f5847dd0..00000000 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionCollection.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. - -namespace Asp.Versioning.ApiExplorer.Internal; - -internal sealed class ApiVersionDescriptionCollection( - Func, IReadOnlyList> describe, - IEnumerable collators ) - where T : IGroupedApiVersionMetadata, IGroupedApiVersionMetadataFactory -{ - private readonly object syncRoot = new(); - private readonly Func, IReadOnlyList> describe = describe; - private readonly IApiVersionMetadataCollationProvider[] collators = collators.ToArray(); - private IReadOnlyList? items; - private int version; - - 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(); - - for ( var i = 0; i < collators.Length; i++ ) - { - collators[i].Execute( context ); - } - - var results = context.Results; - var metadata = new T[results.Count]; - - for ( var i = 0; i < metadata.Length; i++ ) - { - metadata[i] = T.New( context.Results.GroupName( i ), results[i] ); - } - - items = describe( metadata ); - version = currentVersion; - } - - return items; - } - } - - private int ComputeVersion() => - collators.Length switch - { - 0 => 0, - 1 => collators[0].Version, - _ => ComputeVersion( collators ), - }; - - private static int ComputeVersion( IApiVersionMetadataCollationProvider[] providers ) - { - var hash = default( HashCode ); - - for ( var i = 0; i < providers.Length; i++ ) - { - hash.Add( providers[i].Version ); - } - - return hash.ToHashCode(); - } -} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionComparer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionComparer.cs deleted file mode 100644 index 3fb73385..00000000 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionComparer.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. - -namespace Asp.Versioning.ApiExplorer.Internal; - -internal sealed class ApiVersionDescriptionComparer : IComparer -{ - public int Compare( ApiVersionDescription? x, ApiVersionDescription? y ) - { - if ( x is null ) - { - return y is null ? 0 : -1; - } - - if ( y is null ) - { - return 1; - } - - var result = x.ApiVersion.CompareTo( y.ApiVersion ); - - if ( result == 0 ) - { - result = StringComparer.Ordinal.Compare( x.GroupName, y.GroupName ); - } - - return result; - } -} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/DescriptionProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/DescriptionProvider.cs deleted file mode 100644 index 064a7736..00000000 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/DescriptionProvider.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. - -namespace Asp.Versioning.ApiExplorer.Internal; - -using static Asp.Versioning.ApiVersionMapping; -using static System.Globalization.CultureInfo; - -internal static class DescriptionProvider -{ - internal static ApiVersionDescription[] Describe( - IReadOnlyList metadata, - IPolicyManager sunsetPolicyManager, - IPolicyManager deprecationPolicyManager, - ApiExplorerOptions options ) - where T : IGroupedApiVersionMetadata, IEquatable - { - var descriptions = new SortedSet( new ApiVersionDescriptionComparer() ); - var supported = new HashSet(); - var deprecated = new HashSet(); - - BucketizeApiVersions( metadata, supported, deprecated, options ); - AppendDescriptions( descriptions, supported, sunsetPolicyManager, deprecationPolicyManager, options, deprecated: false ); - AppendDescriptions( descriptions, deprecated, sunsetPolicyManager, deprecationPolicyManager, options, deprecated: true ); - - return [.. descriptions]; - } - - private static void BucketizeApiVersions( - IReadOnlyList list, - HashSet supported, - HashSet deprecated, - ApiExplorerOptions options ) - where T : IGroupedApiVersionMetadata - { - var declared = new HashSet(); - var advertisedSupported = new HashSet(); - var advertisedDeprecated = new HashSet(); - - for ( var i = 0; i < list.Count; i++ ) - { - var metadata = list[i]; - var groupName = metadata.GroupName; - var model = metadata.Map( Explicit | Implicit ); - var versions = model.DeclaredApiVersions; - - for ( var j = 0; j < versions.Count; j++ ) - { - declared.Add( new( groupName, versions[j] ) ); - } - - versions = model.SupportedApiVersions; - - for ( var j = 0; j < versions.Count; j++ ) - { - var version = versions[j]; - supported.Add( new( groupName, version ) ); - advertisedSupported.Add( new( groupName, version ) ); - } - - versions = model.DeprecatedApiVersions; - - for ( var j = 0; j < versions.Count; j++ ) - { - var version = versions[j]; - deprecated.Add( new( groupName, version ) ); - advertisedDeprecated.Add( new( groupName, version ) ); - } - } - - advertisedSupported.ExceptWith( declared ); - advertisedDeprecated.ExceptWith( declared ); - supported.ExceptWith( advertisedSupported ); - deprecated.ExceptWith( supported.Concat( advertisedDeprecated ) ); - - if ( supported.Count == 0 && deprecated.Count == 0 ) - { - supported.Add( new( default, options.DefaultApiVersion ) ); - } - } - - private static void AppendDescriptions( - SortedSet descriptions, - HashSet versions, - IPolicyManager sunsetPolicyManager, - IPolicyManager deprecationPolicyManager, - ApiExplorerOptions options, - bool deprecated ) - { - var format = options.GroupNameFormat; - var formatGroupName = options.FormatGroupName; - - foreach ( var (groupName, version) in versions ) - { - var formattedGroupName = groupName; - - if ( string.IsNullOrEmpty( formattedGroupName ) ) - { - formattedGroupName = version.ToString( format, CurrentCulture ); - } - else if ( formatGroupName is not null ) - { - formattedGroupName = formatGroupName( formattedGroupName, version.ToString( format, CurrentCulture ) ); - } - - sunsetPolicyManager.TryGetPolicy( version, out var sunsetPolicy ); - deprecationPolicyManager.TryGetPolicy( version, out var deprecationPolicy ); - - descriptions.Add( new( version, formattedGroupName, deprecated, sunsetPolicy, deprecationPolicy ) ); - } - } -} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/GroupedApiVersion.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/GroupedApiVersion.cs deleted file mode 100644 index 8d276e60..00000000 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/GroupedApiVersion.cs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. - -namespace Asp.Versioning.ApiExplorer.Internal; - -internal record struct GroupedApiVersion( string? GroupName, ApiVersion ApiVersion ); \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadata.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadata.cs deleted file mode 100644 index ec0c13e3..00000000 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadata.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. - -namespace Asp.Versioning.ApiExplorer.Internal; - -internal interface IGroupedApiVersionMetadata -{ - string? GroupName { get; } - - string Name { get; } - - bool IsApiVersionNeutral { get; } - - ApiVersionModel Map( ApiVersionMapping mapping ); - - ApiVersionMapping MappingTo( ApiVersion? apiVersion ); - - bool IsMappedTo( ApiVersion? apiVersion ); - - void Deconstruct( out ApiVersionModel apiModel, out ApiVersionModel endpointModel ); -} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadataFactory.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadataFactory.cs deleted file mode 100644 index ac9d885f..00000000 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadataFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. - -namespace Asp.Versioning.ApiExplorer.Internal; - -internal interface IGroupedApiVersionMetadataFactory - where T : IGroupedApiVersionMetadata -{ - static abstract T New( string? groupName, ApiVersionMetadata metadata ); -} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ReleaseNotes.txt b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ReleaseNotes.txt index 45062d7e..5f282702 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ReleaseNotes.txt +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ReleaseNotes.txt @@ -1,2 +1 @@ -Enable trimming support ([#1094](https://github.com/dotnet/aspnet-api-versioning/issues/1094)) -Fixed exploring version-neutral API version parameter in URL ([#1149](https://github.com/dotnet/aspnet-api-versioning/issues/1149)) \ No newline at end of file + \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/VersionedApiDescriptionProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/VersionedApiDescriptionProvider.cs index 0f5ac457..ff1ecead 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/VersionedApiDescriptionProvider.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/VersionedApiDescriptionProvider.cs @@ -100,8 +100,11 @@ internal VersionedApiDescriptionProvider( /// The action to evaluate. /// The API version for action being explored. /// True if the action should be explored; otherwise, false. - protected virtual bool ShouldExploreAction( ActionDescriptor actionDescriptor, ApiVersion apiVersion ) => - actionDescriptor.GetApiVersionMetadata().IsMappedTo( apiVersion ); + protected virtual bool ShouldExploreAction( ActionDescriptor actionDescriptor, ApiVersion apiVersion ) + { + ArgumentNullException.ThrowIfNull( actionDescriptor ); + return actionDescriptor.ApiVersionMetadata.IsMappedTo( apiVersion ); + } /// /// Populates the API version parameters for the specified API description. @@ -170,7 +173,7 @@ public virtual void OnProvidersExecuted( ApiDescriptionProviderContext context ) TryUpdateControllerRouteValueForMinimalApi( result ); var groupResult = result.Clone(); - var metadata = action.GetApiVersionMetadata(); + var metadata = action.ApiVersionMetadata; if ( string.IsNullOrEmpty( groupResult.GroupName ) ) { @@ -183,15 +186,15 @@ public virtual void OnProvidersExecuted( ApiDescriptionProviderContext context ) if ( SunsetPolicyManager.TryResolvePolicy( metadata.Name, version, out var sunsetPolicy ) ) { - groupResult.SetSunsetPolicy( sunsetPolicy ); + groupResult.SunsetPolicy = sunsetPolicy; } if ( DeprecationPolicyManager.TryResolvePolicy( metadata.Name, version, out var deprecationPolicy ) ) { - groupResult.SetDeprecationPolicy( deprecationPolicy ); + groupResult.DeprecationPolicy = deprecationPolicy; } - groupResult.SetApiVersion( version ); + groupResult.ApiVersion = version; PopulateApiVersionParameters( groupResult, version ); AddOrUpdateResult( groupResults, groupResult, metadata, version ); } @@ -258,7 +261,7 @@ private static void TryUpdateControllerRouteValueForMinimalApi( ApiDescription d return; } - var metadata = action.GetApiVersionMetadata(); + var metadata = action.ApiVersionMetadata; if ( !string.IsNullOrEmpty( metadata.Name ) ) { @@ -282,7 +285,7 @@ private static void AddOrUpdateResult( comparer.Equals( result.RelativePath, other.RelativePath ) && comparer.Equals( result.HttpMethod, other.HttpMethod ) ) { - var mapping = other.ActionDescriptor.GetApiVersionMetadata().MappingTo( version ); + var mapping = other.ActionDescriptor.ApiVersionMetadata.MappingTo( version ); switch ( metadata.MappingTo( version ) ) { @@ -314,8 +317,7 @@ private ApiVersion[] FlattenApiVersions( IList descriptions ) for ( var i = 0; i < descriptions.Count; i++ ) { var action = descriptions[i].ActionDescriptor; - var model = action.GetApiVersionMetadata().Map( Explicit | Implicit ); - var declaredVersions = model.DeclaredApiVersions; + var declaredVersions = action.ApiVersionMetadata.Map( Explicit | Implicit ).DeclaredApiVersions; if ( versions is null && declaredVersions.Count > 0 ) { diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Abstractions/ActionDescriptorExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Abstractions/ActionDescriptorExtensions.cs index 9ca5df02..2a726c6f 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Abstractions/ActionDescriptorExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Abstractions/ActionDescriptorExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Mvc.Abstractions; using Asp.Versioning; @@ -10,66 +12,72 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions; [CLSCompliant( false )] public static class ActionDescriptorExtensions { - /// - /// Gets the API version information associated with an action. - /// /// The extended action. - /// The API version information for the action. - public static ApiVersionMetadata GetApiVersionMetadata( this ActionDescriptor action ) + extension( ActionDescriptor action ) { - ArgumentNullException.ThrowIfNull( action ); + /// + /// Gets the API version information associated with an action. + /// + /// The API version information for the action. + public ApiVersionMetadata ApiVersionMetadata + { + get + { + ArgumentNullException.ThrowIfNull( action ); - var endpointMetadata = action.EndpointMetadata; + var endpointMetadata = action.EndpointMetadata; - if ( endpointMetadata == null ) - { - return ApiVersionMetadata.Empty; + if ( endpointMetadata == null ) + { + return Asp.Versioning.ApiVersionMetadata.Empty; + } + + for ( var i = 0; i < endpointMetadata.Count; i++ ) + { + if ( endpointMetadata[i] is ApiVersionMetadata metadata ) + { + return metadata; + } + } + + return ApiVersionMetadata.Empty; + } } - for ( var i = 0; i < endpointMetadata.Count; i++ ) + internal void AddOrReplaceApiVersionMetadata( ApiVersionMetadata value ) { - if ( endpointMetadata[i] is ApiVersionMetadata metadata ) + var endpointMetadata = action.EndpointMetadata; + + if ( endpointMetadata == null ) { - return metadata; + action.EndpointMetadata = [value]; + return; } - } - return ApiVersionMetadata.Empty; - } + for ( var i = 0; i < endpointMetadata.Count; i++ ) + { + if ( endpointMetadata[i] is not Asp.Versioning.ApiVersionMetadata ) + { + continue; + } - internal static void AddOrReplaceApiVersionMetadata( this ActionDescriptor action, ApiVersionMetadata value ) - { - var endpointMetadata = action.EndpointMetadata; + if ( endpointMetadata.IsReadOnly ) + { + action.EndpointMetadata = endpointMetadata = [.. endpointMetadata]; + } - if ( endpointMetadata == null ) - { - action.EndpointMetadata = [value]; - return; - } - - for ( var i = 0; i < endpointMetadata.Count; i++ ) - { - if ( endpointMetadata[i] is not ApiVersionMetadata ) - { - continue; + endpointMetadata[i] = value; + return; } if ( endpointMetadata.IsReadOnly ) { - action.EndpointMetadata = endpointMetadata = endpointMetadata.ToList(); + action.EndpointMetadata = [value]; + } + else + { + endpointMetadata.Add( value ); } - - endpointMetadata[i] = value; - return; - } - - if ( endpointMetadata.IsReadOnly ) - { - action.EndpointMetadata = [value]; - } - else - { - endpointMetadata.Add( value ); } } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiExplorer/ActionApiVersionMetadataCollationProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiExplorer/ActionApiVersionMetadataCollationProvider.cs index 6614986a..5f21db5c 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiExplorer/ActionApiVersionMetadataCollationProvider.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiExplorer/ActionApiVersionMetadataCollationProvider.cs @@ -36,7 +36,7 @@ public void Execute( ApiVersionMetadataCollationContext context ) for ( var i = 0; i < actions.Count; i++ ) { var action = actions[i]; - var item = action.GetApiVersionMetadata(); + var item = action.ApiVersionMetadata; var groupName = GetGroupName( action ); context.Results.Add( item, groupName ); diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiVersionCollator.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiVersionCollator.cs index e621198a..d845f0d8 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiVersionCollator.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiVersionCollator.cs @@ -41,7 +41,7 @@ public virtual void OnProvidersExecuted( ActionDescriptorProviderContext context for ( var i = 0; i < actions.Count; i++ ) { var action = actions[i]; - var metadata = action.GetApiVersionMetadata(); + var metadata = action.ApiVersionMetadata; if ( metadata.IsApiVersionNeutral ) { @@ -90,7 +90,7 @@ protected virtual string GetControllerName( ActionDescriptor action ) } [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static bool IsUnversioned( ActionDescriptor action ) => action.GetApiVersionMetadata() == ApiVersionMetadata.Empty; + private static bool IsUnversioned( ActionDescriptor action ) => action.ApiVersionMetadata == ApiVersionMetadata.Empty; private IEnumerable> GroupActionsByController( IList actions ) { @@ -128,5 +128,5 @@ private IEnumerable> GroupActionsByController( I [MethodImpl( MethodImplOptions.AggressiveInlining )] private static ApiVersionModel CollateModel( IEnumerable actions ) => - actions.Select( a => a.GetApiVersionMetadata().Map( Explicit ) ).Aggregate(); + actions.Select( a => a.ApiVersionMetadata.Map( Explicit ) ).Aggregate(); } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiVersionModelBinder.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiVersionModelBinder.cs index 965ed1bd..9b88d96b 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiVersionModelBinder.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApiVersionModelBinder.cs @@ -17,7 +17,7 @@ public virtual Task BindModelAsync( ModelBindingContext bindingContext ) { ArgumentNullException.ThrowIfNull( bindingContext ); - var feature = bindingContext.HttpContext.ApiVersioningFeature(); + var feature = bindingContext.HttpContext.ApiVersioningFeature; var model = feature.RequestedApiVersion; if ( model != null ) diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApplicationModels/DefaultApiControllerFilter.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApplicationModels/DefaultApiControllerFilter.cs index a8a1d46e..ef86ccab 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApplicationModels/DefaultApiControllerFilter.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApplicationModels/DefaultApiControllerFilter.cs @@ -19,7 +19,7 @@ public sealed class DefaultApiControllerFilter : IApiControllerFilter /// specifications used by the filter /// to identify API controllers. public DefaultApiControllerFilter( IEnumerable specifications ) => - this.specifications = specifications.ToList(); + this.specifications = [.. specifications]; /// public IList Apply( IList controllers ) diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApplicationModels/ModelExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApplicationModels/ModelExtensions.cs index 9ccb55d5..e89aa04e 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApplicationModels/ModelExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ApplicationModels/ModelExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Mvc.ApplicationModels; using Asp.Versioning; @@ -12,38 +14,47 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels; [CLSCompliant( false )] public static class ModelExtensions { - /// - /// Gets the API version information associated with an action. - /// /// The extended controller . - /// The API version information for the controller. - /// This API is meant for infrastructure and should not be used by application code. - [EditorBrowsable( EditorBrowsableState.Never )] - public static ApiVersionModel GetApiVersionModel( this ControllerModel controller ) + extension( ControllerModel controller ) { - ArgumentNullException.ThrowIfNull( controller ); - - if ( controller.Properties.TryGetValue( typeof( ApiVersionModel ), out var value ) && - value is ApiVersionModel model ) + /// + /// Gets the API version information associated with an action. + /// + /// The API version information for the controller. + /// This API is meant for infrastructure and should not be used by application code. + [EditorBrowsable( EditorBrowsableState.Never )] + public ApiVersionModel ApiVersionModel { - return model; + get + { + ArgumentNullException.ThrowIfNull( controller ); + + if ( controller.Properties.TryGetValue( typeof( ApiVersionModel ), out var value ) && + value is ApiVersionModel model ) + { + return model; + } + + return ApiVersionModel.Empty; + } } - - return ApiVersionModel.Empty; } - internal static void AddEndpointMetadata( this ActionModel action, object metadata ) + extension( ActionModel action ) { - var selectors = action.Selectors; - - if ( selectors.Count == 0 ) + internal void AddEndpointMetadata( object metadata ) { - selectors.Add( new() ); - } + var selectors = action.Selectors; - for ( var i = 0; i < selectors.Count; i++ ) - { - selectors[i].EndpointMetadata.Add( metadata ); + if ( selectors.Count == 0 ) + { + selectors.Add( new() ); + } + + for ( var i = 0; i < selectors.Count; i++ ) + { + selectors[i].EndpointMetadata.Add( metadata ); + } } } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Asp.Versioning.Mvc.csproj b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Asp.Versioning.Mvc.csproj index 7fbe98ef..1c851426 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Asp.Versioning.Mvc.csproj +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Asp.Versioning.Mvc.csproj @@ -1,8 +1,8 @@  - 8.1.1 - 8.1.0.0 + 10.0.0 + 10.0.0.0 $(DefaultTargetFramework) Asp.Versioning ASP.NET Core API Versioning diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Conventions/ActionApiVersionConventionBuilderBase.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Conventions/ActionApiVersionConventionBuilderBase.cs index 82770380..8e18b0c2 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Conventions/ActionApiVersionConventionBuilderBase.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Conventions/ActionApiVersionConventionBuilderBase.cs @@ -25,7 +25,7 @@ public virtual void ApplyTo( ActionModel item ) ApiVersionModel apiModel; ApiVersionMetadata metadata; - if ( VersionNeutral || ( apiModel = controller.GetApiVersionModel() ).IsApiVersionNeutral ) + if ( VersionNeutral || ( apiModel = controller.ApiVersionModel ).IsApiVersionNeutral ) { metadata = string.IsNullOrEmpty( name ) ? ApiVersionMetadata.Neutral diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/DependencyInjection/IApiVersioningBuilderExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/DependencyInjection/IApiVersioningBuilderExtensions.cs index 21e66163..68f7551d 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/DependencyInjection/IApiVersioningBuilderExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/DependencyInjection/IApiVersioningBuilderExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. -// Ignore Spelling: Mvc +#pragma warning disable IDE0130 + namespace Microsoft.Extensions.DependencyInjection; using Asp.Versioning; @@ -23,34 +24,73 @@ namespace Microsoft.Extensions.DependencyInjection; /// public static class IApiVersioningBuilderExtensions { - /// - /// Adds ASP.NET Core MVC support for API versioning. - /// /// The extended API versioning builder. /// The original . - public static IApiVersioningBuilder AddMvc( this IApiVersioningBuilder builder ) + extension( IApiVersioningBuilder builder ) { - ArgumentNullException.ThrowIfNull( builder ); - AddServices( builder.Services ); - return builder; + /// + /// Adds ASP.NET Core MVC support for API versioning. + /// + public IApiVersioningBuilder AddMvc() + { + ArgumentNullException.ThrowIfNull( builder ); + AddServices( builder.Services ); + return builder; + } + + /// + /// Adds ASP.NET Core MVC support for API versioning. + /// + /// An action used to configure the provided options. + public IApiVersioningBuilder AddMvc( Action setupAction ) + { + ArgumentNullException.ThrowIfNull( builder ); + + var services = builder.Services; + + AddServices( services ); + services.Configure( setupAction ); + + return builder; + } } - /// - /// Adds ASP.NET Core MVC support for API versioning. - /// - /// The extended API versioning builder. - /// An action used to configure the provided options. - /// The original . - public static IApiVersioningBuilder AddMvc( this IApiVersioningBuilder builder, Action setupAction ) + extension( IServiceCollection services ) { - ArgumentNullException.ThrowIfNull( builder ); + private void TryReplace() + { + var serviceType = typeof( TService ); + var implementationType = typeof( TImplementation ); - var services = builder.Services; + for ( var i = services.Count - 1; i >= 0; i-- ) + { + var service = services[i]; - AddServices( services ); - services.Configure( setupAction ); + if ( service.ServiceType == serviceType && service.ImplementationType == implementationType ) + { + services[i] = Describe( serviceType, typeof( TReplacement ), service.Lifetime ); + break; + } + } + } + } - return builder; + extension( IServiceProvider services ) + { + private object CreateInstance( ServiceDescriptor descriptor ) + { + if ( descriptor.ImplementationInstance != null ) + { + return descriptor.ImplementationInstance; + } + + if ( descriptor.ImplementationFactory != null ) + { + return descriptor.ImplementationFactory( services ); + } + + return ActivatorUtilities.GetServiceOrCreateInstance( services, descriptor.ImplementationType! ); + } } private static void AddServices( IServiceCollection services ) @@ -71,42 +111,6 @@ private static void AddServices( IServiceCollection services ) services.TryReplace(); } - private static object CreateInstance( this IServiceProvider services, ServiceDescriptor descriptor ) - { - if ( descriptor.ImplementationInstance != null ) - { - return descriptor.ImplementationInstance; - } - - if ( descriptor.ImplementationFactory != null ) - { - return descriptor.ImplementationFactory( services ); - } - - return ActivatorUtilities.GetServiceOrCreateInstance( services, descriptor.ImplementationType! ); - } - - private static void TryReplace< - TService, - TImplementation, - [DynamicallyAccessedMembers( NonPublicConstructors | PublicConstructors )] - TReplacement>( this IServiceCollection services ) - { - var serviceType = typeof( TService ); - var implementationType = typeof( TImplementation ); - - for ( var i = services.Count - 1; i >= 0; i-- ) - { - var service = services[i]; - - if ( service.ServiceType == serviceType && service.ImplementationType == implementationType ) - { - services[i] = Describe( serviceType, typeof( TReplacement ), service.Lifetime ); - break; - } - } - } - [SkipLocalsInit] private static DecoratedServiceDescriptor WithUrlHelperFactoryDecorator( IServiceCollection services ) { diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ReleaseNotes.txt b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ReleaseNotes.txt index 800ce0ac..5f282702 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ReleaseNotes.txt +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/ReleaseNotes.txt @@ -1 +1 @@ -Enable trimming support ([#1094](https://github.com/dotnet/aspnet-api-versioning/issues/1094)) \ No newline at end of file + \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Routing/ApiVersionUrlHelper.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Routing/ApiVersionUrlHelper.cs index bd3e398e..49b81a1a 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Routing/ApiVersionUrlHelper.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Routing/ApiVersionUrlHelper.cs @@ -24,7 +24,7 @@ public ApiVersionUrlHelper( ActionContext actionContext, IUrlHelper url ) { ActionContext = actionContext ?? throw new ArgumentNullException( nameof( actionContext ) ); Url = url; - feature = actionContext.HttpContext.ApiVersioningFeature(); + feature = actionContext.HttpContext.ApiVersioningFeature; } /// diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Routing/IUrlHelperExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Routing/IUrlHelperExtensions.cs index 29fd1648..2a303543 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Routing/IUrlHelperExtensions.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Routing/IUrlHelperExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Mvc; using Asp.Versioning; @@ -11,27 +13,29 @@ namespace Microsoft.AspNetCore.Mvc; [CLSCompliant( false )] public static class IUrlHelperExtensions { - /// - /// Returns a new URL helper that includes the requested API version. - /// /// The extended URL helper. - /// A new URL helper that excludes the requested - /// API version or the original URL helper if - /// unnecessary. - /// Excluding the requested API version is useful in a limited set of scenarios - /// such as building a URL from an API that versions by URL segment to an API that is - /// version-neutral. A version-neutral API would not use the specified route value and - /// it would be erroneously added as a query string parameter. - public static IUrlHelper WithoutApiVersion( this IUrlHelper urlHelper ) + extension( IUrlHelper urlHelper ) { - ArgumentNullException.ThrowIfNull( urlHelper ); - - if ( urlHelper is WithoutApiVersionUrlHelper || - urlHelper.ActionContext.HttpContext.Features.Get() is null ) + /// + /// Returns a new URL helper that includes the requested API version. + /// + /// A new URL helper that excludes the requested + /// API version or the original URL helper, if unnecessary. + /// Excluding the requested API version is useful in a limited set of scenarios + /// such as building a URL from an API that versions by URL segment to an API that is + /// version-neutral. A version-neutral API would not use the specified route value and + /// it would be erroneously added as a query string parameter. + public IUrlHelper WithoutApiVersion() { - return urlHelper; - } + ArgumentNullException.ThrowIfNull( urlHelper ); - return new WithoutApiVersionUrlHelper( urlHelper ); + if ( urlHelper is WithoutApiVersionUrlHelper || + urlHelper.ActionContext.HttpContext.Features.Get() is null ) + { + return urlHelper; + } + + return new WithoutApiVersionUrlHelper( urlHelper ); + } } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj new file mode 100644 index 00000000..1df0ce32 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj @@ -0,0 +1,26 @@ + + + + 10.0.0 + 10.0.0.0 + $(DefaultTargetFramework) + Asp.Versioning.OpenApi + ASP.NET Core API Versioning + The OpenAPI extensions for ASP.NET Core API Versioning. + Asp;AspNet;AspNetCore;Versioning;OpenAPI + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Builder/IEndpointConventionBuilderExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Builder/IEndpointConventionBuilderExtensions.cs new file mode 100644 index 00000000..aa2559fe --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Builder/IEndpointConventionBuilderExtensions.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +#pragma warning disable IDE0130 + +namespace Microsoft.AspNetCore.Builder; + +using Asp.Versioning; +using Asp.Versioning.ApiExplorer; +using Asp.Versioning.OpenApi.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +/// +/// Provides extension methods for . +/// +[CLSCompliant( false )] +public static class IEndpointConventionBuilderExtensions +{ + extension( IEndpointConventionBuilder builder ) + { + /// + /// Enables generating one OpenAPI document per APi Version for the associated endpoint builder. + /// + /// + /// This method is only intended to apply API Versioning conventions the OpenAPI endpoint. Applying this + /// method to other endpoints may have unintended effects. + /// + /// The original endpoint convention builder. + public IEndpointConventionBuilder WithDocumentPerVersion() + { + builder.Finally( ApplyApiVersioning ); + return builder; + } + } + + private static void ApplyApiVersioning( EndpointBuilder builder ) + { + if ( builder.RequestDelegate is { } action ) + { + builder.RequestDelegate = context => InterceptRequestServices( context, action ); + } + } + + private static Task InterceptRequestServices( HttpContext context, RequestDelegate action ) + { + if ( context.RequestServices is not KeyedServiceContainer requestServices ) + { + requestServices = context.RequestServices.GetRequiredService(); + } + + context.RequestServices = requestServices; + return action( context ); + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Configuration/ConfigureOpenApiOptions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Configuration/ConfigureOpenApiOptions.cs new file mode 100644 index 00000000..a8dc7380 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Configuration/ConfigureOpenApiOptions.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +#pragma warning disable CA1812 + +namespace Asp.Versioning.OpenApi; + +using Asp.Versioning.ApiExplorer; +using Asp.Versioning.OpenApi.Configuration; +using Asp.Versioning.OpenApi.Reflection; +using Asp.Versioning.OpenApi.Transformers; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +internal sealed class ConfigureOpenApiOptions( + XmlCommentsFile file, + IApiVersionDescriptionProvider provider, + VersionedOpenApiOptionsFactory factory ) + : IPostConfigureOptions +{ + public void PostConfigure( string? name, OpenApiOptions options ) + { + var comparer = StringComparer.OrdinalIgnoreCase; + var descriptions = provider.ApiVersionDescriptions; + var xmlComments = new XmlCommentsTransformer( file ); + + for ( var i = 0; i < descriptions.Count; i++ ) + { + var description = descriptions[i]; + + if ( !comparer.Equals( name, description.GroupName ) ) + { + continue; + } + + var context = new VersionedOpenApiOptionsFactory.Context() + { + Name = name, + Description = description, + Options = options, + OnCreated = versionedOptions => Configure( versionedOptions, xmlComments ), + }; + + factory.CreateAndConfigure( context ); + break; + } + } + + private static void Configure( VersionedOpenApiOptions versionedOptions, XmlCommentsTransformer xmlComments ) + { + var options = versionedOptions.Document; + var apiExplorer = new ApiExplorerTransformer( versionedOptions ); + + options.SetDocumentName( versionedOptions.Description.GroupName ); + options.AddDocumentTransformer( apiExplorer ); + options.AddSchemaTransformer( apiExplorer ); + options.AddOperationTransformer( apiExplorer ); + + if ( !xmlComments.IsEmpty ) + { + options.AddSchemaTransformer( xmlComments ); + options.AddOperationTransformer( xmlComments ); + } + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Configuration/VersionedOpenApiOptionsFactory.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Configuration/VersionedOpenApiOptionsFactory.cs new file mode 100644 index 00000000..dfd36126 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Configuration/VersionedOpenApiOptionsFactory.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +#pragma warning disable CA1812 + +namespace Asp.Versioning.OpenApi.Configuration; + +using Asp.Versioning.ApiExplorer; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.Extensions.Options; +using System.Collections.Generic; + +// OpenApiOptions is sealed so we can't inherit from it, but we need to get in front of it. this factory allows +// configuring VersionedOpenApiOptions registered when the services are added. IOptions isn't +// ever directly resolved and never uses a name. whenever OpenApiOptions are created and configured, this factory is +// invoked to create the VersionedOpenApiOptions, which will be passed down to transformers, etc. +internal sealed class VersionedOpenApiOptionsFactory( + IEnumerable> setups, + IEnumerable> postConfigures, + IEnumerable> validations ) + : IOptionsFactory +{ + private readonly IConfigureOptions[] setups = [.. setups]; + private readonly IPostConfigureOptions[] postConfigures = [.. postConfigures]; + private readonly IValidateOptions[] validations = [.. validations]; + private Context? context; + + internal VersionedOpenApiOptions CreateAndConfigure( Context newContext ) + { + context = newContext; + var instance = Create( newContext.Name ); + context = default; + return instance; + } + + public VersionedOpenApiOptions Create( string name ) + { + if ( string.IsNullOrEmpty( name ) || context is null ) + { + return DefaultOptions(); + } + + if ( name != context.Name ) + { + return DefaultOptions(); + } + + var options = new VersionedOpenApiOptions() + { + Description = context.Description, + Document = context.Options, + DocumentDescription = new(), + }; + + context.OnCreated( options ); + + for ( var i = 0; i < setups.Length; i++ ) + { + setups[i].Configure( options ); + } + + for ( var i = 0; i < postConfigures.Length; i++ ) + { + postConfigures[i].PostConfigure( Options.DefaultName, options ); + } + + if ( validations.Length > 0 ) + { + var failures = new List(); + + for ( var i = 0; i < validations.Length; i++ ) + { + var result = validations[i].Validate( Options.DefaultName, options ); + + if ( result is not null && result.Failed ) + { + failures.AddRange( result.Failures ); + } + } + + if ( failures.Count > 0 ) + { + throw new OptionsValidationException( name, typeof( VersionedOpenApiOptions ), failures ); + } + } + + return options; + } + + private static VersionedOpenApiOptions DefaultOptions() => new() + { + Description = new( ApiVersion.Neutral, string.Empty ), + Document = new(), + DocumentDescription = new(), + }; + + internal sealed class Context + { + public required string Name { get; init; } + + public required ApiVersionDescription Description { get; init; } + + public required OpenApiOptions Options { get; init; } + + public required Action OnCreated { get; init; } + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/DependencyInjection/IApiVersioningBuilderExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/DependencyInjection/IApiVersioningBuilderExtensions.cs new file mode 100644 index 00000000..13e1c648 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/DependencyInjection/IApiVersioningBuilderExtensions.cs @@ -0,0 +1,141 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +#pragma warning disable IDE0130 + +namespace Microsoft.Extensions.DependencyInjection; + +using Asp.Versioning; +using Asp.Versioning.ApiExplorer; +using Asp.Versioning.OpenApi; +using Asp.Versioning.OpenApi.Configuration; +using Asp.Versioning.OpenApi.Reflection; +using Asp.Versioning.OpenApi.Transformers; +using Microsoft.AspNetCore.Http.Json; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using System.Reflection; +using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor; +using EM = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions; + +/// +/// Provides OpenAPI specific extension methods for . +/// +[CLSCompliant( false )] +public static class IApiVersioningBuilderExtensions +{ + extension( IApiVersioningBuilder builder ) + { + /// + /// Adds OpenAPI support for API versioning. + /// + /// The original builder. + public IApiVersioningBuilder AddOpenApi() + { + ArgumentNullException.ThrowIfNull( builder ); + + AddOpenApiServices( builder, GetAssemblies( Assembly.GetCallingAssembly() ) ); + + return builder; + } + + /// + /// Adds OpenAPI support for API versioning. + /// + /// The function used to configure the + /// versioned OpenAPI options. + /// The original builder. + public IApiVersioningBuilder AddOpenApi( Action configureOptions ) + { + ArgumentNullException.ThrowIfNull( builder ); + + AddOpenApiServices( builder, GetAssemblies( Assembly.GetCallingAssembly() ) ); + builder.Services.Configure( configureOptions ); + + return builder; + } + } + + [UnconditionalSuppressMessage( "ILLink", "IL2026" )] + private static void AddOpenApiServices( IApiVersioningBuilder builder, Assembly[] assemblies ) + { + builder.AddApiExplorer(); + + var services = builder.Services; + + services.AddTransient( NewRequestServices ); + services.Add( Singleton( Type.IDocumentProvider, ResolveDocumentProvider ) ); + services.AddSingleton(); + services.TryAddEnumerable( Transient, ConfigureOpenApiOptions>() ); + services.TryAdd( Singleton>( EM.GetRequiredService ) ); + builder.Services.AddSingleton( sp => new XmlCommentsFile( assemblies, sp.GetRequiredService() ) ); + + if ( GetJsonConfiguration() is { } descriptor ) + { + services.TryAddEnumerable( descriptor ); + } + } + + // NOTE: The calling assembly must be captured at the call site that invokes AddOpenApi. In 99% of the cases that + // should be the entry point to the application. It is technically possible to be invoked from some other assembly - + // perhaps another extension library. If that were to happen, that library must resolve the path on its own and + // register another XmlCommentsTransformer with the resolved path + private static Assembly[] GetAssemblies( Assembly callingAssembly ) + { + var assemblies = new List( capacity: 2 ) { callingAssembly }; + + if ( Assembly.GetEntryAssembly() is { } entryAssembly && assemblies[0] != callingAssembly ) + { + assemblies.Add( entryAssembly ); + } + + return [.. assemblies]; + } + + // HACK: the json configuration is internal; this approach negates the use of reflection + // REF: https://github.com/dotnet/aspnetcore/blob/08a9fc2c3864d99759ab3d71cfda868d852bfc4b/src/OpenApi/src/Extensions/OpenApiServiceCollectionExtensions.cs#L121 + private static ServiceDescriptor? GetJsonConfiguration() + { + var services = new ServiceCollection(); + services.AddOpenApi( "*" ); + return services.SingleOrDefault( sd => sd.ServiceType == typeof( IConfigureOptions ) ); + } + + private static object ResolveDocumentProvider( IServiceProvider provider ) => + provider.GetRequiredService().GetRequiredService( Type.IDocumentProvider ); + + [UnconditionalSuppressMessage( "ILLink", "IL3050" )] + private static KeyedServiceContainer NewRequestServices( IServiceProvider services ) + { + var provider = services.GetRequiredService(); + var keyedServices = new KeyedServiceContainer( services ); + var names = new List(); + + foreach ( var description in provider.ApiVersionDescriptions ) + { + names.Add( description.GroupName ); + keyedServices.Add( Type.OpenApiSchemaService, description.GroupName, Class.OpenApiSchemaService.New ); + keyedServices.Add( Type.OpenApiDocumentService, description.GroupName, Class.OpenApiDocumentService.New ); + keyedServices.Add( + typeof( IOpenApiDocumentProvider ), + description.GroupName, + ( sp, k ) => sp.GetRequiredKeyedService( Type.OpenApiDocumentService, k ) ); + } + + if ( names.Count > 0 ) + { + var array = Array.CreateInstance( Type.NamedService, names.Count ); + + for ( var i = 0; i < names.Count; i++ ) + { + array.SetValue( Class.NamedService.New( names[i] ), i ); + } + + keyedServices.Add( Type.IDocumentProvider, Class.OpenApiDocumentProvider.New ); + keyedServices.Add( Type.IEnumerableOfNamedService, array ); + } + + return keyedServices; + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/DependencyInjection/KeyedServiceContainer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/DependencyInjection/KeyedServiceContainer.cs new file mode 100644 index 00000000..71941c02 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/DependencyInjection/KeyedServiceContainer.cs @@ -0,0 +1,98 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +#pragma warning disable IDE0130 + +namespace Microsoft.Extensions.DependencyInjection; + +using System.ComponentModel.Design; + +internal sealed class KeyedServiceContainer( IServiceProvider parent ) : + IServiceProvider, + IKeyedServiceProvider, + IServiceProviderIsService, + IServiceProviderIsKeyedService, + IDisposable +{ + private readonly ServiceContainer services = new( parent ); + private readonly Dictionary keyedServices = []; + private bool disposed; + + public object? GetKeyedService( Type serviceType, object? serviceKey ) + { + if ( serviceKey is not null && keyedServices.TryGetValue( serviceKey, out var container ) ) + { + return container.GetService( serviceType ); + } + + return services.GetKeyedService( serviceType, serviceKey ); + } + + public object GetRequiredKeyedService( Type serviceType, object? serviceKey ) + { + if ( serviceKey is not null && keyedServices.TryGetValue( serviceKey, out var container ) ) + { + return container.GetRequiredService( serviceType ); + } + + return services.GetRequiredKeyedService( serviceType, serviceKey ); + } + + public object? GetService( Type serviceType ) => services.GetService( serviceType ); + + public bool IsKeyedService( Type serviceType, object? serviceKey ) + { + if ( serviceKey is not null && keyedServices.ContainsKey( serviceKey ) ) + { + return true; + } + else if ( services.GetService() is { } service ) + { + return service.IsKeyedService( serviceType, serviceKey ); + } + + return false; + } + + public bool IsService( Type serviceType ) + { + if ( services.GetService() is { } service + && service.IsService( serviceType ) ) + { + return true; + } + + return services.GetService( serviceType ) is not null; + } + + public void Add( Type serviceType, object instance ) => services.AddService( serviceType, instance ); + + public void Add( Type serviceType, Func activator ) => + services.AddService( serviceType, ( _, _ ) => activator( this ) ); + + public void Add( Type serviceType, string serviceKey, Func activator ) + { + if ( !keyedServices.TryGetValue( serviceKey, out var container ) ) + { + keyedServices.Add( serviceKey, container = new() ); + } + + container.AddService( serviceType, ( _, _ ) => activator( this, serviceKey ) ); + } + + public void Dispose() + { + if ( disposed ) + { + return; + } + + disposed = true; + + foreach ( var container in keyedServices.Values ) + { + container.Dispose(); + } + + services.Dispose(); + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/OpenApiDocumentDescriptionOptions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/OpenApiDocumentDescriptionOptions.cs new file mode 100644 index 00000000..960c7803 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/OpenApiDocumentDescriptionOptions.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi; + +using System.Globalization; +using System.Text; + +/// +/// Represents the options used to format the OpenAPI document title. +/// +public class OpenApiDocumentDescriptionOptions +{ + private static CompositeFormat? deprecatedNoticeFormat; + private static CompositeFormat? sunsetNoticeFormat; + + /// + /// Gets or sets a value indicating whether API versioning policy links are hidden. + /// + /// True if API versioning policy links are hidden; otherwise, false. The default value is + /// false. + /// Only API versioning policy links with the media type text/html will be shown. + public bool HidePolicyLinks { get; set; } + + /// + /// Gets or sets the function used to generate the API versioning deprecation notice based on the provided policy. + /// + /// The function used to generate the deprecation notice. + /// If the function generates a null or empty message, then no notice is displayed. + public Func DeprecationNotice { get; set; } = DefaultDeprecationNotice; + + /// + /// Gets or sets the function used to generate the API versioning sunset notice based on the provided policy. + /// + /// The function used to generate the sunset notice. + /// If the function generates a null or empty message, then no notice is displayed. + public Func SunsetNotice { get; set; } = DefaultSunsetNotice; + + private static string? DefaultDeprecationNotice( DeprecationPolicy policy ) + { + if ( policy.Date is not { } when ) + { + return SR.DeprecatedNotice; + } + + var participle = when < DateTimeOffset.Now ? SR.Was : SR.WillBe; + deprecatedNoticeFormat ??= CompositeFormat.Parse( SR.DeprecatedNoticeFormat ); + return string.Format( CultureInfo.CurrentCulture, deprecatedNoticeFormat, participle, when ); + } + + private static string? DefaultSunsetNotice( SunsetPolicy policy ) + { + if ( policy.Date is not { } when ) + { + return default; + } + + var participle = when < DateTimeOffset.Now ? SR.Was : SR.WillBe; + sunsetNoticeFormat ??= CompositeFormat.Parse( SR.SunsetNoticeFormat ); + return string.Format( CultureInfo.CurrentCulture, sunsetNoticeFormat, participle, when ); + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/README.md b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/README.md new file mode 100644 index 00000000..18e2fbd4 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/README.md @@ -0,0 +1,16 @@ +ASP.NET API versioning gives you a powerful, but easy-to-use method for adding API versioning semantics to your new +and existing REST services built with ASP.NET Core. The API versioning extensions define simple metadata attributes +and conventions that you use to describe which API versions are implemented by your services. + +This package contains the OpenAPI extensions which integrates [Microsoft.AspNetCore.OpenApi] with API Versioning. + +[Microsoft.AspNetCore.OpenApi]: https://www.nuget.org/packages/Microsoft.AspNetCore.OpenApi + +## Commonly Used Types + +- Asp.Versioning.OpenApi.IApiVersioningBuilderExtensions +- Asp.Versioning.OpenApi.IEndpointConventionBuilderExtensions +- Asp.Versioning.OpenApi.IEndpointRouteBuilderExtensions + +## Release Notes + diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Class.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Class.cs new file mode 100644 index 00000000..4d4ec845 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Class.cs @@ -0,0 +1,111 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Reflection; + +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Json; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; + +// HACK: all of these types are internal in Microsoft.AspNetCore.OpenApi +// REF: https://github.com/dotnet/aspnetcore/tree/main/src/OpenApi/src +internal static class Class +{ + public static class OpenApiDocumentService + { + private static readonly Func factory = NewFactory(); + + public static object New( IServiceProvider serviceProvider, string documentName ) => factory( serviceProvider, documentName ); + + private static Func NewFactory() + { + var constructor = Type.OpenApiDocumentService.GetConstructors().Single(); + var serviceProvider = Parameter( typeof( IServiceProvider ), "serviceProvider" ); + var documentName = Parameter( typeof( string ), "documentName" ); + var getRequiredService = typeof( ServiceProviderServiceExtensions ).GetMethod( + nameof( ServiceProviderServiceExtensions.GetRequiredService ), + [typeof( IServiceProvider ), typeof( System.Type )] )!; + var apiDescriptionGroupCollectionProvider = typeof( IApiDescriptionGroupCollectionProvider ); + var hostEnvironment = typeof( IHostEnvironment ); + var optionsMonitor = typeof( IOptionsMonitor ); + var server = typeof( IServer ); + var body = Expression.New( + constructor, + documentName, + Convert( Call( getRequiredService, serviceProvider, Constant( apiDescriptionGroupCollectionProvider ) ), apiDescriptionGroupCollectionProvider ), + Convert( Call( getRequiredService, serviceProvider, Constant( hostEnvironment ) ), hostEnvironment ), + Convert( Call( getRequiredService, serviceProvider, Constant( optionsMonitor ) ), optionsMonitor ), + serviceProvider, + Convert( Call( getRequiredService, serviceProvider, Constant( server ) ), server ) ); + var lambda = Lambda>( body, serviceProvider, documentName ); + + return lambda.Compile(); + } + } + + public static class OpenApiSchemaService + { + private static readonly Func factory = NewFactory(); + + public static object New( IServiceProvider serviceProvider, string documentName ) => factory( serviceProvider, documentName ); + + private static Func NewFactory() + { + var constructor = Type.OpenApiSchemaService.GetConstructors().Single(); + var serviceProvider = Parameter( typeof( IServiceProvider ), "serviceProvider" ); + var documentName = Parameter( typeof( string ), "documentName" ); + var getRequiredService = typeof( ServiceProviderServiceExtensions ).GetMethod( + nameof( ServiceProviderServiceExtensions.GetRequiredService ), + [typeof( IServiceProvider ), typeof( System.Type )] )!; + var jsonOptions = typeof( IOptions ); + var optionsMonitor = typeof( IOptionsMonitor ); + var body = Expression.New( + constructor, + documentName, + Convert( Call( getRequiredService, serviceProvider, Constant( jsonOptions ) ), jsonOptions ), + Convert( Call( getRequiredService, serviceProvider, Constant( optionsMonitor ) ), optionsMonitor ) ); + var lambda = Lambda>( body, serviceProvider, documentName ); + + return lambda.Compile(); + } + } + + public static class OpenApiDocumentProvider + { + private static readonly Func factory = NewFactory(); + + public static object New( IServiceProvider serviceProvider ) => factory( serviceProvider ); + + private static Func NewFactory() + { + var constructor = Type.OpenApiDocumentProvider.GetConstructors().Single(); + var serviceProvider = Parameter( typeof( IServiceProvider ), "serviceProvider" ); + var body = Expression.New( constructor, serviceProvider ); + var lambda = Lambda>( body, serviceProvider ); + + return lambda.Compile(); + } + } + + public static class NamedService + { + private static readonly Func factory = NewFactory(); + + public static object New( string name ) => factory( name ); + + private static Func NewFactory() + { + var constructor = Type.NamedService.GetConstructors().Single(); + var name = Parameter( typeof( string ), "name" ); + var body = Expression.New( constructor, name ); + var lambda = Lambda>( body, name ); + + return lambda.Compile(); + } + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Property.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Property.cs new file mode 100644 index 00000000..b8c7cec0 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Property.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Reflection; + +using Microsoft.AspNetCore.OpenApi; +using static System.Linq.Expressions.Expression; +using static System.Reflection.BindingFlags; + +// HACK: all of these properties are internal in Microsoft.AspNetCore.OpenApi +// REF: https://github.com/dotnet/aspnetcore/tree/main/src/OpenApi/src +internal static class Property +{ + private static readonly Action setDocumentName = NewSetDocumentName(); + + extension( OpenApiOptions options ) + { + public void SetDocumentName( string value ) => setDocumentName( options, value ); + } + + private static Action NewSetDocumentName() + { + var options = Parameter( typeof( OpenApiOptions ), "options" ); + var documentName = Parameter( typeof( string ), "documentName" ); + var property = typeof( OpenApiOptions ).GetProperty( nameof( OpenApiOptions.DocumentName ), Instance | NonPublic | Public )!; + var body = Assign( Property( options, property ), documentName ); + var lambda = Lambda>( body, options, documentName ); + + return lambda.Compile(); + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Type.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Type.cs new file mode 100644 index 00000000..83e39e9a --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Type.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Reflection; + +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +// HACK: all of these types are internal in Microsoft.AspNetCore.OpenApi +// REF: https://github.com/dotnet/aspnetcore/tree/main/src/OpenApi/src +internal static class Type +{ + [DynamicallyAccessedMembers( PublicConstructors )] + public static readonly System.Type IDocumentProvider = System.Type.GetType( "Microsoft.Extensions.ApiDescriptions.IDocumentProvider, Microsoft.AspNetCore.OpenApi", throwOnError: true )!; + + [DynamicallyAccessedMembers( PublicConstructors )] + public static readonly System.Type NamedService = System.Type.GetType( "Microsoft.AspNetCore.OpenApi.NamedService`1[[Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi]], Microsoft.AspNetCore.OpenApi", throwOnError: true )!; + + public static readonly System.Type IEnumerableOfNamedService = System.Type.GetType( "System.Collections.Generic.IEnumerable`1[[Microsoft.AspNetCore.OpenApi.NamedService`1[[Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi]], Microsoft.AspNetCore.OpenApi]], System.Private.CoreLib", throwOnError: true )!; + + public static readonly System.Type ListOfNamedService = System.Type.GetType( "System.Collections.Generic.List`1[[Microsoft.AspNetCore.OpenApi.NamedService`1[[Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi]], Microsoft.AspNetCore.OpenApi]], System.Private.CoreLib", throwOnError: true )!; + + [DynamicallyAccessedMembers( PublicConstructors )] + public static readonly System.Type OpenApiDocumentProvider = System.Type.GetType( "Microsoft.Extensions.ApiDescriptions.OpenApiDocumentProvider, Microsoft.AspNetCore.OpenApi", throwOnError: true )!; + + [DynamicallyAccessedMembers( PublicConstructors )] + public static readonly System.Type OpenApiDocumentService = System.Type.GetType( "Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi", throwOnError: true )!; + + [DynamicallyAccessedMembers( PublicConstructors )] + public static readonly System.Type OpenApiSchemaService = System.Type.GetType( "Microsoft.AspNetCore.OpenApi.OpenApiSchemaService, Microsoft.AspNetCore.OpenApi", throwOnError: true )!; + + [DynamicallyAccessedMembers( PublicConstructors )] + public static readonly System.Type OpenApiSchemaJsonOptions = System.Type.GetType( "Microsoft.AspNetCore.OpenApi.OpenApiSchemaJsonOptions, Microsoft.AspNetCore.OpenApi", throwOnError: true )!; +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/ReleaseNotes.txt b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/ReleaseNotes.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/ReleaseNotes.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/SR.Designer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/SR.Designer.cs new file mode 100644 index 00000000..cf5f5e1b --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/SR.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// +// 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.OpenApi { + 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", "18.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.OpenApi.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 API is deprecated.. + /// + internal static string DeprecatedNotice { + get { + return ResourceManager.GetString("DeprecatedNotice", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The API {0} deprecated on {1:d}.. + /// + internal static string DeprecatedNoticeFormat { + get { + return ResourceManager.GetString("DeprecatedNoticeFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The API {0} sunset on {1:d}.. + /// + internal static string SunsetNoticeFormat { + get { + return ResourceManager.GetString("SunsetNoticeFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to was. + /// + internal static string Was { + get { + return ResourceManager.GetString("Was", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to will be. + /// + internal static string WillBe { + get { + return ResourceManager.GetString("WillBe", resourceCulture); + } + } + } +} diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/SR.resx b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/SR.resx new file mode 100644 index 00000000..3b6c7e21 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/SR.resx @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 API {0} deprecated on {1:d}. + 0 = participle, 1 = the deprecation date and time + + + was + + + The API {0} sunset on {1:d}. + 0 = participle, 1 = the sunset date and time + + + will be + + + The API is deprecated. + + \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/ApiExplorerTransformer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/ApiExplorerTransformer.cs new file mode 100644 index 00000000..84b1d7d3 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/ApiExplorerTransformer.cs @@ -0,0 +1,360 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Transformers; + +using Asp.Versioning.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.Extensions.Primitives; +using Microsoft.OpenApi; +using System.Reflection; +using System.Text; +using System.Text.Json.Nodes; +using System.Threading; +using System.Threading.Tasks; + +/// +/// Represents a transformer used to apply API Explorer metadata to an +/// OpenAPI document. +/// +[CLSCompliant( false )] +public class ApiExplorerTransformer : + IOpenApiSchemaTransformer, + IOpenApiDocumentTransformer, + IOpenApiOperationTransformer +{ + /// + /// Initializes a new instance of the class. + /// + /// The options applied + /// to OpenAPI document descriptions. + public ApiExplorerTransformer( VersionedOpenApiOptions options ) => Options = options; + + /// + /// Gets the associated, versioned OpenAPI options. + /// + /// The associated options. + protected VersionedOpenApiOptions Options { get; } + + /// + /// Gets or sets the OpenApi extension name. + /// + /// The OpenAPI extension name. The default value is x-api-versioning. + protected string ExtensionName { get; set; } = "x-api-versioning"; + + /// + public Task TransformAsync( + OpenApiSchema schema, + OpenApiSchemaTransformerContext context, + CancellationToken cancellationToken ) + { + ArgumentNullException.ThrowIfNull( schema ); + ArgumentNullException.ThrowIfNull( context ); + + if ( schema.Default is null + && context.ParameterDescription?.DefaultValue is string value ) + { + schema.Default = JsonNode.Parse( $"\"{value}\"" ); + } + + return Task.CompletedTask; + } + + /// + public Task TransformAsync( + OpenApiDocument document, + OpenApiDocumentTransformerContext context, + CancellationToken cancellationToken ) + { + ArgumentNullException.ThrowIfNull( document ); + ArgumentNullException.ThrowIfNull( context ); + + UpdateFromAssemblyInfo( document, Options.Description ); + + document.Info.Version = Options.Description.ApiVersion.ToString(); + + UpdateDescriptionToMarkdown( document, Options.Description, Options.DocumentDescription ); + AddLinkExtensions( document, Options.Description ); + + return Task.CompletedTask; + } + + /// + public Task TransformAsync( + OpenApiOperation operation, + OpenApiOperationTransformerContext context, + CancellationToken cancellationToken ) + { + ArgumentNullException.ThrowIfNull( operation ); + ArgumentNullException.ThrowIfNull( context ); + + operation.Deprecated |= context.Description.IsDeprecated; + + if ( operation.Parameters is not { } parameters ) + { + return Task.CompletedTask; + } + + var descriptions = context.Description.ParameterDescriptions; + + for ( var i = 0; i < descriptions.Count; i++ ) + { + var description = descriptions[i]; + + if ( description.ModelMetadata is not { } metadata + || string.IsNullOrEmpty( metadata.Description ) ) + { + continue; + } + + for ( var j = 0; j < parameters.Count; j++ ) + { + var parameter = parameters[j]; + + if ( parameter.Name == description.Name + && string.IsNullOrEmpty( parameter.Description ) ) + { + parameter.Description = metadata.Description; + } + } + } + + return Task.CompletedTask; + } + + private static void UpdateFromAssemblyInfo( OpenApiDocument document, ApiVersionDescription api ) + { + if ( Assembly.GetEntryAssembly() is not { } assembly ) + { + return; + } + + var title = assembly.GetCustomAttribute()?.Title; + var description = assembly.GetCustomAttribute()?.Description; + + if ( !string.IsNullOrEmpty( title ) ) + { + document.Info.Title = $"{title} | {api.GroupName}"; + } + + if ( !string.IsNullOrEmpty( description ) ) + { + document.Info.Description = description; + } + } + + private void UpdateDescriptionToMarkdown( + OpenApiDocument document, + ApiVersionDescription api, + OpenApiDocumentDescriptionOptions options ) + { + var description = new StringBuilder( document.Info.Description ); + var links = new StringBuilder(); + + if ( api.DeprecationPolicy is { } deprecation ) + { + var notice = options.DeprecationNotice( deprecation ); + + if ( !string.IsNullOrEmpty( notice ) ) + { + AddSentence( description, notice ); + } + + if ( !options.HidePolicyLinks && deprecation.HasLinks ) + { + AddMarkdownLinks( links, deprecation.Links ); + } + } + else if ( api.IsDeprecated ) + { + var notice = options.DeprecationNotice( new() ); + + if ( !string.IsNullOrEmpty( notice ) ) + { + AddSentence( description, notice ); + } + } + + if ( api.SunsetPolicy is { } sunset ) + { + var notice = options.SunsetNotice( sunset ); + + if ( !string.IsNullOrEmpty( notice ) ) + { + AddSentence( description, notice ); + } + + if ( !options.HidePolicyLinks && sunset.HasLinks ) + { + AddMarkdownLinks( links, sunset.Links ); + } + } + + if ( links.Length > 0 ) + { + description.AppendLine() + .AppendLine() + .AppendLine( "### Links" ) + .AppendLine() + .Append( links ); + } + + document.Info.Description = description.ToString(); + } + + /// + /// Determines if the specified link should be rendered. + /// + /// The link to evaluate. + /// True if the link should be rendered; otherwise, false. + /// The default implementation only renders text/html links. + protected virtual bool ShouldRenderLink( LinkHeaderValue link ) + { + ArgumentNullException.ThrowIfNull( link ); + return StringSegmentComparer.OrdinalIgnoreCase.Equals( link.Type, "text/html" ); + } + + /// + /// Renders the specified link as markdown. + /// + /// The builder to render the Markdown into. + /// The link to render. + protected virtual void RenderLink( StringBuilder markdown, LinkHeaderValue link ) + { + ArgumentNullException.ThrowIfNull( markdown ); + ArgumentNullException.ThrowIfNull( link ); + + if ( StringSegment.IsNullOrEmpty( link.Title ) ) + { + if ( link.LinkTarget.IsAbsoluteUri ) + { + markdown.Append( "- " ).AppendLine( link.LinkTarget.OriginalString ); + } + else + { + markdown.Append( "- " ) + .Append( link.LinkTarget.OriginalString ) + .AppendLine( "" ); + } + } + else + { + markdown.Append( "- [" ) + .Append( link.Title.ToString() ) + .Append( "](" ) + .Append( link.LinkTarget.OriginalString ) + .Append( ')' ); + } + } + + private static void AddSentence( StringBuilder text, string sentence ) + { + if ( text.Length > 0 ) + { + if ( text[^1] != '.' ) + { + text.Append( '.' ); + } + + text.Append( ' ' ); + } + + text.Append( sentence ); + } + + private void AddMarkdownLinks( StringBuilder markdown, IList links ) + { + var appendLine = markdown.Length > 0; + + for ( var i = 0; i < links.Count; i++ ) + { + var link = links[i]; + + if ( ShouldRenderLink( link ) ) + { + if ( appendLine ) + { + markdown.AppendLine(); + } + + RenderLink( markdown, link ); + appendLine = true; + } + } + } + + private void AddLinkExtensions( OpenApiDocument document, ApiVersionDescription api ) + { + var array = new JsonArray(); + + if ( api.DeprecationPolicy is { } deprecation && deprecation.HasLinks ) + { + AddLinks( array, deprecation.Links ); + } + + if ( api.SunsetPolicy is { } sunset && sunset.HasLinks ) + { + AddLinks( array, sunset.Links ); + } + + if ( array.Count > 0 ) + { + var extensions = document.Extensions ??= new Dictionary(); + extensions[ExtensionName] = new JsonNodeExtension( array ); + } + } + + [UnconditionalSuppressMessage( "ILLink", "IL2026" )] + [UnconditionalSuppressMessage( "ILLink", "IL3050" )] + private void AddLinks( JsonArray array, IList links ) + { + for ( var i = 0; i < links.Count; i++ ) + { + array.Add( ToJson( links[i] ) ); + } + } + + /// + /// Converts the specified link into JSON as an OpenAPI extension. + /// + /// The link to convert. + /// The OpenAPI extension JSON node. + protected virtual JsonObject ToJson( LinkHeaderValue link ) + { + ArgumentNullException.ThrowIfNull( link ); + + var obj = new JsonObject(); + + if ( link.Title.HasValue ) + { + obj["title"] = link.Title.ToString(); + } + + if ( link.Type.HasValue ) + { + obj["type"] = link.Type.ToString(); + } + + obj["rel"] = link.RelationType.ToString(); + obj["url"] = link.LinkTarget.ToString(); + + if ( link.Media.HasValue ) + { + obj["media"] = link.Media.ToString(); + } + + if ( link.Languages.Count > 0 ) + { + obj["lang"] = new JsonArray( link.Languages.Select( l => JsonNode.Parse( l.ToString() ) ).ToArray() ); + } + + foreach ( var (key, value) in link.Extensions ) + { + obj[key.ToString()] = value.ToString(); + } + + return obj; + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/StringBuilderExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/StringBuilderExtensions.cs new file mode 100644 index 00000000..b5bc8209 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/StringBuilderExtensions.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Transformers; + +using System.Text; + +internal static class StringBuilderExtensions +{ + extension( StringBuilder sb ) + { + public StringBuilder AppendWith( Func append, T arg ) => append( sb, arg ); + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs new file mode 100644 index 00000000..d25a2703 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs @@ -0,0 +1,140 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Transformers; + +using System.Collections.Concurrent; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +/// +/// Provides access to XML documentation comments, which enables the retrieval of summaries, remarks, return values, +/// examples, and parameter descriptions. +/// +public class XmlComments +{ + private readonly ConcurrentDictionary members = new(); + + /// + /// Initializes a new instance of the class. + /// + /// The file path of the XML comments to read. + protected XmlComments( string path ) => Xml = File.Exists( path ) ? XDocument.Load( path ) : new(); + + /// + /// Creates and returns new from the specified file. + /// + /// The file path of the XML comments to read. + /// New . + public static XmlComments FromFile( string path ) => new( path ); + + internal bool IsEmpty => Xml.Root is null; + + /// + /// Gets the underlying XML document. + /// + /// The for the source XML comments file. + protected XDocument Xml { get; } + + /// + /// Gets the summary from the specified member, if any. + /// + /// The member to get the summary from. + /// The corresponding summary or an empty string. + public string GetSummary( MemberInfo member ) + => GetMember( member )?.Element( "summary" )?.Value.Trim() ?? string.Empty; + + /// + /// Gets the description from the specified member, if any. + /// + /// The member to get the description from. + /// The corresponding description or an empty string. + public string GetDescription( MemberInfo member ) + => GetMember( member )?.Element( "description" )?.Value.Trim() ?? string.Empty; + + /// + /// Gets the remarks from the specified member, if any. + /// + /// The member to get the remarks from. + /// The corresponding remarks or an empty string. + public string GetRemarks( MemberInfo member ) + => GetMember( member )?.Element( "remarks" )?.Value.Trim() ?? string.Empty; + + /// + /// Gets the returns from the specified member, if any. + /// + /// The member to get the returns from. + /// The corresponding returns or an empty string. + public string GetReturns( MemberInfo member ) + => GetMember( member )?.Element( "returns" )?.Value.Trim() ?? string.Empty; + + /// + /// Gets the param description from the specified member, if any. + /// + /// The member to get the parameter from. + /// The name of the parameter. + /// The corresponding returns or an empty string. + public string GetParameterDescription( MemberInfo member, string name ) + { + if ( GetMember( member ) is { } element ) + { + return element.Elements( "param" ) + .FirstOrDefault( x => x.Attribute( "name" )?.Value == name )? + .Value + .Trim() ?? string.Empty; + } + + return string.Empty; + } + + /// + /// Gets the response description from the specified member, if any. + /// + /// The member to get the parameter from. + /// The status code to get the description for. + /// The corresponding response description or an empty string. + /// This method is based on the custom extension element that was introduced and popularized by + /// Swashbuckle; for example, <response code="200">The operation was successful</response>. See the + /// tutorial + /// for more information. + public string GetResponseDescription( MemberInfo member, int statusCode ) + => GetResponseDescription( member, statusCode.ToString( CultureInfo.InvariantCulture ) ); + + /// + /// Gets the response description from the specified member, if any. + /// + /// The member to get the parameter from. + /// The status code to get the description for. + /// The corresponding response description or an empty string. + /// This method is based on the custom extension element that was introduced and popularized by + /// Swashbuckle; for example, <response code="200">The operation was successful</response>. See the + /// tutorial + /// for more information. + public string GetResponseDescription( MemberInfo member, string statusCode ) + { + if ( GetMember( member ) is { } element ) + { + return element.Elements( "response" ) + .FirstOrDefault( x => x.Attribute( "code" )?.Value == statusCode )? + .Value + .Trim() ?? string.Empty; + } + + return string.Empty; + } + + /// + /// Gets the member documentation for the specified type member. + /// + /// The member to get the information for. + /// The representing the matching member element or null. + protected XElement? GetMember( MemberInfo member ) => + GetMemberById( XmlCommentsProvider.GetDocumentationMemberId( member ) ); + + private static XElement? FindMember( XDocument xml, string key ) => + xml.Descendants( "member" ).FirstOrDefault( member => member.Attribute( "name" )?.Value == key ); + + private XElement? GetMemberById( string id ) => members.GetOrAdd( id, key => FindMember( Xml, key ) ); +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsFile.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsFile.cs new file mode 100644 index 00000000..375deaf2 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsFile.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Transformers; + +using Microsoft.Extensions.Hosting; +using System.Collections.Generic; +using System.Reflection; +using FilePath = System.IO.Path; + +internal sealed class XmlCommentsFile +{ + [UnconditionalSuppressMessage( "ILLink", "IL3000" )] + public XmlCommentsFile( Assembly[] assemblies, IHostEnvironment environment ) + { + var paths = new List( capacity: 3 ) + { + default!, + environment.ContentRootPath, + AppContext.BaseDirectory, + }; + string? directory; + int start; + + for ( var i = 0; i < assemblies.Length; i++ ) + { + var assembly = assemblies[i]; + var fileName = FilePath.ChangeExtension( assembly.GetName().Name, ".xml" ); + + if ( string.IsNullOrEmpty( fileName ) ) + { + continue; + } + + try + { + directory = FilePath.GetDirectoryName( assembly.Location ); + } + catch ( NotSupportedException ) + { + directory = default; + } + + if ( string.IsNullOrEmpty( directory ) ) + { + start = 1; + } + else + { + paths[0] = directory; + start = 0; + } + + for ( var j = start; j < paths.Count; j++ ) + { + var path = FilePath.Join( paths[j], fileName ); + + if ( File.Exists( path ) ) + { + Path = path; + return; + } + } + } + + Path = string.Empty; + } + + public string Path { get; } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsProvider.cs new file mode 100644 index 00000000..54180aa4 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsProvider.cs @@ -0,0 +1,124 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Transformers; + +using System.Reflection; +using System.Text; + +internal static class XmlCommentsProvider +{ + public static string GetDocumentationMemberId( MemberInfo member ) + { + return member switch + { + Type t => new StringBuilder( "T:" ).AppendWith( GetTypeName, t ).ToString(), + MethodInfo m => new StringBuilder( "M:" ).AppendWith( GetTypeName, m.DeclaringType! ).Append( '.' ).AppendWith( GetMethodSignature, m ).ToString(), + ConstructorInfo c => new StringBuilder( "M:" ).AppendWith( GetTypeName, c.DeclaringType! ).Append( ".#ctor" ).AppendWith( GetParameters, c.GetParameters() ).ToString(), + PropertyInfo p => new StringBuilder( "P:" ).AppendWith( GetTypeName, p.DeclaringType! ).Append( '.' ).AppendWith( GetProperty, p ).ToString(), + FieldInfo f => new StringBuilder( "F:" ).AppendWith( GetTypeName, f.DeclaringType! ).Append( '.' ).Append( f.Name ).ToString(), + EventInfo e => new StringBuilder( "E:" ).AppendWith( GetTypeName, e.DeclaringType! ).Append( '.' ).Append( e.Name ).ToString(), + _ => string.Empty, + }; + } + + private static StringBuilder GetTypeName( StringBuilder builder, Type type ) + { + if ( type.IsGenericType ) + { + var name = type.FullName ?? type.Name; + var i = name.IndexOf( '`', StringComparison.Ordinal ); + + if ( i >= 0 ) + { + name = name[..i] + "``" + type.GetGenericArguments().Length; + } + + return builder.Append( name.Replace( '+', '.' ) ); + } + + return builder.Append( type.FullName ?? type.Name ).Replace( '+', '.' ); + } + + private static StringBuilder GetMethodSignature( StringBuilder builder, MethodInfo method ) + { + builder.Append( method.Name ); + + if ( method.IsGenericMethod ) + { + builder.Append( "``" ).Append( method.GetGenericArguments().Length ); + } + + return builder.AppendWith( GetParameters, method.GetParameters() ); + } + + private static StringBuilder GetParameters( StringBuilder builder, ParameterInfo[] parameters ) + { + if ( parameters.Length == 0 ) + { + return builder; + } + + builder.Append( '(' ); + builder.AppendWith( GetParameterTypeName, parameters[0].ParameterType ); + + for ( var i = 1; i < parameters.Length; i++ ) + { + builder.Append( ',' ).AppendWith( GetParameterTypeName, parameters[i].ParameterType ); + } + + return builder.Append( ')' ); + } + + private static StringBuilder GetParameterTypeName( StringBuilder builder, Type type ) + { + if ( type.IsGenericParameter ) + { + return builder.Append( "``" ).Append( type.GenericParameterPosition ); + } + + if ( type.IsArray ) + { + return builder.AppendWith( GetParameterTypeName, type.GetElementType()! ).Append( "[]" ); + } + + if ( type.IsGenericType ) + { + var name = type.FullName ?? type.Name; + var args = type.GetGenericArguments(); + var i = name.IndexOf( '`', StringComparison.Ordinal ); + + if ( i >= 0 ) + { + builder.Append( name[0..i] ); + } + else + { + builder.Append( name ); + } + + builder.Append( '{' ); + builder.AppendWith( GetParameterTypeName, args[0] ); + + for ( i = 1; i < args.Length; i++ ) + { + builder.Append( ',' ).AppendWith( GetParameterTypeName, args[i] ); + } + + return builder.Append( '}' ); + } + + return builder.Append( type.FullName ?? type.Name ); + } + + private static StringBuilder GetProperty( StringBuilder builder, PropertyInfo property ) + { + var parameters = property.GetIndexParameters(); + + if ( parameters.Length == 0 ) + { + return builder.Append( property.Name ); + } + + return builder.Append( "Item" ).AppendWith( GetParameters, parameters ); + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsTransformer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsTransformer.cs new file mode 100644 index 00000000..664551c6 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlCommentsTransformer.cs @@ -0,0 +1,162 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Transformers; + +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi; +using System; +using System.Reflection; +using System.Threading; +using static System.Reflection.BindingFlags; + +/// +/// Represents a transformer used to apply XML comments to an +/// OpenAPI document. +/// +[CLSCompliant( false )] +public class XmlCommentsTransformer : IOpenApiSchemaTransformer, IOpenApiOperationTransformer +{ + internal XmlCommentsTransformer( XmlCommentsFile file ) : + this( file.Path ) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The file path of the XML commands file. + public XmlCommentsTransformer( string path ) => Documentation = XmlComments.FromFile( path ); + + internal bool IsEmpty => Documentation.IsEmpty; + + /// + /// Gets the documentation associated with the transformer. + /// + protected XmlComments Documentation { get; } + + /// + public virtual Task TransformAsync( + OpenApiSchema schema, + OpenApiSchemaTransformerContext context, + CancellationToken cancellationToken ) + { + ArgumentNullException.ThrowIfNull( schema ); + ArgumentNullException.ThrowIfNull( context ); + + if ( schema.Properties is not { } properties + || context.JsonTypeInfo?.Type is not Type type ) + { + return Task.CompletedTask; + } + + if ( string.IsNullOrEmpty( schema.Description ) ) + { + schema.Description = Documentation.GetSummary( type ); + } + + foreach ( var (name, prop) in properties ) + { + if ( prop is not null + && string.IsNullOrEmpty( prop.Description ) + && type.GetProperty( name, IgnoreCase | Instance | Public ) is { } property ) + { + prop.Description = Documentation.GetSummary( property ); + } + } + + return Task.CompletedTask; + } + + /// + public virtual Task TransformAsync( + OpenApiOperation operation, + OpenApiOperationTransformerContext context, + CancellationToken cancellationToken ) + { + ArgumentNullException.ThrowIfNull( operation ); + ArgumentNullException.ThrowIfNull( context ); + + if ( !TryResolveMethod( context.Description.ActionDescriptor, out var method ) ) + { + return Task.CompletedTask; + } + + if ( string.IsNullOrEmpty( operation.Summary ) ) + { + operation.Summary = Documentation.GetSummary( method ); + } + + if ( string.IsNullOrEmpty( operation.Description ) ) + { + operation.Description = Documentation.GetDescription( method ); + } + + if ( operation.Responses is { } responses ) + { + foreach ( var (statusCode, response) in responses ) + { + var description = Documentation.GetResponseDescription( method, statusCode ); + + if ( !string.IsNullOrEmpty( description ) ) + { + response.Description = description; + } + } + } + + var parameters = operation.Parameters; + var args = context.Description.ParameterDescriptions; + + if ( parameters is null || parameters.Count == 0 || args.Count == 0 ) + { + return Task.CompletedTask; + } + + for ( var i = 0; i < parameters.Count; i++ ) + { + var parameter = parameters[i]; + + if ( !string.IsNullOrEmpty( parameter.Name ) && string.IsNullOrEmpty( parameter.Description ) ) + { + for ( var j = 0; j < args.Count; j++ ) + { + var arg = args[i]; + + if ( arg.Name == parameter.Name ) + { + var name = arg.ParameterDescriptor.Name; + parameter.Description = Documentation.GetParameterDescription( method, name ); + } + } + } + } + + return Task.CompletedTask; + } + + private static bool TryResolveMethod( ActionDescriptor action, [MaybeNullWhen( false )] out MethodInfo method ) + { + if ( action is ControllerActionDescriptor controller ) + { + method = controller.MethodInfo; + return true; + } + else + { + var metadata = action.EndpointMetadata; + + for ( var i = 0; i < metadata.Count; i++ ) + { + if ( ( method = metadata[i] as MethodInfo ) is not null ) + { + return true; + } + } + } + + method = default; + return false; + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/VersionedOpenApiOptions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/VersionedOpenApiOptions.cs new file mode 100644 index 00000000..cf1cd9a8 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/VersionedOpenApiOptions.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi; + +using Asp.Versioning.ApiExplorer; +using Microsoft.AspNetCore.OpenApi; + +/// +/// Represents the versioned OpenAPI configuration options. +/// +public class VersionedOpenApiOptions +{ + /// + /// Gets the associated API version description. + /// + /// The associated associated API version description. + public required ApiVersionDescription Description { get; init; } + + /// + /// Gets the OpenAPI options. + /// + /// The associated OpenAPI options. + [CLSCompliant( false )] + public required OpenApiOptions Document { get; init; } + + /// + /// Gets the OpenAPI document description configuration options. + /// + /// These options provide additional configuration + /// for indicating which additional information should be included in an OpenAPI document description such + /// as the deprecation and sunset policies. + public required OpenApiDocumentDescriptionOptions DocumentDescription { get; init; } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Builder/IEndpointConventionBuilderExtensionsTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Builder/IEndpointConventionBuilderExtensionsTest.cs index 13907a8c..448d8b07 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Builder/IEndpointConventionBuilderExtensionsTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Builder/IEndpointConventionBuilderExtensionsTest.cs @@ -68,11 +68,11 @@ public void with_api_version_set_should_apply_to_endpoint() .BeEquivalentTo( new ApiVersionMetadata( new ApiVersionModel( - new ApiVersion[] { new( 0.9 ), new( 1.0 ) }, - new ApiVersion[] { new( 1.0 ) }, - new ApiVersion[] { new( 0.9 ) }, - new ApiVersion[] { new( 2.0 ) }, - new ApiVersion[] { new( 2.0, "beta" ) } ), + [new( 0.9 ), new( 1.0 )], + [new( 1.0 )], + [new( 0.9 )], + [new( 2.0 )], + [new( 2.0, "beta" )] ), ApiVersionModel.Empty ) ); } @@ -120,17 +120,17 @@ public void with_api_version_set_should_apply_across_endpoints() .BeEquivalentTo( new ApiVersionMetadata( new ApiVersionModel( - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - Array.Empty(), - Array.Empty(), - Array.Empty() ), + [new( 1.0 ), new( 2.0 )], + [new( 1.0 ), new( 2.0 )], + [], + [], + [] ), new ApiVersionModel( - new ApiVersion[] { new( 0.9 ), new( 1.0 ) }, - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - new ApiVersion[] { new( 0.9 ) }, - Array.Empty(), - Array.Empty() ) ) ); + [new( 0.9 ), new( 1.0 )], + [new( 1.0 ), new( 2.0 )], + [new( 0.9 )], + [], + [] ) ) ); endpoints[1].Metadata .OfType() @@ -139,17 +139,17 @@ public void with_api_version_set_should_apply_across_endpoints() .BeEquivalentTo( new ApiVersionMetadata( new ApiVersionModel( - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - Array.Empty(), - Array.Empty(), - Array.Empty() ), + [new( 1.0 ), new( 2.0 )], + [new( 1.0 ), new( 2.0 )], + [], + [], + [] ), new ApiVersionModel( - new ApiVersion[] { new( 2.0 ) }, - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - new ApiVersion[] { new( 0.9 ) }, - Array.Empty(), - Array.Empty() ) ) ); + [new( 2.0 )], + [new( 1.0 ), new( 2.0 )], + [new( 0.9 )], + [], + [] ) ) ); } [Fact] @@ -184,11 +184,11 @@ public void with_api_version_set_should_collate_grouped_endpoint() new ApiVersionMetadata( ApiVersionModel.Empty, new ApiVersionModel( - new ApiVersion[] { new( 0.9 ), new( 1.0 ) }, - new ApiVersion[] { new( 1.0 ) }, - new ApiVersion[] { new( 0.9 ) }, - new ApiVersion[] { new( 2.0 ) }, - new ApiVersion[] { new( 2.0, "beta" ) } ) ) ); + [new( 0.9 ), new( 1.0 )], + [new( 1.0 )], + [new( 0.9 )], + [new( 2.0 )], + [new( 2.0, "beta" )] ) ) ); } [Fact] @@ -225,11 +225,11 @@ public void with_api_version_set_should_collate_across_grouped_endpoints() new ApiVersionMetadata( ApiVersionModel.Empty, new ApiVersionModel( - new ApiVersion[] { new( 0.9 ), new( 1.0 ) }, - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - new ApiVersion[] { new( 0.9 ) }, - Array.Empty(), - Array.Empty() ) ) ); + [new( 0.9 ), new( 1.0 )], + [new( 1.0 ), new( 2.0 )], + [new( 0.9 )], + [], + [] ) ) ); endpoints[1].Metadata .OfType() @@ -239,11 +239,11 @@ public void with_api_version_set_should_collate_across_grouped_endpoints() new ApiVersionMetadata( ApiVersionModel.Empty, new ApiVersionModel( - new ApiVersion[] { new( 2.0 ) }, - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - new ApiVersion[] { new( 0.9 ) }, - Array.Empty(), - Array.Empty() ) ) ); + [new( 2.0 )], + [new( 1.0 ), new( 2.0 )], + [new( 0.9 )], + [], + [] ) ) ); endpoints[2].Metadata .OfType() @@ -319,7 +319,7 @@ public void report_api_versions_should_add_convention() reportApiVersions = endpoint.Metadata.OfType().First(); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.ReportApiVersions(); @@ -345,7 +345,7 @@ public void is_api_version_neutral_should_add_convention() versionNeutral = endpoint.Metadata.OfType().First(); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.IsApiVersionNeutral(); @@ -371,7 +371,7 @@ public void has_api_version_should_add_convention() provider = endpoint.Metadata.OfType().First(); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.HasApiVersion( 1.0 ); @@ -400,7 +400,7 @@ public void has_api_version_should_propagate_to_version_set() callback( endpoint ); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.HasApiVersion( 1.0 ); @@ -426,7 +426,7 @@ public void has_deprecated_api_version_should_add_convention() provider = endpoint.Metadata.OfType().First(); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.HasDeprecatedApiVersion( 0.9 ); @@ -455,7 +455,7 @@ public void has_deprecated_api_version_should_propagate_to_version_set() callback( endpoint ); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.HasDeprecatedApiVersion( 0.9 ); @@ -481,7 +481,7 @@ public void advertises_api_version_should_add_convention() provider = endpoint.Metadata.OfType().First(); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.AdvertisesApiVersion( 42.0 ); @@ -510,7 +510,7 @@ public void advertises_api_version_should_propagate_to_version_set() callback( endpoint ); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.AdvertisesApiVersion( 42.0 ); @@ -536,7 +536,7 @@ public void advertises_deprecated_api_version_should_add_convention() provider = endpoint.Metadata.OfType().First(); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.AdvertisesDeprecatedApiVersion( 42.0, "rc" ); @@ -565,7 +565,7 @@ public void advertises_deprecated_api_version_should_propagate_to_version_set() callback( endpoint ); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.AdvertisesDeprecatedApiVersion( 42.0, "rc" ); @@ -591,7 +591,7 @@ public void map_to_api_version_should_add_convention() provider = endpoint.Metadata.OfType().First(); } ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act route.MapToApiVersion( 2.0 ); @@ -614,7 +614,7 @@ public void map_to_api_version_should_throw_exception_without_version_set() conventions.Setup( b => b.Add( It.IsAny>() ) ) .Callback( ( Action callback ) => callback( Mock.Of() ) ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act var mapToApiVersion = () => route.MapToApiVersion( 2.0 ); @@ -632,7 +632,7 @@ public void has_api_version_should_throw_exception_without_version_set() conventions.Setup( b => b.Add( It.IsAny>() ) ) .Callback( ( Action callback ) => callback( Mock.Of() ) ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act var hasApiVersion = () => route.HasApiVersion( 2.0 ); @@ -650,7 +650,7 @@ public void has_deprecated_api_version_should_throw_exception_without_version_se conventions.Setup( b => b.Add( It.IsAny>() ) ) .Callback( ( Action callback ) => callback( Mock.Of() ) ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act var hasDeprecatedApiVersion = () => route.HasDeprecatedApiVersion( 2.0 ); @@ -668,7 +668,7 @@ public void advertises_api_version_should_throw_exception_without_version_set() conventions.Setup( b => b.Add( It.IsAny>() ) ) .Callback( ( Action callback ) => callback( Mock.Of() ) ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act var advertisesApiVersion = () => route.AdvertisesApiVersion( 2.0 ); @@ -686,7 +686,7 @@ public void advertises_deprecated_api_version_should_throw_exception_without_ver conventions.Setup( b => b.Add( It.IsAny>() ) ) .Callback( ( Action callback ) => callback( Mock.Of() ) ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act var advertisesDeprecatedApiVersion = () => route.AdvertisesDeprecatedApiVersion( 2.0 ); @@ -704,7 +704,7 @@ public void is_api_version_neutral_should_throw_exception_without_version_set() conventions.Setup( b => b.Add( It.IsAny>() ) ) .Callback( ( Action callback ) => callback( Mock.Of() ) ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act var isApiVersionNeutral = () => route.IsApiVersionNeutral(); @@ -722,7 +722,7 @@ public void reports_api_versions_should_throw_exception_without_version_set() conventions.Setup( b => b.Add( It.IsAny>() ) ) .Callback( ( Action callback ) => callback( Mock.Of() ) ); - var route = new RouteHandlerBuilder( new[] { conventions.Object } ); + var route = new RouteHandlerBuilder( [conventions.Object] ); // act var reportsApiVersions = () => route.ReportApiVersions(); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/CompositeApiVersionReaderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/CompositeApiVersionReaderTest.cs index f3881e90..8efb97a0 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/CompositeApiVersionReaderTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/CompositeApiVersionReaderTest.cs @@ -32,7 +32,7 @@ public void read_should_return_ambiguous_api_versions() { // arrange var query = new Mock(); - var headers = new HeaderDictionary() { ["api-version"] = new StringValues( new[] { "1.0" } ) }; + var headers = new HeaderDictionary() { ["api-version"] = new StringValues( ["1.0"] ) }; var request = new Mock(); var reader = ApiVersionReader.Combine( new QueryStringApiVersionReader(), new HeaderApiVersionReader( "api-version" ) ); @@ -52,7 +52,7 @@ public void read_should_not_throw_exception_when_duplicate_api_versions_are_requ { // arrange var query = new Mock(); - var headers = new HeaderDictionary() { ["api-version"] = new StringValues( new[] { "1.0" } ) }; + var headers = new HeaderDictionary() { ["api-version"] = new StringValues( ["1.0"] ) }; var request = new Mock(); var reader = ApiVersionReader.Combine( new QueryStringApiVersionReader(), new HeaderApiVersionReader( "api-version" ) ); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/CurrentImplementationApiVersionSelectorTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/CurrentImplementationApiVersionSelectorTest.cs index e13ad308..a4396f9a 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/CurrentImplementationApiVersionSelectorTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/CurrentImplementationApiVersionSelectorTest.cs @@ -8,10 +8,13 @@ public class CurrentImplementationApiVersionSelectorTest { [Theory] [ClassData( typeof( MaxSelectVersionData ) )] - public void select_version_should_return_max_api_version( IEnumerable supportedVersions, IEnumerable deprecatedVersions, ApiVersion expectedVersion ) + public void select_version_should_return_max_api_version( + IEnumerable supportedVersions, + IEnumerable deprecatedVersions, + ApiVersion expectedVersion ) { // arrange - var options = new ApiVersioningOptions() { DefaultApiVersion = new ApiVersion( 42, 0 ) }; + var options = new ApiVersioningOptions() { DefaultApiVersion = new( 42, 0 ) }; var selector = new CurrentImplementationApiVersionSelector( options ); var request = Mock.Of(); var model = new ApiVersionModel( supportedVersions, deprecatedVersions ); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DefaultApiVersionReporterTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DefaultApiVersionReporterTest.cs index 39971144..c5283fd3 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DefaultApiVersionReporterTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DefaultApiVersionReporterTest.cs @@ -26,17 +26,17 @@ public void report_should_add_expected_headers() }; var serviceProvider = new Mock(); var apiModel = new ApiVersionModel( - declaredVersions: new ApiVersion[] { new( 0.9 ), new( 1.0 ), new( 2.0 ) }, - supportedVersions: new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - deprecatedVersions: new[] { new ApiVersion( 0.9 ) }, - advertisedVersions: Enumerable.Empty(), - deprecatedAdvertisedVersions: Enumerable.Empty() ); + declaredVersions: [new( 0.9 ), new( 1.0 ), new( 2.0 )], + supportedVersions: [new( 1.0 ), new( 2.0 )], + deprecatedVersions: [new ApiVersion( 0.9 )], + advertisedVersions: [], + deprecatedAdvertisedVersions: [] ); var endpointModel = new ApiVersionModel( - declaredVersions: new ApiVersion[] { new( 1.0 ) }, - supportedVersions: new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - deprecatedVersions: new[] { new ApiVersion( 0.9 ) }, - advertisedVersions: Enumerable.Empty(), - deprecatedAdvertisedVersions: Enumerable.Empty() ); + declaredVersions: [new( 1.0 )], + supportedVersions: [new( 1.0 ), new( 2.0 )], + deprecatedVersions: [new ApiVersion( 0.9 )], + advertisedVersions: [], + deprecatedAdvertisedVersions: [] ); var metadata = new ApiVersionMetadata( apiModel, endpointModel, "Test" ); var endpoint = new Endpoint( c => Task.CompletedTask, new( metadata ), default ); var endpoints = new Mock(); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DependencyInjection/IServiceCollectionExtensionsTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DependencyInjection/IServiceCollectionExtensionsTest.cs index 8ed7463e..094b2c7c 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DependencyInjection/IServiceCollectionExtensionsTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DependencyInjection/IServiceCollectionExtensionsTest.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.Extensions.DependencyInjection; using Asp.Versioning; diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/ErrorObjectWriterTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/ErrorObjectWriterTest.cs index 822f619b..04aac15c 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/ErrorObjectWriterTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/ErrorObjectWriterTest.cs @@ -10,6 +10,18 @@ namespace Asp.Versioning; public class ErrorObjectWriterTest { + private static IOptions JsonOptions => Options.Create( + new JsonOptions() + { + SerializerOptions = + { + TypeInfoResolverChain = + { + ErrorObjectWriter.DefaultJsonSerializerContext, + }, + }, + } ); + [Theory] [InlineData( "https://docs.api-versioning.org/problems#unsupported" )] [InlineData( "https://docs.api-versioning.org/problems#unspecified" )] @@ -18,7 +30,7 @@ public class ErrorObjectWriterTest public void can_write_should_be_true_for_api_versioning_problem_types( string type ) { // arrange - var writer = new ErrorObjectWriter( Options.Create( new JsonOptions() ) ); + var writer = new ErrorObjectWriter( JsonOptions ); var context = new ProblemDetailsContext() { HttpContext = new DefaultHttpContext(), @@ -40,7 +52,7 @@ public void can_write_should_be_false_for_other_problem_types() { // arrange const string BadRequest = "https://tools.ietf.org/html/rfc7231#section-6.5.1"; - var writer = new ErrorObjectWriter( Options.Create( new JsonOptions() ) ); + var writer = new ErrorObjectWriter( JsonOptions ); var context = new ProblemDetailsContext() { HttpContext = new DefaultHttpContext(), @@ -75,12 +87,14 @@ public async Task write_async_should_output_expected_json() }, }; - var writer = new ErrorObjectWriter( Options.Create( new JsonOptions() ) ); + var writer = new ErrorObjectWriter( JsonOptions ); using var stream = new MemoryStream(); + var feature = new StreamResponseBodyFeature( stream ); var response = new Mock() { CallBase = true }; var httpContext = new Mock() { CallBase = true }; - response.SetupGet( r => r.Body ).Returns( stream ); + response.SetupProperty( r => r.Body, feature.Stream ); + response.SetupGet( r => r.BodyWriter ).Returns( feature.Writer ); response.SetupProperty( r => r.ContentType ); response.SetupGet( r => r.HttpContext ).Returns( () => httpContext.Object ); httpContext.SetupGet( c => c.Response ).Returns( response.Object ); @@ -104,7 +118,7 @@ public async Task write_async_should_output_expected_json() // act await writer.WriteAsync( context ); - await stream.FlushAsync(); + await stream.FlushAsync( TestContext.Current.CancellationToken ); stream.Position = 0; var error = await DeserializeByExampleAsync( stream, example ); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/HeaderApiVersionReaderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/HeaderApiVersionReaderTest.cs index a5bd98e6..56288d7e 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/HeaderApiVersionReaderTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/HeaderApiVersionReaderTest.cs @@ -30,7 +30,7 @@ public void read_should_retrieve_version_from_header( string headerName, string public void read_should_return_ambiguous_api_versions() { // arrange - var headers = new HeaderDictionary() { ["api-version"] = new StringValues( new[] { "1.0", "2.0" } ) }; + var headers = new HeaderDictionary() { ["api-version"] = new StringValues( ["1.0", "2.0"] ) }; var request = new Mock(); var reader = new HeaderApiVersionReader() { HeaderNames = { "api-version" } }; diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Http/HttpContextExtensionsTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Http/HttpContextExtensionsTest.cs index e10099a3..ad038f19 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Http/HttpContextExtensionsTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Http/HttpContextExtensionsTest.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Http; using Asp.Versioning; @@ -83,7 +85,7 @@ public void http_context_should_return_requested_api_version() httpContext.SetupProperty( c => c.RequestServices, serviceProvider.Object ); // act - var result = httpContext.Object.GetRequestedApiVersion(); + var result = httpContext.Object.RequestedApiVersion; // assert result.Should().Be( version ); @@ -109,7 +111,7 @@ public void http_context_should_return_null_api_version_when_the_value_is_invali httpContext.SetupProperty( c => c.RequestServices, serviceProvider.Object ); // act - var result = httpContext.Object.GetRequestedApiVersion(); + var result = httpContext.Object.RequestedApiVersion; // assert result.Should().BeNull(); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Http/HttpResponseExtensionsTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Http/HttpResponseExtensionsTest.cs index 45d46645..cbfacd7b 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Http/HttpResponseExtensionsTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Http/HttpResponseExtensionsTest.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace Microsoft.AspNetCore.Http; using Asp.Versioning; diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/LowestImplementedApiVersionSelectorTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/LowestImplementedApiVersionSelectorTest.cs index 60f5de6d..62677126 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/LowestImplementedApiVersionSelectorTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/LowestImplementedApiVersionSelectorTest.cs @@ -8,10 +8,13 @@ public class LowestImplementedApiVersionSelectorTest { [Theory] [ClassData( typeof( MinSelectVersionData ) )] - public void select_version_should_return_min_api_version( IEnumerable supportedVersions, IEnumerable deprecatedVersions, ApiVersion expectedVersion ) + public void select_version_should_return_min_api_version( + IEnumerable supportedVersions, + IEnumerable deprecatedVersions, + ApiVersion expectedVersion ) { // arrange - var options = new ApiVersioningOptions() { DefaultApiVersion = new ApiVersion( 42, 0 ) }; + var options = new ApiVersioningOptions() { DefaultApiVersion = new( 42, 0 ) }; var selector = new LowestImplementedApiVersionSelector( options ); var request = Mock.Of(); var versionInfo = new ApiVersionModel( supportedVersions, deprecatedVersions ); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/MediaTypeApiVersionBuilderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/MediaTypeApiVersionBuilderTest.cs index 7050bf6a..e5372d37 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/MediaTypeApiVersionBuilderTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/MediaTypeApiVersionBuilderTest.cs @@ -81,7 +81,7 @@ public void read_should_retrieve_version_from_accept_with_quality( string[] medi var reader = new MediaTypeApiVersionReaderBuilder() .Parameter( "v" ) .Parameter( "api.ver" ) - .Select( ( request, versions ) => versions.Count == 0 ? versions : new[] { versions[^1] } ) + .Select( ( request, versions ) => versions.Count == 0 ? versions : [versions[^1]] ) .Build(); var request = new Mock(); var headers = new HeaderDictionary() diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/QueryStringApiVersionReaderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/QueryStringApiVersionReaderTest.cs index 9f5187ea..b3d03902 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/QueryStringApiVersionReaderTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/QueryStringApiVersionReaderTest.cs @@ -64,9 +64,10 @@ public void read_should_return_empty_list_when_query_parameter_is_empty() [Theory] [MemberData( nameof( AmbiguousQueryCollection ) )] - public void read_should_return_ambiguous_api_versions( IQueryCollection query ) + public void read_should_return_ambiguous_api_versions( string[] names, string[] values ) { // arrange + var query = ToQueryStringParameter( names, values ); var request = new Mock(); var reader = new QueryStringApiVersionReader( "api-version", "version" ); @@ -81,9 +82,10 @@ public void read_should_return_ambiguous_api_versions( IQueryCollection query ) [Theory] [MemberData( nameof( DuplicateQueryCollection ) )] - public void read_should_not_throw_exception_when_duplicate_api_versions_are_requested( IQueryCollection query ) + public void read_should_not_throw_exception_when_duplicate_api_versions_are_requested( string[] names, string[] values ) { // arrange + var query = ToQueryStringParameter( names, values ); var request = new Mock(); var reader = new QueryStringApiVersionReader( "api-version", "version" ); @@ -96,41 +98,39 @@ public void read_should_not_throw_exception_when_duplicate_api_versions_are_requ versions.Single().Should().Be( "1.0" ); } - public static IEnumerable AmbiguousQueryCollection + private static IQueryCollection ToQueryStringParameter( string[] names, string[] values ) { - get + var query = new Mock(); + + if ( names.Length == values.Length ) { - var query = new Mock(); - query.SetupGet( q => q["api-version"] ).Returns( new StringValues( new[] { "1.0", "2.0" } ) ); - yield return new object[] { query.Object }; - - query = new Mock(); - query.SetupGet( q => q["version"] ).Returns( new StringValues( new[] { "1.0", "2.0" } ) ); - yield return new object[] { query.Object }; - - query = new Mock(); - query.SetupGet( q => q["api-version"] ).Returns( new StringValues( "1.0" ) ); - query.SetupGet( q => q["version"] ).Returns( new StringValues( "2.0" ) ); - yield return new object[] { query.Object }; + for ( var i = 0; i < names.Length; i++ ) + { + query.SetupGet( q => q[names[i]] ).Returns( new StringValues( values[i] ) ); + } } - } - - public static IEnumerable DuplicateQueryCollection - { - get + else { - var query = new Mock(); - query.SetupGet( q => q["api-version"] ).Returns( new StringValues( new[] { "1.0", "1.0" } ) ); - yield return new object[] { query.Object }; - - query = new Mock(); - query.SetupGet( q => q["version"] ).Returns( new StringValues( new[] { "1.0", "1.0" } ) ); - yield return new object[] { query.Object }; - - query = new Mock(); - query.SetupGet( q => q["api-version"] ).Returns( new StringValues( "1.0" ) ); - query.SetupGet( q => q["version"] ).Returns( new StringValues( "1.0" ) ); - yield return new object[] { query.Object }; + foreach ( var name in names ) + { + query.SetupGet( q => q[name] ).Returns( new StringValues( values ) ); + } } + + return query.Object; } + + public static TheoryData AmbiguousQueryCollection => new() + { + { ["api-version"], ["1.0", "2.0"] }, + { ["version"], ["1.0", "2.0"] }, + { ["api-version", "version"], ["1.0", "2.0"] }, + }; + + public static TheoryData DuplicateQueryCollection => new() + { + { ["api-version"], ["1.0", "1.0"] }, + { ["version"], ["1.0", "1.0"] }, + { ["api-version", "version"], ["1.0", "1.0"] }, + }; } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Routing/ApiVersionMatcherPolicyTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Routing/ApiVersionMatcherPolicyTest.cs index bc8d7e9c..3df00a29 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Routing/ApiVersionMatcherPolicyTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Routing/ApiVersionMatcherPolicyTest.cs @@ -52,20 +52,20 @@ public void apply_should_use_400_endpoint_for_ambiguous_api_version() // arrange var feature = new Mock(); - feature.SetupProperty( f => f.RawRequestedApiVersions, new[] { "1.0", "2.0" } ); + feature.SetupProperty( f => f.RawRequestedApiVersions, ["1.0", "2.0"] ); var options = new ApiVersioningOptions() { ApiVersionReader = new QueryStringApiVersionReader(), }; var policy = NewApiVersionMatcherPolicy( options ); - var httpContext = NewHttpContext( feature, queryParameters: new() { ["api-version"] = new( new[] { "1.0", "2.0" } ) } ); + var httpContext = NewHttpContext( feature, queryParameters: new() { ["api-version"] = new( ["1.0", "2.0"] ) } ); var model = new ApiVersionModel( - declaredVersions: new ApiVersion[] { new( 1, 0 ), new( 2, 0 ) }, - supportedVersions: new ApiVersion[] { new( 1, 0 ), new( 2, 0 ) }, - deprecatedVersions: Enumerable.Empty(), - advertisedVersions: Enumerable.Empty(), - deprecatedAdvertisedVersions: Enumerable.Empty() ); + declaredVersions: [new( 1, 0 ), new( 2, 0 )], + supportedVersions: [new( 1, 0 ), new( 2, 0 )], + deprecatedVersions: [], + advertisedVersions: [], + deprecatedAdvertisedVersions: [] ); var routePattern = RoutePatternFactory.Parse( "api/values" ); var builder = new RouteEndpointBuilder( Limbo, routePattern, 0 ) { @@ -97,7 +97,7 @@ public async Task apply_should_have_candidate_for_matched_api_version() var model = new ApiVersionModel( new ApiVersion( 1, 0 ) ); var items = new object[] { new ApiVersionMetadata( model, model ) }; var endpoint = new Endpoint( Limbo, new( items ), default ); - var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, [0] ); + var candidates = new CandidateSet( [endpoint], [[]], [0] ); var policy = NewApiVersionMatcherPolicy(); feature.SetupProperty( f => f.RequestedApiVersion, new ApiVersion( 1, 0 ) ); @@ -118,14 +118,14 @@ public async Task apply_should_use_400_endpoint_for_unmatched_api_version() var feature = new Mock(); feature.SetupProperty( f => f.RawRequestedApiVersion, "2.0" ); - feature.SetupProperty( f => f.RawRequestedApiVersions, new[] { "2.0" } ); + feature.SetupProperty( f => f.RawRequestedApiVersions, ["2.0"] ); feature.SetupProperty( f => f.RequestedApiVersion, new ApiVersion( 2, 0 ) ); var policy = NewApiVersionMatcherPolicy(); var model = new ApiVersionModel( new ApiVersion( 1, 0 ) ); var items = new object[] { new ApiVersionMetadata( model, model ) }; var endpoint = new Endpoint( Limbo, new( items ), default ); - var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, [0] ); + var candidates = new CandidateSet( [endpoint], [[]], [0] ); var httpContext = NewHttpContext( feature ); // act @@ -141,20 +141,20 @@ public void apply_should_use_400_endpoint_for_invalid_api_version() // arrange var feature = new Mock(); - feature.SetupProperty( f => f.RawRequestedApiVersions, new[] { "blah" } ); + feature.SetupProperty( f => f.RawRequestedApiVersions, ["blah"] ); var options = new ApiVersioningOptions() { ApiVersionReader = new QueryStringApiVersionReader(), }; var policy = NewApiVersionMatcherPolicy( options ); - var httpContext = NewHttpContext( feature, queryParameters: new() { ["api-version"] = new( new[] { "blah" } ) } ); + var httpContext = NewHttpContext( feature, queryParameters: new() { ["api-version"] = new( ["blah"] ) } ); var model = new ApiVersionModel( - declaredVersions: new ApiVersion[] { new( 1, 0 ) }, - supportedVersions: new ApiVersion[] { new( 1, 0 ) }, - deprecatedVersions: Enumerable.Empty(), - advertisedVersions: Enumerable.Empty(), - deprecatedAdvertisedVersions: Enumerable.Empty() ); + declaredVersions: [new( 1, 0 )], + supportedVersions: [new( 1, 0 )], + deprecatedVersions: [], + advertisedVersions: [], + deprecatedAdvertisedVersions: [] ); var routePattern = RoutePatternFactory.Parse( "api/values" ); var builder = new RouteEndpointBuilder( Limbo, routePattern, 0 ) { @@ -185,14 +185,14 @@ public async Task apply_should_use_400_endpoint_for_unspecified_api_version() var feature = new Mock(); feature.SetupProperty( f => f.RawRequestedApiVersion, default ); - feature.SetupProperty( f => f.RawRequestedApiVersions, Array.Empty() ); + feature.SetupProperty( f => f.RawRequestedApiVersions, [] ); feature.SetupProperty( f => f.RequestedApiVersion, default ); var policy = NewApiVersionMatcherPolicy(); var model = new ApiVersionModel( new ApiVersion( 1, 0 ) ); var items = new object[] { new ApiVersionMetadata( model, model ) }; var endpoint = new Endpoint( Limbo, new( items ), "Test" ); - var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, [0] ); + var candidates = new CandidateSet( [endpoint], [[]], [0] ); var httpContext = NewHttpContext( feature ); // act @@ -210,7 +210,7 @@ public async Task apply_should_have_candidate_for_unspecified_api_version() var model = new ApiVersionModel( new ApiVersion( 1, 0 ) ); var items = new object[] { new ApiVersionMetadata( model, model ) }; var endpoint = new Endpoint( Limbo, new( items ), default ); - var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, [0] ); + var candidates = new CandidateSet( [endpoint], [[]], [0] ); var options = new ApiVersioningOptions() { AssumeDefaultVersionWhenUnspecified = true }; var policy = NewApiVersionMatcherPolicy( options ); @@ -232,7 +232,7 @@ public async Task apply_should_have_candidate_for_unspecified_api_version() private static ApiVersionMatcherPolicy NewApiVersionMatcherPolicy( ApiVersioningOptions options = default ) => new( ApiVersionParser.Default, - Enumerable.Empty(), + [], Options.Create( options ?? new() ), Mock.Of>() ); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Routing/ApiVersioningRouteOptionsSetupTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Routing/ApiVersioningRouteOptionsSetupTest.cs index cdbadd33..d883e093 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Routing/ApiVersioningRouteOptionsSetupTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/Routing/ApiVersioningRouteOptionsSetupTest.cs @@ -19,7 +19,7 @@ public void post_configure_should_add_route_constraint_with_default_name() setup.PostConfigure( default, routeOptions ); // assert - routeOptions.ConstraintMap["apiVersion"].Should().Be( typeof( ApiVersionRouteConstraint ) ); + routeOptions.ConstraintMap["apiVersion"].Should().Be(); } [Fact] @@ -35,6 +35,6 @@ public void post_configure_should_add_route_constraint_with_custom_name() setup.PostConfigure( default, routeOptions ); // assert - routeOptions.ConstraintMap[key].Should().Be( typeof( ApiVersionRouteConstraint ) ); + routeOptions.ConstraintMap[key].Should().Be(); } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/ApiDescriptionExtensionsTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/ApiDescriptionExtensionsTest.cs index 5f209cf7..52fc4f0c 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/ApiDescriptionExtensionsTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/ApiDescriptionExtensionsTest.cs @@ -19,7 +19,7 @@ public void get_api_version_should_return_associated_value() description.Properties[typeof( ApiVersion )] = version; // act - var value = description.GetApiVersion(); + var value = description.ApiVersion; // assert value.Should().Be( version ); @@ -32,7 +32,7 @@ public void set_api_version_should_associate_value() var version = new ApiVersion( 42, 0 ); var description = new ApiDescription(); - description.SetApiVersion( version ); + description.ApiVersion = version; // act var value = (ApiVersion) description.Properties[typeof( ApiVersion )]; @@ -51,19 +51,19 @@ public void is_deprecated_should_match_model( int majorVersion, int minorVersion var metadata = new ApiVersionMetadata( ApiVersionModel.Empty, new ApiVersionModel( - declaredVersions: new ApiVersion[] { new( 0, 9 ), new( 1, 0 ) }, - supportedVersions: new[] { new ApiVersion( 1, 0 ) }, - deprecatedVersions: new[] { new ApiVersion( 0, 9 ) }, - advertisedVersions: Array.Empty(), - deprecatedAdvertisedVersions: Array.Empty() ) ); + declaredVersions: [new( 0, 9 ), new( 1, 0 )], + supportedVersions: [new ApiVersion( 1, 0 )], + deprecatedVersions: [new ApiVersion( 0, 9 )], + advertisedVersions: [], + deprecatedAdvertisedVersions: [] ) ); var description = new ApiDescription() { - ActionDescriptor = new() { EndpointMetadata = new[] { metadata } }, + ActionDescriptor = new() { EndpointMetadata = [metadata] }, Properties = { [typeof( ApiVersion )] = apiVersion }, }; // act - var deprecated = description.IsDeprecated(); + var deprecated = description.IsDeprecated; // assert deprecated.Should().Be( expected ); @@ -76,11 +76,11 @@ public void is_deprecated_should_return_false_for_versionX2Dneutral_action() var metadata = ApiVersionMetadata.Neutral; var description = new ApiDescription() { - ActionDescriptor = new() { EndpointMetadata = new[] { metadata } }, + ActionDescriptor = new() { EndpointMetadata = [metadata] }, }; // act - var deprecated = description.IsDeprecated(); + var deprecated = description.IsDeprecated; // assert deprecated.Should().BeFalse(); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/DefaultApiVersionDescriptionProviderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/DefaultApiVersionDescriptionProviderTest.cs deleted file mode 100644 index da5cf86e..00000000 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/DefaultApiVersionDescriptionProviderTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. - -namespace Asp.Versioning.ApiExplorer; - -using Microsoft.Extensions.Options; - -public class DefaultApiVersionDescriptionProviderTest -{ - [Fact] - public void api_version_descriptions_should_collate_expected_versions() - { - // arrange - var descriptionProvider = new DefaultApiVersionDescriptionProvider( - new IApiVersionMetadataCollationProvider[] - { - new EndpointApiVersionMetadataCollationProvider( new TestEndpointDataSource() ), - new ActionApiVersionMetadataCollationProvider( new TestActionDescriptorCollectionProvider() ), - }, - Mock.Of>(), - Mock.Of>(), - Options.Create( new ApiExplorerOptions() { GroupNameFormat = "'v'VVV" } ) ); - - // act - var descriptions = descriptionProvider.ApiVersionDescriptions; - - // assert - descriptions.Should().BeEquivalentTo( - new ApiVersionDescription[] - { - new( new ApiVersion( 0, 9 ), "v0.9", true ), - new( new ApiVersion( 1, 0 ), "v1", false ), - new( new ApiVersion( 2, 0 ), "v2", false ), - new( new ApiVersion( 3, 0 ), "v3", false ), - } ); - } - - [Fact] - public void api_version_descriptions_should_apply_sunset_policy() - { - // arrange - var expected = new SunsetPolicy(); - var apiVersion = new ApiVersion( 0.9 ); - var policyManager = new Mock>(); - - policyManager.Setup( pm => pm.TryGetPolicy( default, apiVersion, out expected ) ).Returns( true ); - - var descriptionProvider = new DefaultApiVersionDescriptionProvider( - new IApiVersionMetadataCollationProvider[] - { - new EndpointApiVersionMetadataCollationProvider( new TestEndpointDataSource() ), - new ActionApiVersionMetadataCollationProvider( new TestActionDescriptorCollectionProvider() ), - }, - policyManager.Object, - Mock.Of>(), - Options.Create( new ApiExplorerOptions() { GroupNameFormat = "'v'VVV" } ) ); - - // act - var description = descriptionProvider.ApiVersionDescriptions.Single( api => api.GroupName == "v0.9" ); - - // assert - description.SunsetPolicy.Should().BeSameAs( expected ); - } -} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/GroupedApiVersionDescriptionProviderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/GroupedApiVersionDescriptionProviderTest.cs index 946df754..70b89227 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/GroupedApiVersionDescriptionProviderTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/GroupedApiVersionDescriptionProviderTest.cs @@ -14,11 +14,10 @@ public void api_version_descriptions_should_collate_expected_versions() { // arrange var descriptionProvider = new GroupedApiVersionDescriptionProvider( - new IApiVersionMetadataCollationProvider[] - { - new EndpointApiVersionMetadataCollationProvider( new TestEndpointDataSource() ), + [ + new EndpointApiVersionMetadataCollationProvider( new TestEndpointDataSource(), new DefaultEndpointInspector() ), new ActionApiVersionMetadataCollationProvider( new TestActionDescriptorCollectionProvider() ), - }, + ], Mock.Of>(), Mock.Of>(), Options.Create( new ApiExplorerOptions() { GroupNameFormat = "'v'VVV" } ) ); @@ -42,7 +41,7 @@ public void api_version_descriptions_should_collate_expected_versions_with_custo { // arrange var provider = new TestActionDescriptorCollectionProvider(); - using var source = new CompositeEndpointDataSource( Enumerable.Empty() ); + using var source = new CompositeEndpointDataSource( [] ); var data = new ApiDescriptionActionData() { GroupName = "Test" }; foreach ( var descriptor in provider.ActionDescriptors.Items ) @@ -51,11 +50,10 @@ public void api_version_descriptions_should_collate_expected_versions_with_custo } var descriptionProvider = new GroupedApiVersionDescriptionProvider( - new IApiVersionMetadataCollationProvider[] - { - new EndpointApiVersionMetadataCollationProvider( source ), + [ + new EndpointApiVersionMetadataCollationProvider( source, new DefaultEndpointInspector() ), new ActionApiVersionMetadataCollationProvider( provider ), - }, + ], Mock.Of>(), Mock.Of>(), Options.Create( @@ -90,11 +88,10 @@ public void api_version_descriptions_should_apply_sunset_policy() policyManager.Setup( pm => pm.TryGetPolicy( default, apiVersion, out expected ) ).Returns( true ); var descriptionProvider = new GroupedApiVersionDescriptionProvider( - new IApiVersionMetadataCollationProvider[] - { - new EndpointApiVersionMetadataCollationProvider( new TestEndpointDataSource() ), + [ + new EndpointApiVersionMetadataCollationProvider( new TestEndpointDataSource(), new DefaultEndpointInspector() ), new ActionApiVersionMetadataCollationProvider( new TestActionDescriptorCollectionProvider() ), - }, + ], policyManager.Object, Mock.Of>(), Options.Create( new ApiExplorerOptions() { GroupNameFormat = "'v'VVV" } ) ); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/IApiDescriptionProviderExtensions.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/IApiDescriptionProviderExtensions.cs index 98ac33fb..aeff3a27 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/IApiDescriptionProviderExtensions.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/IApiDescriptionProviderExtensions.cs @@ -7,13 +7,16 @@ namespace Asp.Versioning.ApiExplorer; internal static class IApiDescriptionProviderExtensions { - internal static IReadOnlyList Execute( this IApiDescriptionProvider apiDescriptionProvider, ActionDescriptor actionDescriptor ) + extension( IApiDescriptionProvider apiDescriptionProvider ) { - var context = new ApiDescriptionProviderContext( new[] { actionDescriptor } ); + internal IReadOnlyList Execute( ActionDescriptor actionDescriptor ) + { + var context = new ApiDescriptionProviderContext( [actionDescriptor] ); - apiDescriptionProvider.OnProvidersExecuting( context ); - apiDescriptionProvider.OnProvidersExecuted( context ); + apiDescriptionProvider.OnProvidersExecuting( context ); + apiDescriptionProvider.OnProvidersExecuted( context ); - return context.Results.ToArray(); + return [.. context.Results]; + } } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/TestActionDescriptorCollectionProvider.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/TestActionDescriptorCollectionProvider.cs index 4ed2d30e..7be86bdf 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/TestActionDescriptorCollectionProvider.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/TestActionDescriptorCollectionProvider.cs @@ -38,7 +38,7 @@ private static ActionDescriptorCollection CreateActionDescriptors() AddOrderActionDescriptors( actions ); AddPeopleActionDescriptors( actions ); - return new( actions.ToArray(), 0 ); + return new( [.. actions], 0 ); } private static void AddOrderActionDescriptors( List actions ) @@ -47,63 +47,63 @@ private static void AddOrderActionDescriptors( List actions ) actions.Add( NewActionDescriptor( "GET-orders/{id}", - declared: new ApiVersion[] { new( 0, 9 ), new( 1, 0 ) }, - supported: new ApiVersion[] { new( 1, 0 ) }, - deprecated: new ApiVersion[] { new( 0, 9 ) } ) ); + declared: [new( 0, 9 ), new( 1, 0 )], + supported: [new( 1, 0 )], + deprecated: [new( 0, 9 )] ) ); actions.Add( NewActionDescriptor( "POST-orders", - declared: new ApiVersion[] { new( 1, 0 ) }, - supported: new ApiVersion[] { new( 1, 0 ) } ) ); + declared: [new( 1, 0 )], + supported: [new( 1, 0 )] ) ); // api version 2.0 actions.Add( NewActionDescriptor( "GET-orders", - declared: new ApiVersion[] { new( 2, 0 ) }, - supported: new ApiVersion[] { new( 2, 0 ) } ) ); + declared: [new( 2, 0 )], + supported: [new( 2, 0 )] ) ); actions.Add( NewActionDescriptor( "GET-orders/{id}", - declared: new ApiVersion[] { new( 2, 0 ) }, - supported: new ApiVersion[] { new( 2, 0 ) } ) ); + declared: [new( 2, 0 )], + supported: [new( 2, 0 )] ) ); actions.Add( NewActionDescriptor( "POST-orders", - declared: new ApiVersion[] { new( 2, 0 ) }, - supported: new ApiVersion[] { new( 2, 0 ) } ) ); + declared: [new( 2, 0 )], + supported: [new( 2, 0 )] ) ); // api version 3.0 actions.Add( NewActionDescriptor( "GET-orders", - declared: new ApiVersion[] { new( 3, 0 ) }, - supported: new ApiVersion[] { new( 3, 0 ) }, - advertised: new ApiVersion[] { new( 4, 0 ) } ) ); + declared: [new( 3, 0 )], + supported: [new( 3, 0 )], + advertised: [new( 4, 0 )] ) ); actions.Add( NewActionDescriptor( "GET-orders/{id}", - declared: new ApiVersion[] { new( 3, 0 ) }, - supported: new ApiVersion[] { new( 3, 0 ) }, - advertised: new ApiVersion[] { new( 4, 0 ) } ) ); + declared: [new( 3, 0 )], + supported: [new( 3, 0 )], + advertised: [new( 4, 0 )] ) ); actions.Add( NewActionDescriptor( "POST-orders", - declared: new ApiVersion[] { new( 3, 0 ) }, - supported: new ApiVersion[] { new( 3, 0 ) }, - advertised: new ApiVersion[] { new( 4, 0 ) } ) ); + declared: [new( 3, 0 )], + supported: [new( 3, 0 )], + advertised: [new( 4, 0 )] ) ); actions.Add( NewActionDescriptor( "DELETE-orders/{id}", - declared: new ApiVersion[] { new( 3, 0 ) }, - supported: new ApiVersion[] { new( 3, 0 ) }, - advertised: new ApiVersion[] { new( 4, 0 ) } ) ); + declared: [new( 3, 0 )], + supported: [new( 3, 0 )], + advertised: [new( 4, 0 )] ) ); } private static void AddPeopleActionDescriptors( List actions ) @@ -112,56 +112,56 @@ private static void AddPeopleActionDescriptors( List actions ) actions.Add( NewActionDescriptor( "GET-people/{id}", - declared: new ApiVersion[] { new( 0, 9 ), new( 1, 0 ) }, - supported: new ApiVersion[] { new( 1, 0 ) }, - deprecated: new ApiVersion[] { new( 0, 9 ) } ) ); + declared: [new( 0, 9 ), new( 1, 0 )], + supported: [new( 1, 0 )], + deprecated: [new( 0, 9 )] ) ); actions.Add( NewActionDescriptor( "POST-people", - declared: new ApiVersion[] { new( 1, 0 ) }, - supported: new ApiVersion[] { new( 1, 0 ) } ) ); + declared: [new( 1, 0 )], + supported: [new( 1, 0 )] ) ); // api version 2.0 actions.Add( NewActionDescriptor( "GET-people", - declared: new ApiVersion[] { new( 2, 0 ) }, - supported: new ApiVersion[] { new( 2, 0 ) } ) ); + declared: [new( 2, 0 )], + supported: [new( 2, 0 )] ) ); actions.Add( NewActionDescriptor( "GET-people/{id}", - declared: new ApiVersion[] { new( 2, 0 ) }, - supported: new ApiVersion[] { new( 2, 0 ) } ) ); + declared: [new( 2, 0 )], + supported: [new( 2, 0 )] ) ); actions.Add( NewActionDescriptor( "POST-people", - declared: new ApiVersion[] { new( 2, 0 ) }, - supported: new ApiVersion[] { new( 2, 0 ) } ) ); + declared: [new( 2, 0 )], + supported: [new( 2, 0 )] ) ); // api version 3.0 actions.Add( NewActionDescriptor( "GET-people", - declared: new ApiVersion[] { new( 3, 0 ) }, - supported: new ApiVersion[] { new( 3, 0 ) }, - advertised: new ApiVersion[] { new( 4, 0 ) } ) ); + declared: [new( 3, 0 )], + supported: [new( 3, 0 )], + advertised: [new( 4, 0 )] ) ); actions.Add( NewActionDescriptor( "GET-people/{id}", - declared: new ApiVersion[] { new( 3, 0 ) }, - supported: new ApiVersion[] { new( 3, 0 ) }, - advertised: new ApiVersion[] { new( 4, 0 ) } ) ); + declared: [new( 3, 0 )], + supported: [new( 3, 0 )], + advertised: [new( 4, 0 )] ) ); actions.Add( NewActionDescriptor( "POST-people", - declared: new ApiVersion[] { new( 3, 0 ) }, - supported: new ApiVersion[] { new( 3, 0 ) }, - advertised: new ApiVersion[] { new( 4, 0 ) } ) ); + declared: [new( 3, 0 )], + supported: [new( 3, 0 )], + advertised: [new( 4, 0 )] ) ); } private static ActionDescriptor NewActionDescriptor( @@ -177,14 +177,14 @@ private static ActionDescriptor NewActionDescriptor( new ApiVersionModel( declared, supported, - deprecated ?? Enumerable.Empty(), - advertised ?? Enumerable.Empty(), - advertisedDeprecated ?? Enumerable.Empty() ) ); + deprecated ?? [], + advertised ?? [], + advertisedDeprecated ?? [] ) ); return new() { DisplayName = displayName, - EndpointMetadata = new[] { metadata }, + EndpointMetadata = [metadata], }; } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/VersionedApiDescriptionProviderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/VersionedApiDescriptionProviderTest.cs index cbe48584..94e0cdf9 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/VersionedApiDescriptionProviderTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.ApiExplorer.Tests/VersionedApiDescriptionProviderTest.cs @@ -32,8 +32,8 @@ public void versioned_api_explorer_should_group_and_order_descriptions_on_provid // assert context.Results.Should().BeEquivalentTo( - new[] - { + [ + // orders new { GroupName = "v0.9", Properties = new Dictionary() { [typeof( ApiVersion )] = new ApiVersion( 0, 9 ) } }, new { GroupName = "v1", Properties = new Dictionary() { [typeof( ApiVersion )] = new ApiVersion( 1, 0 ) } }, @@ -56,7 +56,7 @@ public void versioned_api_explorer_should_group_and_order_descriptions_on_provid new { GroupName = "v3", Properties = new Dictionary() { [typeof( ApiVersion )] = new ApiVersion( 3, 0 ) } }, new { GroupName = "v3", Properties = new Dictionary() { [typeof( ApiVersion )] = new ApiVersion( 3, 0 ) } }, new { GroupName = "v3", Properties = new Dictionary() { [typeof( ApiVersion )] = new ApiVersion( 3, 0 ) } }, - }, + ], options => options.ExcludingMissingMembers() ); } @@ -89,7 +89,7 @@ public void versioned_api_explorer_should_apply_sunset_policy() // assert context.Results .Where( api => api.GroupName == "v0.9" ) - .Select( api => api.GetSunsetPolicy() ) + .Select( api => api.SunsetPolicy ) .All( policy => policy == expected ) .Should() .BeTrue(); @@ -100,7 +100,7 @@ public void versioned_api_explorer_should_preserve_group_name() { // arrange var metadata = new ApiVersionMetadata( ApiVersionModel.Empty, new ApiVersionModel( ApiVersion.Default ) ); - var descriptor = new ActionDescriptor() { EndpointMetadata = new[] { metadata } }; + var descriptor = new ActionDescriptor() { EndpointMetadata = [metadata] }; var actionProvider = new TestActionDescriptorCollectionProvider( descriptor ); var context = new ApiDescriptionProviderContext( actionProvider.ActionDescriptors.Items ); var apiExplorer = new VersionedApiDescriptionProvider( @@ -127,7 +127,7 @@ public void versioned_api_explorer_should_use_custom_group_name() { // arrange var metadata = new ApiVersionMetadata( ApiVersionModel.Empty, new ApiVersionModel( ApiVersion.Default ) ); - var descriptor = new ActionDescriptor() { EndpointMetadata = new[] { metadata } }; + var descriptor = new ActionDescriptor() { EndpointMetadata = [metadata] }; var actionProvider = new TestActionDescriptorCollectionProvider( descriptor ); var context = new ApiDescriptionProviderContext( actionProvider.ActionDescriptors.Items ); var options = new ApiExplorerOptions() @@ -160,42 +160,42 @@ public void versioned_api_explorer_should_prefer_explicit_over_implicit_action_m var @implicit = new ActionDescriptor() { DisplayName = "Implicit GET ~/test?api-version=[1.0,2.0]", - EndpointMetadata = new[] - { + EndpointMetadata = + [ new ApiVersionMetadata( new ApiVersionModel( - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - Array.Empty(), - Array.Empty(), - Array.Empty() ), + [new( 1.0 ), new( 2.0 )], + [new( 1.0 ), new( 2.0 )], + [], + [], + [] ), new ApiVersionModel( - Array.Empty(), - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - Array.Empty(), - Array.Empty(), - Array.Empty() ) ), - }, + [], + [new( 1.0 ), new( 2.0 )], + [], + [], + [] ) ), + ], }; var @explicit = new ActionDescriptor() { DisplayName = "Explicit GET ~/test?api-version=2.0", - EndpointMetadata = new[] - { + EndpointMetadata = + [ new ApiVersionMetadata( new ApiVersionModel( - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - Array.Empty(), - Array.Empty(), - Array.Empty() ), + [new( 1.0 ), new( 2.0 )], + [new( 1.0 ), new( 2.0 )], + [], + [], + [] ), new ApiVersionModel( - new ApiVersion[] { new( 2.0 ) }, - new ApiVersion[] { new( 1.0 ), new( 2.0 ) }, - Array.Empty(), - Array.Empty(), - Array.Empty() ) ), - }, + [new( 2.0 )], + [new( 1.0 ), new( 2.0 )], + [], + [], + [] ) ), + ], }; var actionProvider = new TestActionDescriptorCollectionProvider( @implicit, @explicit ); var context = new ApiDescriptionProviderContext( actionProvider.ActionDescriptors.Items ); @@ -227,8 +227,7 @@ public void versioned_api_explorer_should_prefer_explicit_over_implicit_action_m // assert context.Results.Should().BeEquivalentTo( - new[] - { + [ new { GroupName = "v1", @@ -241,7 +240,7 @@ public void versioned_api_explorer_should_prefer_explicit_over_implicit_action_m ActionDescriptor = @explicit, Properties = new Dictionary() { [typeof( ApiVersion )] = new ApiVersion( 2.0 ) }, }, - }, + ], options => options.ExcludingMissingMembers() ); } diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersionCollatorTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersionCollatorTest.cs index 07ce8bb3..9a70e1a1 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersionCollatorTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersionCollatorTest.cs @@ -11,9 +11,10 @@ public class ApiVersionCollatorTest { [Theory] [MemberData( nameof( ActionDescriptorProviderContexts ) )] - public void on_providers_executed_should_aggregate_api_version_models_by_controller( ActionDescriptorProviderContext context ) + public void on_providers_executed_should_aggregate_api_version_models_by_controller( ContextKind kind ) { // arrange + var context = NewContext( kind ); var collator = new ApiVersionCollator( ControllerNameConvention.Default ); var expected = new ApiVersion[] { new( 1, 0 ), new( 2, 0 ), new( 3, 0 ) }; @@ -23,18 +24,32 @@ public void on_providers_executed_should_aggregate_api_version_models_by_control // assert var actions = context.Results.Where( a => a.GetProperty() != null ); - actions.All( a => a.GetApiVersionMetadata().Map( Explicit ).SupportedApiVersions.SequenceEqual( expected ) ).Should().BeTrue(); + actions.All( a => a.ApiVersionMetadata.Map( Explicit ).SupportedApiVersions.SequenceEqual( expected ) ).Should().BeTrue(); } - public static IEnumerable ActionDescriptorProviderContexts + public enum ContextKind { - get - { - yield return new object[] { ActionsWithRouteValues }; - yield return new object[] { ActionsByControllerName }; - } + /// + /// Gets an action context with route values. + /// + WithRouteValues, + + /// + /// Gets an action context by controller name. + /// + ByControllerName, } + private static ActionDescriptorProviderContext NewContext( ContextKind kind ) => kind switch + { + ContextKind.WithRouteValues => ActionsWithRouteValues, + ContextKind.ByControllerName => ActionsByControllerName, + _ => throw new ArgumentOutOfRangeException( nameof( kind ) ), + }; + + public static TheoryData ActionDescriptorProviderContexts => + new( ContextKind.WithRouteValues, ContextKind.ByControllerName ); + private static ApiVersionMetadata NewApiVersionMetadata( double version ) { var model = new ApiVersionModel( new ApiVersion( version ) ); @@ -53,7 +68,7 @@ private static ApiVersionMetadata NewApiVersionMetadata( double version ) ["controller"] = "Values", ["action"] = "Get", }, - EndpointMetadata = new[] { NewApiVersionMetadata( 1.0 ) }, + EndpointMetadata = [NewApiVersionMetadata( 1.0 )], }, new() { @@ -69,7 +84,7 @@ private static ApiVersionMetadata NewApiVersionMetadata( double version ) ["controller"] = "Values", ["action"] = "Get", }, - EndpointMetadata = new[] { NewApiVersionMetadata( 2.0 ) }, + EndpointMetadata = [NewApiVersionMetadata( 2.0 )], }, new() { @@ -78,7 +93,7 @@ private static ApiVersionMetadata NewApiVersionMetadata( double version ) ["controller"] = "Values", ["action"] = "Get", }, - EndpointMetadata = new[] { NewApiVersionMetadata( 3.0 ) }, + EndpointMetadata = [NewApiVersionMetadata( 3.0 )], }, }, }; @@ -95,7 +110,7 @@ private static ApiVersionMetadata NewApiVersionMetadata( double version ) { ["action"] = "Get", }, - EndpointMetadata = new[] { NewApiVersionMetadata( 1.0 ) }, + EndpointMetadata = [NewApiVersionMetadata( 1.0 )], }, new ActionDescriptor() { @@ -111,7 +126,7 @@ private static ApiVersionMetadata NewApiVersionMetadata( double version ) { ["action"] = "Get", }, - EndpointMetadata = new[] { NewApiVersionMetadata( 2.0 ) }, + EndpointMetadata = [NewApiVersionMetadata( 2.0 )], }, new ControllerActionDescriptor() { @@ -120,7 +135,7 @@ private static ApiVersionMetadata NewApiVersionMetadata( double version ) { ["action"] = "Get", }, - EndpointMetadata = new[] { NewApiVersionMetadata( 3.0 ) }, + EndpointMetadata = [NewApiVersionMetadata( 3.0 )], }, }, }; diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersioningApplicationModelProviderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersioningApplicationModelProviderTest.cs index 0d4c2ff9..237da138 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersioningApplicationModelProviderTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersioningApplicationModelProviderTest.cs @@ -32,11 +32,11 @@ public void on_providers_executing_should_apply_api_version_model_conventions() var actionMethod = type.GetRuntimeMethod( nameof( object.ToString ), Type.EmptyTypes ); var controller = new ControllerModel( type.GetTypeInfo(), attributes ) { - Actions = { new ActionModel( actionMethod, Array.Empty() ) }, + Actions = { new ActionModel( actionMethod, [] ) }, }; - var context = new ApplicationModelProviderContext( new[] { controller.ControllerType } ); + var context = new ApplicationModelProviderContext( [controller.ControllerType] ); var provider = new ApiVersioningApplicationModelProvider( - new DefaultApiControllerFilter( Array.Empty() ), + new DefaultApiControllerFilter( [] ), ControllerNameConvention.Default, Options.Create( new ApiVersioningOptions() ), Options.Create( new MvcApiVersioningOptions() ) ); @@ -77,9 +77,9 @@ public void on_providers_executing_should_apply_api_versionX2Dneutral_model_conv var actionMethod = type.GetRuntimeMethod( nameof( object.ToString ), Type.EmptyTypes ); var controller = new ControllerModel( type.GetTypeInfo(), attributes ) { - Actions = { new ActionModel( actionMethod, Array.Empty() ) }, + Actions = { new ActionModel( actionMethod, [] ) }, }; - var context = new ApplicationModelProviderContext( new[] { controller.ControllerType } ); + var context = new ApplicationModelProviderContext( [controller.ControllerType] ); var provider = new ApiVersioningApplicationModelProvider( new NoControllerFilter(), ControllerNameConvention.Default, @@ -114,9 +114,9 @@ public void on_providers_executing_should_apply_implicit_api_version_model_conve var actionMethod = type.GetRuntimeMethod( nameof( object.ToString ), Type.EmptyTypes ); var controller = new ControllerModel( type.GetTypeInfo(), attributes ) { - Actions = { new ActionModel( actionMethod, Array.Empty() ) }, + Actions = { new ActionModel( actionMethod, [] ) }, }; - var context = new ApplicationModelProviderContext( new[] { controller.ControllerType } ); + var context = new ApplicationModelProviderContext( [controller.ControllerType] ); var provider = new ApiVersioningApplicationModelProvider( new NoControllerFilter(), ControllerNameConvention.Default, @@ -159,11 +159,11 @@ public void on_providers_executing_should_not_apply_implicit_api_version_model_c var v1 = new ApiVersion( 1, 0 ); var controller = new ControllerModel( type.GetTypeInfo(), attributes ) { - Actions = { new ActionModel( actionMethod, Array.Empty() ) }, + Actions = { new ActionModel( actionMethod, [] ) }, }; - var context = new ApplicationModelProviderContext( new[] { controller.ControllerType } ); + var context = new ApplicationModelProviderContext( [controller.ControllerType] ); var provider = new ApiVersioningApplicationModelProvider( - new DefaultApiControllerFilter( Array.Empty() ), + new DefaultApiControllerFilter( [] ), ControllerNameConvention.Default, Options.Create( new ApiVersioningOptions() { DefaultApiVersion = v1 } ), Options.Create( new MvcApiVersioningOptions() ) ); @@ -201,17 +201,17 @@ public void on_providers_executing_should_only_apply_api_version_model_conventio var actionMethod = type.GetRuntimeMethod( nameof( object.ToString ), Type.EmptyTypes ); var apiController = new ControllerModel( type.GetTypeInfo(), attributes ) { - Actions = { new ActionModel( actionMethod, Array.Empty() ) }, + Actions = { new ActionModel( actionMethod, [] ) }, }; - var uiController = new ControllerModel( type.GetTypeInfo(), Array.Empty() ) + var uiController = new ControllerModel( type.GetTypeInfo(), [] ) { - Actions = { new ActionModel( actionMethod, Array.Empty() ) }, + Actions = { new ActionModel( actionMethod, [] ) }, }; var controllers = new[] { apiController, uiController }; var controllerTypes = new[] { apiController.ControllerType, uiController.ControllerType }; var context = new ApplicationModelProviderContext( controllerTypes ); var provider = new ApiVersioningApplicationModelProvider( - new DefaultApiControllerFilter( new[] { new ApiBehaviorSpecification() } ), + new DefaultApiControllerFilter( [new ApiBehaviorSpecification()] ), ControllerNameConvention.Default, Options.Create( new ApiVersioningOptions() ), Options.Create( new MvcApiVersioningOptions() ) ); @@ -258,7 +258,7 @@ public void on_providers_executing_should_trim_trailing_numbers_by_convention( s var controllerTypes = new[] { controller.ControllerType }; var context = new ApplicationModelProviderContext( controllerTypes ); var provider = new ApiVersioningApplicationModelProvider( - new DefaultApiControllerFilter( new[] { new ApiBehaviorSpecification() } ), + new DefaultApiControllerFilter( [new ApiBehaviorSpecification()] ), ControllerNameConvention.Default, Options.Create( new ApiVersioningOptions() ), Options.Create( new MvcApiVersioningOptions() ) ); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersioningMvcOptionsSetupTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersioningMvcOptionsSetupTest.cs index b4409418..08522e86 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersioningMvcOptionsSetupTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApiVersioningMvcOptionsSetupTest.cs @@ -36,7 +36,7 @@ public void post_configure_should_register_report_filter() setup.PostConfigure( default, mvcOptions ); // assert - mvcOptions.Filters.OfType().Single().ServiceType.Should().Be( typeof( ReportApiVersionsAttribute ) ); + mvcOptions.Filters.OfType().Single().ServiceType.Should().Be(); } [Fact] @@ -55,6 +55,6 @@ public void post_configure_should_register_model_binder_provider() setup.PostConfigure( default, mvcOptions ); // assert - mvcOptions.ModelBinderProviders.First().GetBinder( context.Object ).Should().BeOfType( typeof( ApiVersionModelBinder ) ); + mvcOptions.ModelBinderProviders.First().GetBinder( context.Object ).Should().BeOfType(); } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApplicationModels/DefaultApiControllerFilterTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApplicationModels/DefaultApiControllerFilterTest.cs index b662cf76..d8c47840 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApplicationModels/DefaultApiControllerFilterTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ApplicationModels/DefaultApiControllerFilterTest.cs @@ -12,7 +12,7 @@ public class DefaultApiControllerFilterTest public void apply_should_not_filter_list_without_specifications() { // arrange - var filter = new DefaultApiControllerFilter( Enumerable.Empty() ); + var filter = new DefaultApiControllerFilter( [] ); var controllerType = typeof( ControllerBase ).GetTypeInfo(); var attributes = Array.Empty(); var controllers = new List() @@ -40,7 +40,7 @@ public void apply_should_filter_controllers() specification.Setup( s => s.IsSatisfiedBy( It.Is( m => m.ControllerType.Equals( controllerBaseType ) ) ) ).Returns( true ); specification.Setup( s => s.IsSatisfiedBy( It.Is( m => m.ControllerType.Equals( controllerType ) ) ) ).Returns( false ); - var filter = new DefaultApiControllerFilter( new[] { specification.Object } ); + var filter = new DefaultApiControllerFilter( [specification.Object] ); var attributes = Array.Empty(); var controllers = new List() { diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ActionApiVersionConventionBuilderTTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ActionApiVersionConventionBuilderTTest.cs index 3e4cd918..8d70c8eb 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ActionApiVersionConventionBuilderTTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ActionApiVersionConventionBuilderTTest.cs @@ -16,9 +16,9 @@ public void apply_to_should_assign_empty_model_without_api_versions_from_mapped_ var controllerBuilder = new ControllerApiVersionConventionBuilder(); var actionBuilder = new ActionApiVersionConventionBuilder( controllerBuilder ); var method = typeof( UndecoratedController ).GetMethod( nameof( UndecoratedController.Get ) ); - var actionModel = new ActionModel( method, Array.Empty() ) + var actionModel = new ActionModel( method, [] ) { - Controller = new( typeof( ControllerBase ).GetTypeInfo(), Array.Empty() ), + Controller = new( typeof( ControllerBase ).GetTypeInfo(), [] ), }; // act @@ -53,7 +53,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_ var attributes = new object[] { new MapToApiVersionAttribute( "2.0" ) }; var actionModel = new ActionModel( method, attributes ) { - Controller = new( typeof( ControllerBase ).GetTypeInfo(), Array.Empty() ), + Controller = new( typeof( ControllerBase ).GetTypeInfo(), [] ), }; actionBuilder.MapToApiVersion( new ApiVersion( 2, 0 ) ); @@ -90,7 +90,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_ var attributes = method.GetCustomAttributes().Cast().ToArray(); var actionModel = new ActionModel( method, attributes ) { - Controller = new( typeof( ControllerBase ).GetTypeInfo(), Array.Empty() ), + Controller = new( typeof( ControllerBase ).GetTypeInfo(), [] ), }; actionBuilder.MapToApiVersion( new ApiVersion( 2, 0 ) ) diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ActionApiVersionConventionBuilderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ActionApiVersionConventionBuilderTest.cs index 053a2b9e..3f2a40aa 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ActionApiVersionConventionBuilderTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ActionApiVersionConventionBuilderTest.cs @@ -16,9 +16,9 @@ public void apply_to_should_assign_empty_model_without_api_versions_from_mapped_ var controllerBuilder = new ControllerApiVersionConventionBuilder( typeof( UndecoratedController ) ); var actionBuilder = new ActionApiVersionConventionBuilder( controllerBuilder ); var method = typeof( UndecoratedController ).GetMethod( nameof( UndecoratedController.Get ) ); - var actionModel = new ActionModel( method, Array.Empty() ) + var actionModel = new ActionModel( method, [] ) { - Controller = new( typeof( ControllerBase ).GetTypeInfo(), Array.Empty() ), + Controller = new( typeof( ControllerBase ).GetTypeInfo(), [] ), }; // act @@ -52,7 +52,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_ var attributes = new object[] { new MapToApiVersionAttribute( "2.0" ) }; var actionModel = new ActionModel( method, attributes ) { - Controller = new( typeof( ControllerBase ).GetTypeInfo(), Array.Empty() ), + Controller = new( typeof( ControllerBase ).GetTypeInfo(), [] ), }; actionBuilder.MapToApiVersion( new ApiVersion( 2, 0 ) ); @@ -88,7 +88,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_ var attributes = method.GetCustomAttributes().Cast().ToArray(); var actionModel = new ActionModel( method, attributes ) { - Controller = new( typeof( ControllerBase ).GetTypeInfo(), Array.Empty() ), + Controller = new( typeof( ControllerBase ).GetTypeInfo(), [] ), }; actionBuilder.MapToApiVersion( new ApiVersion( 2, 0 ) ) diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderTTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderTTest.cs index ae6d8405..4657ee4e 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderTTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderTTest.cs @@ -91,7 +91,7 @@ public void apply_to_should_assign_model_to_controller_from_conventions_and_attr var controllerType = typeof( DecoratedController ).GetTypeInfo(); var action = controllerType.GetRuntimeMethod( nameof( DecoratedController.Get ), Type.EmptyTypes ); var attributes = controllerType.GetCustomAttributes().Cast().ToArray(); - var actionModel = new ActionModel( action, Array.Empty() ); + var actionModel = new ActionModel( action, [] ); var controllerModel = new ControllerModel( controllerType, attributes ) { Actions = { actionModel } }; var controllerBuilder = new ControllerApiVersionConventionBuilder(); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderTest.cs index 37a316b2..184ee2c8 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderTest.cs @@ -91,7 +91,7 @@ public void apply_to_should_assign_model_to_controller_from_conventions_and_attr var controllerType = typeof( DecoratedController ); var action = controllerType.GetRuntimeMethod( nameof( DecoratedController.Get ), Type.EmptyTypes ); var attributes = controllerType.GetTypeInfo().GetCustomAttributes().Cast().ToArray(); - var actionModel = new ActionModel( action, Array.Empty() ); + var actionModel = new ActionModel( action, [] ); var controllerModel = new ControllerModel( controllerType.GetTypeInfo(), attributes ) { Actions = { actionModel } }; var controllerBuilder = new ControllerApiVersionConventionBuilder( controllerType ); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/MvcApiVersioningOptionsFactoryTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/MvcApiVersioningOptionsFactoryTest.cs index b3167b8e..724efe3d 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/MvcApiVersioningOptionsFactoryTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/MvcApiVersioningOptionsFactoryTest.cs @@ -16,8 +16,8 @@ public void create_should_construct_expected_options() var postConfigure = Mock.Of>(); var factory = new MvcApiVersioningOptionsFactory( conventionBuilder, - new[] { configure }, - new[] { postConfigure } ); + [configure], + [postConfigure] ); // act var options = factory.Create( Options.DefaultName ); diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ReportApiVersionsAttributeTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ReportApiVersionsAttributeTest.cs index 627b593a..331a6906 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ReportApiVersionsAttributeTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ReportApiVersionsAttributeTest.cs @@ -82,7 +82,7 @@ private static ActionExecutingContext CreateContext( var filters = Array.Empty(); var actionArguments = new Dictionary(); var controller = default( object ); - var endpoint = new Endpoint( c => Task.CompletedTask, new( new[] { metadata } ), "Test" ); + var endpoint = new Endpoint( c => Task.CompletedTask, new( [metadata] ), "Test" ); var options = Options.Create( new ApiVersioningOptions() ); var reporter = new DefaultApiVersionReporter( new SunsetPolicyManager( options ), new DeprecationPolicyManager( options ) ); @@ -98,7 +98,7 @@ private static ActionExecutingContext CreateContext( httpContext.SetupGet( c => c.Response ).Returns( response.Object ); httpContext.SetupGet( c => c.Features ).Returns( features ); httpContext.SetupProperty( c => c.RequestServices, serviceProvider.Object ); - action.EndpointMetadata = new[] { metadata }; + action.EndpointMetadata = [metadata]; return new( actionContext, filters, actionArguments, controller ); } diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Asp.Versioning.OpenApi.Tests.csproj b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Asp.Versioning.OpenApi.Tests.csproj new file mode 100644 index 00000000..804ff876 --- /dev/null +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Asp.Versioning.OpenApi.Tests.csproj @@ -0,0 +1,42 @@ + + + + $(DefaultTargetFramework) + Asp.Versioning.OpenApi + Test API + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1.json b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1.json new file mode 100644 index 00000000..6cb8098e --- /dev/null +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Content/v1.json @@ -0,0 +1,105 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "Test API | v1", + "description": "The API was deprecated on 1/1/2026. The API was sunset on 2/10/2026.\r\n\r\n### Links\r\n\r\n- [Version Deprecation Policy](http://my.api.com/policies/versions/deprecated.html)\r\n- [Version Sunset Policy](http://my.api.com/policies/versions/sunset.html)", + "version": "1.0" + }, + "servers": [ + { + "url": "http://localhost/" + } + ], + "paths": { + "/Test": { + "get": { + "tags": [ + "Test" + ], + "summary": "", + "description": "", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "", + "schema": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + } + }, + { + "name": "api-version", + "in": "query", + "description": "The requested API version", + "required": true, + "schema": { + "type": "string", + "default": "1.0" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + } + }, + "application/json": { + "schema": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + } + }, + "text/json": { + "schema": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + } + } + } + } + } + } + } + }, + "tags": [ + { + "name": "Test" + } + ], + "x-api-versioning": [ + { + "title": "Version Deprecation Policy", + "type": "text/html", + "rel": "deprecation", + "url": "http://my.api.com/policies/versions/deprecated.html" + }, + { + "title": "Version Sunset Policy", + "type": "text/html", + "rel": "sunset", + "url": "http://my.api.com/policies/versions/sunset.html" + } + ] +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/OpenApiDocumentDescriptionOptionsTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/OpenApiDocumentDescriptionOptionsTest.cs new file mode 100644 index 00000000..e85d4136 --- /dev/null +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/OpenApiDocumentDescriptionOptionsTest.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + + +namespace Asp.Versioning.OpenApi; + +public class OpenApiDocumentDescriptionOptionsTest +{ + [Fact] + public void deprecation_notice_should_be_default_without_a_date() + { + // arrange + var options = new OpenApiDocumentDescriptionOptions(); + var policy = new DeprecationPolicy(); + + // act + var actual = options.DeprecationNotice( policy ); + + // assert + actual.Should().Be( "The API is deprecated." ); + } + + [Fact] + public void deprecation_notice_should_return_expected_message() + { + // arrange + var expected = "The API was deprecated on 2/8/2026."; + var options = new OpenApiDocumentDescriptionOptions(); + var date = new DateTimeOffset( new DateTime( 2026, 2, 8 ) ); + var policy = new DeprecationPolicy( date ); + + // act + var actual = options.DeprecationNotice( policy ); + + // assert + actual.Should().Be( expected ); + } + + [Fact] + public void sunset_notice_should_be_null_without_a_date() + { + // arrange + var options = new OpenApiDocumentDescriptionOptions(); + var policy = new SunsetPolicy(); + + // act + var actual = options.SunsetNotice( policy ); + + // assert + actual.Should().BeNull(); + } + + [Fact] + public void sunset_notice_should_return_expected_message() + { + // arrange + var expected = "The API was sunset on 2/8/2026."; + var options = new OpenApiDocumentDescriptionOptions(); + var date = new DateTimeOffset( new DateTime( 2026, 2, 8 ) ); + var policy = new SunsetPolicy( date ); + + // act + var actual = options.SunsetNotice( policy ); + + // assert + actual.Should().Be( expected ); + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/MinimalApi.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/MinimalApi.cs new file mode 100644 index 00000000..014dafb0 --- /dev/null +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/MinimalApi.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +#pragma warning disable SA1629 + +namespace Asp.Versioning.OpenApi.Simulators; + +public static class MinimalApi +{ + /// + /// Test + /// + /// A test API. + /// A test parameter. + /// The original identifier. + /// Pass + /// Fail + public static int Get( int id ) => id; +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/TestController.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/TestController.cs new file mode 100644 index 00000000..363012e5 --- /dev/null +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Simulators/TestController.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +#pragma warning disable CA1822 +#pragma warning disable SA1629 + +namespace Asp.Versioning.OpenApi.Simulators; + +using Microsoft.AspNetCore.Mvc; + +[ApiVersion( 1.0 )] +[ApiController] +[Route( "[controller]" )] +public class TestController : ControllerBase +{ + /// + /// Test + /// + /// A test API. + /// A test parameter. + /// The original identifier. + /// Pass + /// Fail + [HttpGet] + public int Get( int id ) => id; +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/AcceptanceTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/AcceptanceTest.cs new file mode 100644 index 00000000..738e5d89 --- /dev/null +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/AcceptanceTest.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Transformers; + +using Asp.Versioning.OpenApi.Simulators; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using System.Net.Http.Json; +using System.Text.Json.Nodes; + +public class AcceptanceTest +{ + [Fact] + public async Task minimal_api_should_generate_expected_open_api_document() + { + // arrange + var builder = WebApplication.CreateBuilder(); + + builder.WebHost.UseTestServer(); + builder.Services.AddControllers() + .AddApplicationPart( GetType().Assembly ); + builder.Services.AddApiVersioning( options => AddPolicies( options ) ) + .AddMvc() + .AddApiExplorer( options => options.GroupNameFormat = "'v'VVV" ) + .AddOpenApi(); + + var app = builder.Build(); + var api = app.NewVersionedApi( "Test" ) + .MapGroup( "/test" ) + .HasApiVersion( 1.0 ); + + api.MapGet( "{id:int}", MinimalApi.Get ).Produces().Produces( 400 ); + app.MapOpenApi().WithDocumentPerVersion(); + + var cancellationToken = TestContext.Current.CancellationToken; + using var stream = File.OpenRead( Path.Combine( AppContext.BaseDirectory, "Content", "v1.json" ) ); + var expected = await JsonNode.ParseAsync( stream, default, default, cancellationToken ); + + await app.StartAsync( cancellationToken ); + + using var client = app.GetTestClient(); + + // act + var actual = await client.GetFromJsonAsync( "/openapi/v1.json", cancellationToken ); + + // assert + JsonNode.DeepEquals( actual, expected ).Should().BeTrue(); + } + + [Fact] + public async Task controller_should_generate_expected_open_api_document() + { + // arrange + var builder = WebApplication.CreateBuilder(); + + builder.WebHost.UseTestServer(); + builder.Services.AddControllers() + .AddApplicationPart( GetType().Assembly ); + builder.Services.AddApiVersioning( options => AddPolicies( options ) ) + .AddMvc() + .AddApiExplorer( options => options.GroupNameFormat = "'v'VVV" ) + .AddOpenApi(); + + var app = builder.Build(); + + app.MapControllers(); + app.MapOpenApi().WithDocumentPerVersion(); + + var cancellationToken = TestContext.Current.CancellationToken; + using var stream = File.OpenRead( Path.Combine( AppContext.BaseDirectory, "Content", "v1.json" ) ); + var expected = await JsonNode.ParseAsync( stream, default, default, cancellationToken ); + + await app.StartAsync( cancellationToken ); + + using var client = app.GetTestClient(); + + // act + var actual = await client.GetFromJsonAsync( "/openapi/v1.json", cancellationToken ); + + // assert + JsonNode.DeepEquals( actual, expected ).Should().BeTrue(); + } + + private static ApiVersioningOptions AddPolicies( ApiVersioningOptions options ) + { + options.Policies.Deprecate( 1.0 ) + .Effective( 2026, 1, 1 ) + .Link( "http://my.api.com/policies/versions/deprecated.html" ) + .Title( "Version Deprecation Policy" ) + .Type( "text/html" ); + + options.Policies.Sunset( 1.0 ) + .Effective( 2026, 2, 10 ) + .Link( "http://my.api.com/policies/versions/sunset.html" ) + .Title( "Version Sunset Policy" ) + .Type( "text/html" ); + + return options; + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/XmlCommentsTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/XmlCommentsTest.cs new file mode 100644 index 00000000..c0ae2c71 --- /dev/null +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.OpenApi.Tests/Transformers/XmlCommentsTest.cs @@ -0,0 +1,120 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.OpenApi.Transformers; + +using Asp.Versioning.OpenApi.Simulators; + +public class XmlCommentsTest +{ + [Fact] + public void summary_should_be_retrieved_for_minimal_api() + { + // arrange + var comments = XmlComments.FromFile( FilePath.XmlCommentFile ); + var method = typeof( MinimalApi ).GetMethod( nameof( MinimalApi.Get ) ); + + // act + var summary = comments.GetSummary( method ); + + // assert + summary.Should().Be( "Test" ); + } + + [Fact] + public void description_should_be_retrieved_for_minimal_api() + { + // arrange + var comments = XmlComments.FromFile( FilePath.XmlCommentFile ); + var method = typeof( MinimalApi ).GetMethod( nameof( MinimalApi.Get ) ); + + // act + var description = comments.GetDescription( method ); + + // assert + description.Should().Be( "A test API." ); + } + + [Fact] + public void parameter_description_should_be_retrieved_for_minimal_api() + { + // arrange + var comments = XmlComments.FromFile( FilePath.XmlCommentFile ); + var method = typeof( MinimalApi ).GetMethod( nameof( MinimalApi.Get ) ); + + // act + var description = comments.GetParameterDescription( method, "id" ); + + // assert + description.Should().Be( "A test parameter." ); + } + + [Fact] + public void response_description_should_be_retrieved_for_minimal_api() + { + // arrange + var comments = XmlComments.FromFile( FilePath.XmlCommentFile ); + var method = typeof( MinimalApi ).GetMethod( nameof( MinimalApi.Get ) ); + + // act + var description = comments.GetResponseDescription( method, 200 ); + + // assert + description.Should().Be( "Pass" ); + } + + [Fact] + public void summary_should_be_retrieved_for_controller() + { + // arrange + var comments = XmlComments.FromFile( FilePath.XmlCommentFile ); + var method = typeof( TestController ).GetMethod( nameof( TestController.Get ) ); + + // act + var summary = comments.GetSummary( method ); + + // assert + summary.Should().Be( "Test" ); + } + + [Fact] + public void description_should_be_retrieved_for_controller() + { + // arrange + var comments = XmlComments.FromFile( FilePath.XmlCommentFile ); + var method = typeof( TestController ).GetMethod( nameof( TestController.Get ) ); + + // act + var description = comments.GetDescription( method ); + + // assert + description.Should().Be( "A test API." ); + } + + [Fact] + public void parameter_description_should_be_retrieved_for_controller() + { + // arrange + var comments = XmlComments.FromFile( FilePath.XmlCommentFile ); + var method = typeof( TestController ).GetMethod( nameof( TestController.Get ) ); + + // act + var description = comments.GetParameterDescription( method, "id" ); + + // assert + description.Should().Be( "A test parameter." ); + } + + [Fact] + public void response_description_should_be_retrieved_for_controller() + { + // arrange + var comments = XmlComments.FromFile( FilePath.XmlCommentFile ); + var method = typeof( TestController ).GetMethod( nameof( TestController.Get ) ); + + // act + var description = comments.GetResponseDescription( method, 400 ); + + // assert + description.Should().Be( "Fail" ); + } +} \ No newline at end of file diff --git a/src/Client/src/Asp.Versioning.Http.Client/ApiInformation.cs b/src/Client/src/Asp.Versioning.Http.Client/ApiInformation.cs index 53046eab..6b357793 100644 --- a/src/Client/src/Asp.Versioning.Http.Client/ApiInformation.cs +++ b/src/Client/src/Asp.Versioning.Http.Client/ApiInformation.cs @@ -33,8 +33,8 @@ public ApiInformation( private ApiInformation() { - SupportedApiVersions = Array.Empty(); - DeprecatedApiVersions = Array.Empty(); + SupportedApiVersions = []; + DeprecatedApiVersions = []; SunsetPolicy = new(); DeprecationPolicy = new(); OpenApiDocumentUrls = new Dictionary( capacity: 0 ); diff --git a/src/Client/src/Asp.Versioning.Http.Client/ApiNotificationContext.cs b/src/Client/src/Asp.Versioning.Http.Client/ApiNotificationContext.cs index 27483f37..bf6a01db 100644 --- a/src/Client/src/Asp.Versioning.Http.Client/ApiNotificationContext.cs +++ b/src/Client/src/Asp.Versioning.Http.Client/ApiNotificationContext.cs @@ -51,11 +51,11 @@ public ApiNotificationContext( HttpResponseMessage response, ApiVersion apiVersi /// Gets the API sunset policy reported in the response. /// /// The reported API sunset policy. - public SunsetPolicy SunsetPolicy => sunsetPolicy ??= Response.ReadSunsetPolicy(); + public SunsetPolicy SunsetPolicy => sunsetPolicy ??= Response.SunsetPolicy; /// /// Gets the API deprecation policy reported in the response. /// /// The reported API deprecation policy. - public DeprecationPolicy DeprecationPolicy => deprecationPolicy ??= Response.ReadDeprecationPolicy(); + public DeprecationPolicy DeprecationPolicy => deprecationPolicy ??= Response.DeprecationPolicy; } \ No newline at end of file diff --git a/src/Client/src/Asp.Versioning.Http.Client/ApiVersionEnumerator.cs b/src/Client/src/Asp.Versioning.Http.Client/ApiVersionEnumerator.cs index 6f940aee..39e2b1bc 100644 --- a/src/Client/src/Asp.Versioning.Http.Client/ApiVersionEnumerator.cs +++ b/src/Client/src/Asp.Versioning.Http.Client/ApiVersionEnumerator.cs @@ -35,7 +35,7 @@ public ApiVersionEnumerator( ArgumentNullException.ThrowIfNull( response ); ArgumentException.ThrowIfNullOrEmpty( headerName ); - this.values = response.Headers.TryGetValues( headerName, out var values ) ? values.ToArray() : []; + this.values = response.Headers.TryGetValues( headerName, out var values ) ? [.. values] : []; this.parser = parser ?? ApiVersionParser.Default; } diff --git a/src/Client/src/Asp.Versioning.Http.Client/ApiVersionHandler.cs b/src/Client/src/Asp.Versioning.Http.Client/ApiVersionHandler.cs index 92abb261..b9defa83 100644 --- a/src/Client/src/Asp.Versioning.Http.Client/ApiVersionHandler.cs +++ b/src/Client/src/Asp.Versioning.Http.Client/ApiVersionHandler.cs @@ -70,7 +70,7 @@ protected virtual bool IsDeprecatedApi( HttpResponseMessage response, out Deprec { ArgumentNullException.ThrowIfNull( response ); - deprecationPolicy = response.ReadDeprecationPolicy(); + deprecationPolicy = response.DeprecationPolicy; if ( deprecationPolicy.Date.HasValue && deprecationPolicy.Date <= DateTimeOffset.UtcNow ) { diff --git a/src/Client/src/Asp.Versioning.Http.Client/Asp.Versioning.Http.Client.csproj b/src/Client/src/Asp.Versioning.Http.Client/Asp.Versioning.Http.Client.csproj index e04e62ca..5dcd6170 100644 --- a/src/Client/src/Asp.Versioning.Http.Client/Asp.Versioning.Http.Client.csproj +++ b/src/Client/src/Asp.Versioning.Http.Client/Asp.Versioning.Http.Client.csproj @@ -1,13 +1,20 @@  - 8.1.0 - 8.1.0.0 + 10.0.0 + 10.0.0.0 $(DefaultTargetFramework);netstandard1.1;netstandard2.0 Asp.Versioning.Http API Versioning Client Extensions The HTTP client extensions library for API versioning. Asp;AspNet;AspNetCore;Versioning;Http + + + $(NoWarn);NU1903 diff --git a/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs b/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs index 974a5db0..351fac00 100644 --- a/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs +++ b/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Net.Http; using Asp.Versioning; @@ -14,89 +16,89 @@ namespace System.Net.Http; /// public static class HttpClientExtensions { - /// - /// Gets the API information for the specified request URL. - /// - /// The extended HTTP client. - /// The URL to get the API information from. - /// The optional parser used - /// to process retrieved API versions. - /// The optional enumerable - /// used to enumerate retrieved API versions. - /// The token that can be used to cancel the operation. - /// A task containing the retrieved API information. - /// API information is retrieved by sending an OPTIONS request to the specified URL. - /// If the resource does not exist or OPTIONS is not allowed, then - /// will be returned. - public static Task GetApiInformationAsync( - this HttpClient client, - string requestUrl, - IApiVersionParser? parser = default, - ApiVersionHeaderEnumerable? enumerable = default, - CancellationToken cancellationToken = default ) => - client.GetApiInformationAsync( - new Uri( requestUrl, UriKind.RelativeOrAbsolute ), - parser, - enumerable, - cancellationToken ); - - /// - /// Gets the API information for the specified request URL. - /// /// The extended HTTP client. - /// The URL to get the API information from. - /// The optional parser used - /// to process retrieved API versions. - /// The optional enumerable - /// used to enumerate retrieved API versions. - /// The token that can be used to cancel the operation. - /// A task containing the retrieved API information. - /// API information is retrieved by sending an OPTIONS request to the specified URL. - /// If the resource does not exist or OPTIONS is not allowed, then - /// will be returned. - public static async Task GetApiInformationAsync( - this HttpClient client, - Uri requestUrl, - IApiVersionParser? parser = default, - ApiVersionHeaderEnumerable? enumerable = default, - CancellationToken cancellationToken = default ) + extension( HttpClient client ) { - ArgumentNullException.ThrowIfNull( client ); + /// + /// Gets the API information for the specified request URL. + /// + /// The URL to get the API information from. + /// The optional parser used + /// to process retrieved API versions. + /// The optional enumerable + /// used to enumerate retrieved API versions. + /// The token that can be used to cancel the operation. + /// A task containing the retrieved API information. + /// API information is retrieved by sending an OPTIONS request to the specified URL. + /// If the resource does not exist or OPTIONS is not allowed, then + /// will be returned. + public Task GetApiInformationAsync( + string requestUrl, + IApiVersionParser? parser = default, + ApiVersionHeaderEnumerable? enumerable = default, + CancellationToken cancellationToken = default ) => + client.GetApiInformationAsync( + new Uri( requestUrl, UriKind.RelativeOrAbsolute ), + parser, + enumerable, + cancellationToken ); - using var request = new HttpRequestMessage( HttpMethod.Options, requestUrl ); - var response = await client.SendAsync( request, cancellationToken ).ConfigureAwait( false ); + /// + /// Gets the API information for the specified request URL. + /// + /// The URL to get the API information from. + /// The optional parser used + /// to process retrieved API versions. + /// The optional enumerable + /// used to enumerate retrieved API versions. + /// The token that can be used to cancel the operation. + /// A task containing the retrieved API information. + /// API information is retrieved by sending an OPTIONS request to the specified URL. + /// If the resource does not exist or OPTIONS is not allowed, then + /// will be returned. + public async Task GetApiInformationAsync( + Uri requestUrl, + IApiVersionParser? parser = default, + ApiVersionHeaderEnumerable? enumerable = default, + CancellationToken cancellationToken = default ) + { + ArgumentNullException.ThrowIfNull( client ); - response.RequestMessage ??= request; + using var request = new HttpRequestMessage( HttpMethod.Options, requestUrl ); + var response = await client.SendAsync( request, cancellationToken ).ConfigureAwait( false ); - switch ( response.StatusCode ) - { - case NotFound: - case MethodNotAllowed: - return ApiInformation.None; - } + response.RequestMessage ??= request; - parser ??= ApiVersionParser.Default; - enumerable ??= new(); - var versions = new SortedSet( enumerable.Supported( response, parser ) ); - var supported = versions.ToArray(); + switch ( response.StatusCode ) + { + case NotFound: + case MethodNotAllowed: + return ApiInformation.None; + } - versions.Clear(); + parser ??= ApiVersionParser.Default; + enumerable ??= new(); + var versions = new SortedSet( enumerable.Supported( response, parser ) ); + var supported = versions.ToArray(); - foreach ( var version in enumerable.Deprecated( response, parser ) ) - { - versions.Add( version ); - } + versions.Clear(); - if ( supported.Length == 0 && versions.Count == 0 ) - { - return ApiInformation.None; - } + foreach ( var version in enumerable.Deprecated( response, parser ) ) + { + versions.Add( version ); + } - var deprecated = versions.ToArray(); - var sunsetPolicy = response.ReadSunsetPolicy(); - var deprecationPolicy = response.ReadDeprecationPolicy(); - var urls = response.GetOpenApiDocumentUrls( parser ); + if ( supported.Length == 0 && versions.Count == 0 ) + { + return ApiInformation.None; + } + + var deprecated = versions.ToArray(); + var sunsetPolicy = response.SunsetPolicy; + var deprecationPolicy = response.DeprecationPolicy; + var urls = response.GetOpenApiDocumentUrls( parser ); - return new( supported, deprecated, sunsetPolicy, deprecationPolicy, urls ); + return new( supported, deprecated, sunsetPolicy, deprecationPolicy, urls ); + } } } \ No newline at end of file diff --git a/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpResponseMessageExtensions.cs b/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpResponseMessageExtensions.cs index a072788c..59ab5520 100644 --- a/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpResponseMessageExtensions.cs +++ b/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpResponseMessageExtensions.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Net.Http; using Asp.Versioning; @@ -17,186 +19,171 @@ public static class HttpResponseMessageExtensions private const string Sunset = nameof( Sunset ); private const string Deprecation = nameof( Deprecation ); private const string Link = nameof( Link ); - #if NETSTANDARD1_1 - private static readonly DateTime UnixEpoch = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc ); + private static readonly DateTime UnixEpoch = new( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc ); #endif - /// - /// Gets an API sunset policy from the HTTP response. - /// - /// The HTTP response to read from. - /// A new sunset policy. - public static SunsetPolicy ReadSunsetPolicy( this HttpResponseMessage response ) + extension( HttpResponseMessage response ) { - ArgumentNullException.ThrowIfNull( response ); - - var headers = response.Headers; - var date = default( DateTimeOffset ); - SunsetPolicy policy; - - if ( headers.TryGetValues( Sunset, out var values ) ) + /// + /// Gets an API sunset policy from the HTTP response. + /// + /// A new sunset policy. + public SunsetPolicy SunsetPolicy { - var culture = CultureInfo.CurrentCulture; - var style = DateTimeStyles.RoundtripKind; - - foreach ( var value in values ) + get { - if ( DateTimeOffset.TryParse( value, culture, style, out var result ) && - ( date == default || date < result ) ) - { - date = result; - } - } - - policy = date == default ? new() : new( date ); - } - else - { - policy = new(); - } + ArgumentNullException.ThrowIfNull( response ); - if ( headers.TryGetValues( Link, out values ) ) - { - var baseUrl = response.RequestMessage?.RequestUri; - Func resolver = baseUrl is null ? url => url : url => new( baseUrl, url ); + var headers = response.Headers; + var date = default( DateTimeOffset ); + SunsetPolicy policy; - foreach ( var value in values ) - { - if ( LinkHeaderValue.TryParse( value, resolver, out var link ) && - link.RelationType.Equals( "sunset", OrdinalIgnoreCase ) ) + if ( headers.TryGetValues( Sunset, out var values ) ) { - policy.Links.Add( link ); - } - } - } + var culture = CultureInfo.CurrentCulture; + var style = DateTimeStyles.RoundtripKind; - return policy; - } + foreach ( var value in values ) + { + if ( DateTimeOffset.TryParse( value, culture, style, out var result ) && + ( date == default || date < result ) ) + { + date = result; + } + } - /// - /// Formats the as required for a Deprecation header. - /// - /// The date when the api is deprecated. - /// A formatted string as required for a Deprecation header. - public static string ToDeprecationHeaderValue( this DateTimeOffset deprecationDate ) - { - var unixTimestamp = deprecationDate.ToUnixTimeSeconds(); - return unixTimestamp.ToString( "'@'0", CultureInfo.InvariantCulture ); - } + policy = date == default ? new() : new( date ); + } + else + { + policy = new(); + } - /// - /// Gets an API deprecation policy from the HTTP response. - /// - /// The HTTP response to read from. - /// A new deprecation policy. - public static DeprecationPolicy ReadDeprecationPolicy( this HttpResponseMessage response ) - { - ArgumentNullException.ThrowIfNull( response ); + response.AddLinks( policy.Links, "sunset" ); - var headers = response.Headers; - var date = default( DateTimeOffset ); - DeprecationPolicy policy; + return policy; + } + } - if ( headers.TryGetValues( Deprecation, out var values ) ) + /// + /// Gets an API deprecation policy from the HTTP response. + /// + /// A new deprecation policy. + public DeprecationPolicy DeprecationPolicy { - var culture = CultureInfo.InvariantCulture; - var style = NumberStyles.Integer; - - foreach ( var value in values ) + get { - if ( value.Length < 2 || value[0] != '@' ) + ArgumentNullException.ThrowIfNull( response ); + + var headers = response.Headers; + var date = default( DateTimeOffset ); + DeprecationPolicy policy; + + if ( headers.TryGetValues( Deprecation, out var values ) ) { - continue; - } + var culture = CultureInfo.InvariantCulture; + var style = NumberStyles.Integer; + + foreach ( var value in values ) + { + if ( value.Length < 2 || value[0] != '@' ) + { + continue; + } #if NETSTANDARD - if ( long.TryParse( value.Substring( 1 ), style, culture, out var unixTimestamp ) ) + if ( long.TryParse( value.Substring( 1 ), style, culture, out var seconds ) ) #else - if ( long.TryParse( value.AsSpan()[1..], style, culture, out var unixTimestamp ) ) + if ( long.TryParse( value.AsSpan()[1..], style, culture, out var seconds ) ) #endif - { - DateTimeOffset parsed; + { + DateTimeOffset parsed; #if NETSTANDARD1_1 - parsed = UnixEpoch + TimeSpan.FromSeconds( unixTimestamp ); + parsed = UnixEpoch + TimeSpan.FromSeconds( seconds ); #else - parsed = DateTimeOffset.FromUnixTimeSeconds( unixTimestamp ); + parsed = DateTimeOffset.FromUnixTimeSeconds( seconds ); #endif - - if ( date == default || date > parsed ) - { - date = parsed; + if ( date == default || date > parsed ) + { + date = parsed; + } + } } + + policy = date == default ? new() : new( date ); + } + else + { + policy = new(); } - } - policy = date == default ? new() : new( date ); - } - else - { - policy = new(); + response.AddLinks( policy.Links, "deprecation" ); + + return policy; + } } - if ( headers.TryGetValues( Link, out values ) ) + /// + /// Gets the OpenAPI document URLs from the HTTP response. + /// + /// The optional parser used to parse API versions. + /// A new read-only dictionary of API version + /// to URL mappings. + public IReadOnlyDictionary GetOpenApiDocumentUrls( IApiVersionParser? parser = default ) { - var baseUrl = response.RequestMessage?.RequestUri; - Func resolver = baseUrl is null ? url => url : url => new( baseUrl, url ); + ArgumentNullException.ThrowIfNull( response ); - foreach ( var value in values ) + var urls = default( Dictionary ); + + if ( response.Headers.TryGetValues( Link, out var values ) ) { - if ( LinkHeaderValue.TryParse( value, resolver, out var link ) && - link.RelationType.Equals( "deprecation", OrdinalIgnoreCase ) ) + var baseUrl = response.RequestMessage?.RequestUri; + Func resolver = baseUrl is null ? url => url : url => new( baseUrl, url ); + + foreach ( var value in values ) { - policy.Links.Add( link ); - } - } - } + if ( !LinkHeaderValue.TryParse( value, resolver, out var link ) || + ( !link.RelationType.Equals( "openapi", OrdinalIgnoreCase ) && + !link.RelationType.Equals( "swagger", OrdinalIgnoreCase ) ) ) + { + continue; + } - return policy; - } + var key = GetApiVersionExtension( link, ref parser ); + urls ??= []; + urls[key] = link.LinkTarget; + } - /// - /// Gets the OpenAPI document URLs from the HTTP response. - /// - /// The HTTP response to read from. - /// The optional parser used to parse API versions. - /// A new read-only dictionary of API version - /// to URL mappings. - public static IReadOnlyDictionary GetOpenApiDocumentUrls( - this HttpResponseMessage response, - IApiVersionParser? parser = default ) - { - ArgumentNullException.ThrowIfNull( response ); + urls ??= []; + } + else + { + urls = []; + } - var urls = default( Dictionary ); + return urls; + } - if ( response.Headers.TryGetValues( Link, out var values ) ) + private void AddLinks( IList links, string relationType ) { + if ( !response.Headers.TryGetValues( Link, out var values ) ) + { + return; + } + var baseUrl = response.RequestMessage?.RequestUri; Func resolver = baseUrl is null ? url => url : url => new( baseUrl, url ); foreach ( var value in values ) { - if ( !LinkHeaderValue.TryParse( value, resolver, out var link ) || - ( !link.RelationType.Equals( "openapi", OrdinalIgnoreCase ) && - !link.RelationType.Equals( "swagger", OrdinalIgnoreCase ) ) ) + if ( LinkHeaderValue.TryParse( value, resolver, out var link ) && + link.RelationType.Equals( relationType, OrdinalIgnoreCase ) ) { - continue; + links.Add( link ); } - - var key = GetApiVersionExtension( link, ref parser ); - urls ??= []; - urls[key] = link.LinkTarget; } - - urls ??= new( capacity: 0 ); } - else - { - urls = new( capacity: 0 ); - } - - return urls; } private static ApiVersion GetApiVersionExtension( LinkHeaderValue link, ref IApiVersionParser? parser ) diff --git a/src/Client/src/Asp.Versioning.Http.Client/net#.0/DependencyInjection/IHttpClientBuilderExtensions.cs b/src/Client/src/Asp.Versioning.Http.Client/net#.0/DependencyInjection/IHttpClientBuilderExtensions.cs index 08f24e42..ad27210f 100644 --- a/src/Client/src/Asp.Versioning.Http.Client/net#.0/DependencyInjection/IHttpClientBuilderExtensions.cs +++ b/src/Client/src/Asp.Versioning.Http.Client/net#.0/DependencyInjection/IHttpClientBuilderExtensions.cs @@ -15,104 +15,99 @@ namespace Microsoft.Extensions.DependencyInjection; /// public static class IHttpClientBuilderExtensions { - /// - /// Adds the specified API version to the corresponding HTTP client. - /// /// The extended HTTP client builder. - /// The major version number. - /// The optional minor version number. - /// The optional version status. - /// The optional API writer. /// The original . - /// If is not provided, then an instance will be - /// resolved from the associated . - public static IHttpClientBuilder AddApiVersion( - this IHttpClientBuilder builder, - int majorVersion, - int? minorVersion = default, - string? status = default, - IApiVersionWriter? apiVersionWriter = default ) => - builder.AddApiVersion( new ApiVersion( majorVersion, minorVersion, status ), apiVersionWriter ); - - /// - /// Adds the specified API version to the corresponding HTTP client. - /// - /// The extended HTTP client builder. - /// The version number. - /// The optional version status. - /// The optional API writer. - /// The original . - /// If is not provided, then an instance will be - /// resolved from the associated . - public static IHttpClientBuilder AddApiVersion( - this IHttpClientBuilder builder, - double version, - string? status = default, - IApiVersionWriter? apiVersionWriter = default ) => - builder.AddApiVersion( new ApiVersion( version, status ), apiVersionWriter ); - - /// - /// Adds the specified API version to the corresponding HTTP client. - /// - /// The extended HTTP client builder. - /// The version year. - /// The version month. - /// The version day. - /// The optional version status. - /// The optional API writer. - /// The original . - /// If is not provided, then an instance will be - /// resolved from the associated . - public static IHttpClientBuilder AddApiVersion( - this IHttpClientBuilder builder, - int year, - int month, - int day, - string? status = default, - IApiVersionWriter? apiVersionWriter = default ) => - builder.AddApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ), apiVersionWriter ); - - /// - /// Adds the specified API version to the corresponding HTTP client. - /// - /// The extended HTTP client builder. - /// The group version. - /// The optional version status. - /// The optional API writer. - /// The original . - /// If is not provided, then an instance will be - /// resolved from the associated . - public static IHttpClientBuilder AddApiVersion( - this IHttpClientBuilder builder, - DateOnly groupVersion, - string? status = default, - IApiVersionWriter? apiVersionWriter = default ) => - builder.AddApiVersion( new ApiVersion( groupVersion, status ), apiVersionWriter ); - - /// - /// Adds the specified API version to the corresponding HTTP client. - /// - /// The extended HTTP client builder. - /// The API version added to requests. - /// The optional API writer. - /// The original . - /// If is not provided, then an instance will be - /// resolved from the associated . - public static IHttpClientBuilder AddApiVersion( - this IHttpClientBuilder builder, - ApiVersion apiVersion, - IApiVersionWriter? apiVersionWriter = default ) + extension( IHttpClientBuilder builder ) { - ArgumentNullException.ThrowIfNull( builder ); + /// + /// Adds the specified API version to the corresponding HTTP client. + /// + /// The major version number. + /// The optional minor version number. + /// The optional version status. + /// The optional API writer. + /// The original builder. + /// If is not provided, then an instance will be + /// resolved from the associated . + public IHttpClientBuilder AddApiVersion( + int majorVersion, + int? minorVersion = default, + string? status = default, + IApiVersionWriter? apiVersionWriter = default ) => + builder.AddApiVersion( new ApiVersion( majorVersion, minorVersion, status ), apiVersionWriter ); + + /// + /// Adds the specified API version to the corresponding HTTP client. + /// + /// The version number. + /// The optional version status. + /// The optional API writer. + /// The original builder. + /// If is not provided, then an instance will be + /// resolved from the associated . + public IHttpClientBuilder AddApiVersion( + double version, + string? status = default, + IApiVersionWriter? apiVersionWriter = default ) => + builder.AddApiVersion( new ApiVersion( version, status ), apiVersionWriter ); + + /// + /// Adds the specified API version to the corresponding HTTP client. + /// + /// The version year. + /// The version month. + /// The version day. + /// The optional version status. + /// The optional API writer. + /// The original builder. + /// If is not provided, then an instance will be + /// resolved from the associated . + public IHttpClientBuilder AddApiVersion( + int year, + int month, + int day, + string? status = default, + IApiVersionWriter? apiVersionWriter = default ) => + builder.AddApiVersion( new ApiVersion( new DateOnly( year, month, day ), status ), apiVersionWriter ); + + /// + /// Adds the specified API version to the corresponding HTTP client. + /// + /// The group version. + /// The optional version status. + /// The optional API writer. + /// The original builder. + /// If is not provided, then an instance will be + /// resolved from the associated . + public IHttpClientBuilder AddApiVersion( + DateOnly groupVersion, + string? status = default, + IApiVersionWriter? apiVersionWriter = default ) => + builder.AddApiVersion( new ApiVersion( groupVersion, status ), apiVersionWriter ); + + /// + /// Adds the specified API version to the corresponding HTTP client. + /// + /// The API version added to requests. + /// The optional API writer. + /// The original builder. + /// If is not provided, then an instance will be + /// resolved from the associated . + public IHttpClientBuilder AddApiVersion( + ApiVersion apiVersion, + IApiVersionWriter? apiVersionWriter = default ) + { + ArgumentNullException.ThrowIfNull( builder ); - var services = builder.Services; + var services = builder.Services; - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddTransient(); - builder.AddHttpMessageHandler( sp => NewApiVersionHandler( sp, apiVersion, apiVersionWriter ) ); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddTransient(); + builder.AddHttpMessageHandler( sp => NewApiVersionHandler( sp, apiVersion, apiVersionWriter ) ); - return builder; + return builder; + } } private static ApiVersionHandler NewApiVersionHandler( diff --git a/src/Client/src/Asp.Versioning.Http.Client/net#.0/ILoggerExtensions.cs b/src/Client/src/Asp.Versioning.Http.Client/net#.0/ILoggerExtensions.cs index 841e086a..6ca02c09 100644 --- a/src/Client/src/Asp.Versioning.Http.Client/net#.0/ILoggerExtensions.cs +++ b/src/Client/src/Asp.Versioning.Http.Client/net#.0/ILoggerExtensions.cs @@ -11,7 +11,6 @@ namespace Asp.Versioning.Http; internal static partial class ILoggerExtensions { - [MethodImpl( MethodImplOptions.AggressiveInlining )] internal static void ApiVersionDeprecated( this ILogger logger, Uri requestUrl, @@ -19,6 +18,11 @@ internal static void ApiVersionDeprecated( SunsetPolicy sunsetPolicy, DeprecationPolicy deprecationPolicy ) { + if ( !logger.IsEnabled( Warning ) ) + { + return; + } + var sunsetDate = FormatDate( sunsetPolicy.Date ); var deprecationDate = FormatDate( deprecationPolicy.Date ); @@ -27,6 +31,9 @@ internal static void ApiVersionDeprecated( var additionalInfo = additionalInfoDeprecation.Concat( additionalInfoSunset ).ToArray(); +#pragma warning disable IDE0079 +#pragma warning disable CA1873 + ApiVersionDeprecated( logger, apiVersion.ToString(), @@ -34,6 +41,9 @@ internal static void ApiVersionDeprecated( sunsetDate, deprecationDate, additionalInfo ); + +#pragma warning restore CA1873 +#pragma warning restore IDE0079 } [LoggerMessage( EventId = 1, Level = Warning, Message = "API version {apiVersion} for {requestUrl} has been deprecated since {deprecationDate} and will sunset on {sunsetDate}. Additional information: {links}" )] @@ -45,7 +55,6 @@ static partial void ApiVersionDeprecated( string deprecationDate, string[] links ); - [MethodImpl( MethodImplOptions.AggressiveInlining )] internal static void NewApiVersionAvailable( this ILogger logger, Uri requestUrl, @@ -53,9 +62,17 @@ internal static void NewApiVersionAvailable( ApiVersion newApiVersion, SunsetPolicy sunsetPolicy ) { + if ( !logger.IsEnabled( Information ) ) + { + return; + } + var sunsetDate = FormatDate( sunsetPolicy.Date ); var additionalInfo = FormatLinks( sunsetPolicy ); +#pragma warning disable IDE0079 +#pragma warning disable CA1873 + NewApiVersionAvailable( logger, newApiVersion.ToString(), @@ -63,10 +80,13 @@ internal static void NewApiVersionAvailable( currentApiVersion.ToString(), sunsetDate, additionalInfo ); + +#pragma warning restore CA1873 +#pragma warning restore IDE0079 } [LoggerMessage( EventId = 2, Level = Information, Message = "API version {newApiVersion} is now available for {requestUrl} ({currentApiVersion}) until {sunsetDate}. Additional information: {links}" )] - static partial void NewApiVersionAvailable( + private static partial void NewApiVersionAvailable( ILogger logger, string newApiVersion, string requestUrl, diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionHandlerTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionHandlerTest.cs index fb87a691..95dc5cec 100644 --- a/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionHandlerTest.cs +++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionHandlerTest.cs @@ -18,7 +18,7 @@ public async Task send_async_should_write_api_version_to_request() using var invoker = new HttpMessageInvoker( handler ); // act - await invoker.SendAsync( request, default ); + await invoker.SendAsync( request, TestContext.Current.CancellationToken ); // assert Mock.Get( writer ).Verify( w => w.Write( request, version ) ); @@ -39,7 +39,7 @@ public async Task send_async_should_not_notify_when_no_headers_are_set() using var invoker = new HttpMessageInvoker( handler ); // act - await invoker.SendAsync( request, default ); + await invoker.SendAsync( request, TestContext.Current.CancellationToken ); // assert Mock.Get( notification ) @@ -57,6 +57,7 @@ public async Task send_async_should_signal_deprecated_api_versions_from_header() var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" ); var response = new HttpResponseMessage(); var version = new ApiVersion( 1.0 ); + var cancellationToken = TestContext.Current.CancellationToken; using var handler = new ApiVersionHandler( writer, version, notification ) { InnerHandler = new TestServer( response ), @@ -67,11 +68,11 @@ public async Task send_async_should_signal_deprecated_api_versions_from_header() response.Headers.Add( "api-deprecated-versions", "1.0" ); // act - await invoker.SendAsync( request, default ); + await invoker.SendAsync( request, cancellationToken ); // assert Mock.Get( notification ) - .Verify( n => n.OnApiDeprecatedAsync( It.IsAny(), default ) ); + .Verify( n => n.OnApiDeprecatedAsync( It.IsAny(), cancellationToken ) ); } [Fact] @@ -83,6 +84,7 @@ public async Task send_async_should_signal_deprecated_api_versions_from_deprecat var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" ); var response = new HttpResponseMessage(); var version = new ApiVersion( 1.0 ); + var cancellationToken = TestContext.Current.CancellationToken; using var handler = new ApiVersionHandler( writer, version, notification ) { InnerHandler = new TestServer( response ), @@ -90,14 +92,14 @@ public async Task send_async_should_signal_deprecated_api_versions_from_deprecat using var invoker = new HttpMessageInvoker( handler ); response.Headers.Add( "api-supported-versions", "2.0" ); - response.Headers.Add( "Deprecation", DateTimeOffset.UtcNow.ToDeprecationHeaderValue() ); + response.Headers.Add( "deprecation", DateTimeOffset.UtcNow.ToDeprecationHeaderValue() ); // act - await invoker.SendAsync( request, default ); + await invoker.SendAsync( request, cancellationToken ); // assert Mock.Get( notification ) - .Verify( n => n.OnApiDeprecatedAsync( It.IsAny(), default ) ); + .Verify( n => n.OnApiDeprecatedAsync( It.IsAny(), cancellationToken ) ); } [Fact] @@ -109,19 +111,20 @@ public async Task send_async_should_signal_new_api_version() var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" ); var response = new HttpResponseMessage(); var version = new ApiVersion( 1.0 ); + var cancellationToken = TestContext.Current.CancellationToken; using var handler = new ApiVersionHandler( writer, version, notification ) { InnerHandler = new TestServer( response ), }; using var invoker = new HttpMessageInvoker( handler ); - response.Headers.Add( "api-supported-versions", new[] { "1.0", "2.0" } ); + response.Headers.Add( "api-supported-versions", ["1.0", "2.0"] ); // act - await invoker.SendAsync( request, default ); + await invoker.SendAsync( request, cancellationToken ); // assert Mock.Get( notification ) - .Verify( n => n.OnNewApiAvailableAsync( It.IsAny(), default ) ); + .Verify( n => n.OnNewApiAvailableAsync( It.IsAny(), cancellationToken ) ); } } \ No newline at end of file diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionWriterTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionWriterTest.cs index 3a015fb9..8f98293f 100644 --- a/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionWriterTest.cs +++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionWriterTest.cs @@ -28,7 +28,7 @@ public void combine_should_not_allow_empty_sequence() // act - Func combine = () => ApiVersionWriter.Combine( Enumerable.Empty() ); + Func combine = () => ApiVersionWriter.Combine( [] ); // assert combine.Should().Throw().And.ParamName.Should().Be( "apiVersionWriters" ); diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/Asp.Versioning.Http.Client.Tests.csproj b/src/Client/test/Asp.Versioning.Http.Client.Tests/Asp.Versioning.Http.Client.Tests.csproj index 5612c3c3..84efe7f3 100644 --- a/src/Client/test/Asp.Versioning.Http.Client.Tests/Asp.Versioning.Http.Client.Tests.csproj +++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/Asp.Versioning.Http.Client.Tests.csproj @@ -1,17 +1,12 @@  - $(DefaultTargetFramework);net452;net472 + $(DefaultTargetFramework);net472 Asp.Versioning.Http - - - - - - - + + diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/DateTimeOffsetExtensions.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/DateTimeOffsetExtensions.cs new file mode 100644 index 00000000..91763cff --- /dev/null +++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/DateTimeOffsetExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +#pragma warning disable IDE0130 + +namespace System; + +using static System.Globalization.CultureInfo; + +internal static class DateTimeOffsetExtensions +{ + extension( DateTimeOffset dateTime ) + { + public string ToDeprecationHeaderValue() => dateTime.ToUnixTimeSeconds().ToString( "'@'0", InvariantCulture ); + } +} \ No newline at end of file diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpClientExtensionsTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpClientExtensionsTest.cs index b64a396e..9f1e7328 100644 --- a/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpClientExtensionsTest.cs +++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpClientExtensionsTest.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Net.Http; using Asp.Versioning; @@ -21,22 +23,21 @@ public async Task get_api_information_async_should_return_expected_result() response.Headers.Add( "sunset", date.ToString( "r" ) ); response.Headers.Add( "link", - new[] - { + [ "; rel=\"sunset\"; type=\"text/html\"", "; rel=\"openapi\"; type=\"application/json\"; api-version=\"1.0\"", - } ); + ] ); using var server = new TestServer( response ); var client = new HttpClient( server ) { BaseAddress = new( "http://tempuri.org" ) }; // act - var info = await client.GetApiInformationAsync( "/?api-version=1.0" ); + var info = await client.GetApiInformationAsync( "/?api-version=1.0", cancellationToken: TestContext.Current.CancellationToken ); // assert info.Should().BeEquivalentTo( new ApiInformation( - new[] { new ApiVersion( 2.0 ) }, - new[] { new ApiVersion( 1.0 ) }, + [new ApiVersion( 2.0 )], + [new ApiVersion( 1.0 )], new( roundtripDate, new( new( "http://tempuri.org/policy?api-version=1.0" ), "sunset" ) { Type = "text/html", diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpResponseMessageExtensionsTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpResponseMessageExtensionsTest.cs index ef10b244..7091d38b 100644 --- a/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpResponseMessageExtensionsTest.cs +++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpResponseMessageExtensionsTest.cs @@ -1,5 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0130 + namespace System.Net.Http; using Asp.Versioning; @@ -18,7 +20,7 @@ public void read_sunset_policy_should_parse_response() response.Headers.Add( "link", "; rel=\"sunset\"; type=\"text/html\"" ); // act - var policy = response.ReadSunsetPolicy(); + var policy = response.SunsetPolicy; // assert policy.Date.Value.ToLocalTime().Should().BeCloseTo( date, TimeSpan.FromMinutes( 1d ) ); @@ -42,15 +44,14 @@ public void read_sunset_policy_should_use_greatest_date() response.Headers.Add( "sunset", - new string[] - { + [ date.ToString( "r" ), expected.ToString( "r" ), date.AddDays( 3 ).ToString( "r" ), - } ); + ] ); // act - var policy = response.ReadSunsetPolicy(); + var policy = response.SunsetPolicy; // assert policy.Date.Value.ToLocalTime().Should().BeCloseTo( expected, TimeSpan.FromMinutes( 1d ) ); @@ -66,15 +67,14 @@ public void read_sunset_policy_should_ignore_unrelated_links() response.Headers.Add( "link", - new[] - { + [ "; rel=\"openapi\"; type=\"application/json\" title=\"OpenAPI\"", "; rel=\"sunset\"; type=\"text/html\"", "; rel=\"info\"; type=\"text/html\" title=\"Documentation\"", - } ); + ] ); // act - var policy = response.ReadSunsetPolicy(); + var policy = response.SunsetPolicy; // assert policy.Date.Should().BeNull(); @@ -99,7 +99,7 @@ public void read_deprecation_policy_should_parse_response() response.Headers.Add( "link", "; rel=\"deprecation\"; type=\"text/html\"" ); // act - var policy = response.ReadDeprecationPolicy(); + var policy = response.DeprecationPolicy; // assert policy.Date.Value.ToLocalTime().Should().BeCloseTo( date, TimeSpan.FromSeconds( 2d ) ); @@ -123,15 +123,14 @@ public void read_deprecation_policy_should_use_smallest_date() response.Headers.Add( "deprecation", - new string[] - { + [ date.ToDeprecationHeaderValue(), expected.ToDeprecationHeaderValue(), expected.AddDays( 3 ).ToDeprecationHeaderValue(), - } ); + ] ); // act - var policy = response.ReadDeprecationPolicy(); + var policy = response.DeprecationPolicy; // assert policy.Date.Value.ToLocalTime().Should().BeCloseTo( expected, TimeSpan.FromSeconds( 2d ) ); @@ -147,15 +146,14 @@ public void read_deprecation_policy_should_ignore_unrelated_links() response.Headers.Add( "link", - new[] - { + [ "; rel=\"openapi\"; type=\"application/json\" title=\"OpenAPI\"", "; rel=\"deprecation\"; type=\"text/html\"", "; rel=\"info\"; type=\"text/html\" title=\"Documentation\"", - } ); + ] ); // act - var policy = response.ReadDeprecationPolicy(); + var policy = response.DeprecationPolicy; // assert policy.Date.Should().BeNull(); @@ -177,13 +175,12 @@ public void get_open_api_document_urls_should_return_expected_values() response.Headers.Add( "link", - new[] - { + [ "; rel=\"openapi\"; type=\"application/json\" title=\"OpenAPI\"", "; rel=\"sunset\"; type=\"text/html\"", "; rel=\"info\"; type=\"text/html\" title=\"Documentation\"", "; rel=\"swagger\"; type=\"application/json\"; api-version=\"1.0\"", - } ); + ] ); // act var urls = response.GetOpenApiDocumentUrls(); diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/ApiVersionHandlerLoggerTTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/ApiVersionHandlerLoggerTTest.cs index 510dec39..353bc585 100644 --- a/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/ApiVersionHandlerLoggerTTest.cs +++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/ApiVersionHandlerLoggerTTest.cs @@ -31,7 +31,7 @@ public async Task on_api_deprecated_should_log_message() response.Headers.Add( "link", "; rel=\"sunset\"; type=\"text/html\"; title=\"API Política\"; hreflang=\"es\"" ); // act - await notification.OnApiDeprecatedAsync( context, default ); + await notification.OnApiDeprecatedAsync( context, TestContext.Current.CancellationToken ); // assert var entry = factory.Sink.LogEntries.Single(); @@ -61,11 +61,11 @@ public async Task on_new_api_available_should_log_message() $"until . Additional information: " + "http://tempuri.org/policy"; - response.Headers.Add( "api-supported-versions", new[] { "1.0", "2.0" } ); + response.Headers.Add( "api-supported-versions", ["1.0", "2.0"] ); response.Headers.Add( "link", "; rel=\"sunset\"; type=\"text/html\"" ); // act - await notification.OnNewApiAvailableAsync( context, default ); + await notification.OnNewApiAvailableAsync( context, TestContext.Current.CancellationToken ); // assert var entry = factory.Sink.LogEntries.Single(); diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/DependencyInjection/IHttpClientBuilderExtensionsTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/DependencyInjection/IHttpClientBuilderExtensionsTest.cs index afd2f19c..cd28799d 100644 --- a/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/DependencyInjection/IHttpClientBuilderExtensionsTest.cs +++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/DependencyInjection/IHttpClientBuilderExtensionsTest.cs @@ -21,7 +21,7 @@ public async Task add_api_version_should_decorate_http_client() var client = factory.CreateClient( "Test" ); // act - var response = await client.GetAsync( "http://tempuri.org" ); + var response = await client.GetAsync( "http://tempuri.org", TestContext.Current.CancellationToken ); // assert response.RequestMessage.RequestUri.Should().Be( new Uri( "http://tempuri.org?api-version=1.0" ) ); @@ -43,7 +43,7 @@ public async Task add_api_version_should_use_registered_writer() var client = factory.CreateClient( "Test" ); // act - var response = await client.GetAsync( "http://tempuri.org" ); + var response = await client.GetAsync( "http://tempuri.org", TestContext.Current.CancellationToken ); // assert response.RequestMessage.Headers.GetValues( "x-ms-version" ).Single().Should().Be( "1.0" ); @@ -66,7 +66,7 @@ public async Task add_api_version_should_ignore_registered_writer() var client = factory.CreateClient( "Test" ); // act - var response = await client.GetAsync( "http://tempuri.org" ); + var response = await client.GetAsync( "http://tempuri.org", TestContext.Current.CancellationToken ); // assert response.RequestMessage.RequestUri.Should().Be( new Uri( "http://tempuri.org?ver=2022-02-01" ) ); diff --git a/src/Common/src/Common.Backport/Array.cs b/src/Common/src/Common.Backport/Array.cs index 64c3ea6f..fc92d7be 100644 --- a/src/Common/src/Common.Backport/Array.cs +++ b/src/Common/src/Common.Backport/Array.cs @@ -7,5 +7,5 @@ namespace Asp.Versioning; internal static class Array { [MethodImpl( MethodImplOptions.AggressiveInlining )] - public static T[] Empty() => new T[0]; + public static T[] Empty() => []; } \ No newline at end of file diff --git a/src/Common/src/Common.Backport/HashCode.cs b/src/Common/src/Common.Backport/HashCode.cs index d9d2794a..1b37eb60 100644 --- a/src/Common/src/Common.Backport/HashCode.cs +++ b/src/Common/src/Common.Backport/HashCode.cs @@ -58,7 +58,6 @@ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT */ -using System.Collections.Generic; #pragma warning restore IDE0079 // Remove unnecessary suppression using System.ComponentModel; using System.Numerics; @@ -93,8 +92,8 @@ private static uint GenerateGlobalSeed() // System.Security.Cryptography.RandomNumberGenerator is available // in .NET Standard 1.0 or 1.1. the goal of this backport is functional // parity with HashCode and does not need to be cryptographically secure - var epoch = new DateTime(2000, 1, 1); - var seed = (int) Math.Ceiling(DateTime.Now.Subtract( epoch ).TotalSeconds); + var epoch = new DateTime( 2000, 1, 1 ); + var seed = (int) Math.Ceiling( DateTime.Now.Subtract( epoch ).TotalSeconds ); var random = new Random( seed ); random.NextBytes( data ); #endif diff --git a/src/Common/src/Common.Backport/StringExtensions.cs b/src/Common/src/Common.Backport/StringExtensions.cs index d48cbd81..5d50a4f9 100644 --- a/src/Common/src/Common.Backport/StringExtensions.cs +++ b/src/Common/src/Common.Backport/StringExtensions.cs @@ -6,58 +6,61 @@ namespace System; internal static class StringExtensions { - public static bool Contains( this string @string, string text, StringComparison comparison ) => - @string.IndexOf( text, comparison ) >= 0; - - public static string Replace( this string @string, string oldValue, string newValue, StringComparison comparison ) + extension( string @string ) { - if ( string.IsNullOrEmpty( @string ) || string.IsNullOrEmpty( oldValue ) ) - { - return @string; - } - - switch ( comparison ) - { - case StringComparison.Ordinal: - case StringComparison.CurrentCulture: -#if NETSTANDARD2_0_OR_GREATER - case StringComparison.InvariantCulture: -#endif - return @string.Replace( oldValue, newValue ); - } - - const int NotFound = -1; - var result = new StringBuilder( @string.Length ); - var hasReplacement = !string.IsNullOrEmpty( @newValue ); - var startSearchFromIndex = 0; - int foundAt; + public bool Contains( string text, StringComparison comparison ) => + @string.IndexOf( text, comparison ) >= 0; - while ( ( foundAt = @string.IndexOf( oldValue, startSearchFromIndex, comparison ) ) != NotFound ) + public string Replace( string oldValue, string newValue, StringComparison comparison ) { - var @charsUntilReplacment = foundAt - startSearchFromIndex; - var matched = @charsUntilReplacment > 0; - - if ( matched ) + if ( string.IsNullOrEmpty( @string ) || string.IsNullOrEmpty( oldValue ) ) { - result.Append( @string, startSearchFromIndex, @charsUntilReplacment ); + return @string; } - if ( hasReplacement ) + switch ( comparison ) { - result.Append( @newValue ); + case StringComparison.Ordinal: + case StringComparison.CurrentCulture: +#if NETSTANDARD2_0_OR_GREATER + case StringComparison.InvariantCulture: +#endif + return @string.Replace( oldValue, newValue ); } - startSearchFromIndex = foundAt + oldValue.Length; + const int NotFound = -1; + var result = new StringBuilder( @string.Length ); + var hasReplacement = !string.IsNullOrEmpty( @newValue ); + var startSearchFromIndex = 0; + int foundAt; - if ( startSearchFromIndex == @string.Length ) + while ( ( foundAt = @string.IndexOf( oldValue, startSearchFromIndex, comparison ) ) != NotFound ) { - return result.ToString(); + var @charsUntilReplacment = foundAt - startSearchFromIndex; + var matched = @charsUntilReplacment > 0; + + if ( matched ) + { + result.Append( @string, startSearchFromIndex, @charsUntilReplacment ); + } + + if ( hasReplacement ) + { + result.Append( @newValue ); + } + + startSearchFromIndex = foundAt + oldValue.Length; + + if ( startSearchFromIndex == @string.Length ) + { + return result.ToString(); + } } - } - var @charsUntilStringEnd = @string.Length - startSearchFromIndex; - result.Append( @string, startSearchFromIndex, @charsUntilStringEnd ); + var @charsUntilStringEnd = @string.Length - startSearchFromIndex; + result.Append( @string, startSearchFromIndex, @charsUntilStringEnd ); - return result.ToString(); + return result.ToString(); + } } } \ No newline at end of file diff --git a/src/Common/src/Common.Mvc/CollectionExtensions.cs b/src/Common/src/Common.Mvc/CollectionExtensions.cs index 59e1e317..7e1de8ef 100644 --- a/src/Common/src/Common.Mvc/CollectionExtensions.cs +++ b/src/Common/src/Common.Mvc/CollectionExtensions.cs @@ -4,46 +4,49 @@ namespace System.Collections.Generic; internal static partial class CollectionExtensions { - internal static void UnionWith( this ICollection collection, IEnumerable other ) + extension( ICollection collection ) { - if ( collection is ISet set ) + internal void UnionWith( IEnumerable other ) { - set.UnionWith( other ); - } - else - { - switch ( other ) + if ( collection is ISet set ) + { + set.UnionWith( other ); + } + else { - case IList list: - for ( var i = 0; i < list.Count; i++ ) - { - if ( !collection.Contains( list[i] ) ) + switch ( other ) + { + case IList list: + for ( var i = 0; i < list.Count; i++ ) { - collection.Add( list[i] ); + if ( !collection.Contains( list[i] ) ) + { + collection.Add( list[i] ); + } } - } - break; - case IReadOnlyList list: - for ( var i = 0; i < list.Count; i++ ) - { - if ( !collection.Contains( list[i] ) ) + break; + case IReadOnlyList list: + for ( var i = 0; i < list.Count; i++ ) { - collection.Add( list[i] ); + if ( !collection.Contains( list[i] ) ) + { + collection.Add( list[i] ); + } } - } - break; - default: - foreach ( var item in other ) - { - if ( !collection.Contains( item ) ) + break; + default: + foreach ( var item in other ) { - collection.Add( item ); + if ( !collection.Contains( item ) ) + { + collection.Add( item ); + } } - } - break; + break; + } } } } diff --git a/src/Common/src/Common.Mvc/Conventions/ActionConventionBuilderExtensions.cs b/src/Common/src/Common.Mvc/Conventions/ActionConventionBuilderExtensions.cs index 63464548..09035a4a 100644 --- a/src/Common/src/Common.Mvc/Conventions/ActionConventionBuilderExtensions.cs +++ b/src/Common/src/Common.Mvc/Conventions/ActionConventionBuilderExtensions.cs @@ -19,67 +19,62 @@ namespace Asp.Versioning.Conventions; #endif public static class ActionConventionBuilderExtensions { - /// - /// Gets or creates the convention builder for the specified controller action method. - /// /// The type of controller. /// The extended . - /// The expression representing the controller action method. - /// A new or existing . - public static IActionConventionBuilder Action( - this IActionConventionBuilder builder, - Expression> actionExpression ) + extension( IActionConventionBuilder builder ) #if NETFRAMEWORK where TController : notnull, IHttpController #else where TController : notnull #endif { - ArgumentNullException.ThrowIfNull( builder ); - ArgumentNullException.ThrowIfNull( actionExpression ); - return builder.Action( actionExpression.ExtractMethod() ); - } + /// + /// Gets or creates the convention builder for the specified controller action method. + /// + /// The expression representing the controller action method. + /// A new or existing . + public IActionConventionBuilder Action( Expression> actionExpression ) + { + ArgumentNullException.ThrowIfNull( builder ); + ArgumentNullException.ThrowIfNull( actionExpression ); + return builder.Action( actionExpression.ExtractMethod() ); + } - /// - /// Gets or creates the convention builder for the specified controller action method. - /// - /// The type of controller. - /// The type of action result. - /// The extended . - /// The expression representing the controller action method. - /// A new or existing . - public static IActionConventionBuilder Action( - this IActionConventionBuilder builder, - Expression> actionExpression ) -#if NETFRAMEWORK - where TController : notnull, IHttpController -#else - where TController : notnull -#endif - { - ArgumentNullException.ThrowIfNull( builder ); - ArgumentNullException.ThrowIfNull( actionExpression ); - return builder.Action( actionExpression.ExtractMethod() ); + /// + /// Gets or creates the convention builder for the specified controller action method. + /// + /// The type of action result. + /// The expression representing the controller action method. + /// A new or existing . + public IActionConventionBuilder Action( Expression> actionExpression ) + { + ArgumentNullException.ThrowIfNull( builder ); + ArgumentNullException.ThrowIfNull( actionExpression ); + return builder.Action( actionExpression.ExtractMethod() ); + } } - /// - /// Gets or creates the convention builder for the specified controller action method. - /// /// The extended . - /// The name of the action method. - /// The optional array of action method argument types. - /// A new or existing . - /// The specified method name must refer to a public, non-static action method. - /// If there is only one corresponding match found, then the argument types are ignored; - /// otherwise, the argument types are used for method overload resolution. Action - /// methods that have the applied will also be ignored. + extension( IActionConventionBuilder builder ) + { + /// + /// Gets or creates the convention builder for the specified controller action method. + /// + /// The name of the action method. + /// The optional array of action method argument types. + /// A new or existing . + /// The specified method name must refer to a public, non-static action method. + /// If there is only one corresponding match found, then the argument types are ignored; + /// otherwise, the argument types are used for method overload resolution. Action + /// methods that have the applied will also be ignored. #if !NETFRAMEWORK - [UnconditionalSuppressMessage( "ILLink", "IL2072", Justification = "Controller types are never trimmed" )] + [UnconditionalSuppressMessage( "ILLink", "IL2072", Justification = "Controller types are never trimmed" )] #endif - public static IActionConventionBuilder Action( this IActionConventionBuilder builder, string methodName, params Type[] argumentTypes ) - { - ArgumentNullException.ThrowIfNull( builder ); - var method = ActionMethodResolver.Resolve( builder.ControllerType, methodName, argumentTypes ); - return builder.Action( method ); + public IActionConventionBuilder Action( string methodName, params Type[] argumentTypes ) + { + ArgumentNullException.ThrowIfNull( builder ); + var method = ActionMethodResolver.Resolve( builder.ControllerType, methodName, argumentTypes ); + return builder.Action( method ); + } } } \ No newline at end of file diff --git a/src/Common/src/Common.Mvc/Conventions/ActionMethodResolver.cs b/src/Common/src/Common.Mvc/Conventions/ActionMethodResolver.cs index 8feb6bfd..1f19c705 100644 --- a/src/Common/src/Common.Mvc/Conventions/ActionMethodResolver.cs +++ b/src/Common/src/Common.Mvc/Conventions/ActionMethodResolver.cs @@ -33,7 +33,7 @@ internal static MethodInfo Resolve( } argumentTypes ??= Type.EmptyTypes; - methods = methods.Where( m => SignatureMatches( m, argumentTypes ) ).ToArray(); + methods = [.. methods.Where( m => SignatureMatches( m, argumentTypes ) )]; if ( methods.Length == 1 ) { diff --git a/src/Common/src/Common.Mvc/Conventions/ControllerConventionBuilderExtensions.cs b/src/Common/src/Common.Mvc/Conventions/ControllerConventionBuilderExtensions.cs index 47e7a491..bc65d7e7 100644 --- a/src/Common/src/Common.Mvc/Conventions/ControllerConventionBuilderExtensions.cs +++ b/src/Common/src/Common.Mvc/Conventions/ControllerConventionBuilderExtensions.cs @@ -19,67 +19,62 @@ namespace Asp.Versioning.Conventions; #endif public static class ControllerConventionBuilderExtensions { - /// - /// Gets or creates the convention builder for the specified controller action method. - /// /// The type of controller. /// The extended . - /// The expression representing the controller action method. - /// A new or existing . - public static IActionConventionBuilder Action( - this IControllerConventionBuilder builder, - Expression> actionExpression ) + extension( IControllerConventionBuilder builder ) #if NETFRAMEWORK where TController : notnull, IHttpController #else where TController : notnull #endif { - ArgumentNullException.ThrowIfNull( builder ); - ArgumentNullException.ThrowIfNull( actionExpression ); - return builder.Action( actionExpression.ExtractMethod() ); - } + /// + /// Gets or creates the convention builder for the specified controller action method. + /// + /// The expression representing the controller action method. + /// A new or existing . + public IActionConventionBuilder Action( Expression> actionExpression ) + { + ArgumentNullException.ThrowIfNull( builder ); + ArgumentNullException.ThrowIfNull( actionExpression ); + return builder.Action( actionExpression.ExtractMethod() ); + } - /// - /// Gets or creates the convention builder for the specified controller action method. - /// - /// The type of controller. - /// The type of action result. - /// The extended . - /// The expression representing the controller action method. - /// A new or existing . - public static IActionConventionBuilder Action( - this IControllerConventionBuilder builder, - Expression> actionExpression ) -#if NETFRAMEWORK - where TController : notnull, IHttpController -#else - where TController : notnull -#endif - { - ArgumentNullException.ThrowIfNull( builder ); - ArgumentNullException.ThrowIfNull( actionExpression ); - return builder.Action( actionExpression.ExtractMethod() ); + /// + /// Gets or creates the convention builder for the specified controller action method. + /// + /// The type of action result. + /// The expression representing the controller action method. + /// A new or existing . + public IActionConventionBuilder Action( Expression> actionExpression ) + { + ArgumentNullException.ThrowIfNull( builder ); + ArgumentNullException.ThrowIfNull( actionExpression ); + return builder.Action( actionExpression.ExtractMethod() ); + } } - /// - /// Gets or creates the convention builder for the specified controller action method. - /// /// The extended . - /// The name of the action method. - /// The optional array of action method argument types. - /// A new or existing . - /// The specified method name must refer to a public, non-static action method. - /// If there is only one corresponding match found, then the argument types are ignored; - /// otherwise, the argument types are used for method overload resolution. Action - /// methods that have the applied will also be ignored. + extension( IControllerConventionBuilder builder ) + { + /// + /// Gets or creates the convention builder for the specified controller action method. + /// + /// The name of the action method. + /// The optional array of action method argument types. + /// A new or existing . + /// The specified method name must refer to a public, non-static action method. + /// If there is only one corresponding match found, then the argument types are ignored; + /// otherwise, the argument types are used for method overload resolution. Action + /// methods that have the applied will also be ignored. #if !NETFRAMEWORK - [UnconditionalSuppressMessage( "ILLink", "IL2072", Justification = "Controller types are never trimmed" )] + [UnconditionalSuppressMessage( "ILLink", "IL2072", Justification = "Controller types are never trimmed" )] #endif - public static IActionConventionBuilder Action( this IControllerConventionBuilder builder, string methodName, params Type[] argumentTypes ) - { - ArgumentNullException.ThrowIfNull( builder ); - var method = ActionMethodResolver.Resolve( builder.ControllerType, methodName, argumentTypes ); - return builder.Action( method ); + public IActionConventionBuilder Action( string methodName, params Type[] argumentTypes ) + { + ArgumentNullException.ThrowIfNull( builder ); + var method = ActionMethodResolver.Resolve( builder.ControllerType, methodName, argumentTypes ); + return builder.Action( method ); + } } } \ No newline at end of file diff --git a/src/Common/src/Common.Mvc/Conventions/ExpressionExtensions.cs b/src/Common/src/Common.Mvc/Conventions/ExpressionExtensions.cs index 5f10baa1..a70c8253 100644 --- a/src/Common/src/Common.Mvc/Conventions/ExpressionExtensions.cs +++ b/src/Common/src/Common.Mvc/Conventions/ExpressionExtensions.cs @@ -8,14 +8,17 @@ namespace Asp.Versioning.Conventions; internal static class ExpressionExtensions { - internal static MethodInfo ExtractMethod( this Expression expression ) + extension( Expression expression ) { - if ( expression.Body is MethodCallExpression methodCall ) + internal MethodInfo ExtractMethod() { - return methodCall.Method; - } + if ( expression.Body is MethodCallExpression methodCall ) + { + return methodCall.Method; + } - var message = string.Format( CultureInfo.CurrentCulture, MvcFormat.InvalidActionMethodExpression, expression ); - throw new InvalidOperationException( message ); + var message = string.Format( CultureInfo.CurrentCulture, MvcFormat.InvalidActionMethodExpression, expression ); + throw new InvalidOperationException( message ); + } } } \ No newline at end of file diff --git a/src/Common/src/Common.Mvc/Conventions/IControllerConvention.cs b/src/Common/src/Common.Mvc/Conventions/IControllerConvention.cs index ee0ca9bf..cc761837 100644 --- a/src/Common/src/Common.Mvc/Conventions/IControllerConvention.cs +++ b/src/Common/src/Common.Mvc/Conventions/IControllerConvention.cs @@ -13,7 +13,7 @@ namespace Asp.Versioning.Conventions; /// Defines the behavior of a controller convention. /// #if !NETFRAMEWORK -[CLSCompliant(false)] +[CLSCompliant( false )] #endif public interface IControllerConvention { diff --git a/src/Common/src/Common.OData.ApiExplorer/CollectionExtensions.cs b/src/Common/src/Common.OData.ApiExplorer/CollectionExtensions.cs index e086c401..82878f4b 100644 --- a/src/Common/src/Common.OData.ApiExplorer/CollectionExtensions.cs +++ b/src/Common/src/Common.OData.ApiExplorer/CollectionExtensions.cs @@ -4,31 +4,34 @@ namespace System.Collections.Generic; internal static class CollectionExtensions { - internal static void AddRange( this ICollection collection, IEnumerable items ) + extension( ICollection collection ) { - switch ( items ) + internal void AddRange( IEnumerable items ) { - case IList list: - for ( var i = 0; i < list.Count; i++ ) - { - collection.Add( list[i] ); - } + switch ( items ) + { + case IList list: + for ( var i = 0; i < list.Count; i++ ) + { + collection.Add( list[i] ); + } - break; - case IReadOnlyList list: - for ( var i = 0; i < list.Count; i++ ) - { - collection.Add( list[i] ); - } + break; + case IReadOnlyList list: + for ( var i = 0; i < list.Count; i++ ) + { + collection.Add( list[i] ); + } - break; - default: - foreach ( var item in items ) - { - collection.Add( item ); - } + break; + default: + foreach ( var item in items ) + { + collection.Add( item ); + } - break; + break; + } } } } \ No newline at end of file diff --git a/src/Common/src/Common.OData.ApiExplorer/Conventions/DefaultODataQueryOptionDescriptionProvider.cs b/src/Common/src/Common.OData.ApiExplorer/Conventions/DefaultODataQueryOptionDescriptionProvider.cs index d13b6d75..4f0c3220 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Conventions/DefaultODataQueryOptionDescriptionProvider.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Conventions/DefaultODataQueryOptionDescriptionProvider.cs @@ -206,7 +206,7 @@ protected virtual string DescribeTop( ODataQueryOptionDescriptionContext context { ArgumentNullException.ThrowIfNull( context ); - if ( context.MaxTop.NoLimitOrNone() ) + if ( context.MaxTop.NoLimitOrNone ) { return ODataExpSR.TopQueryOptionDesc; } @@ -227,7 +227,7 @@ protected virtual string DescribeSkip( ODataQueryOptionDescriptionContext contex { ArgumentNullException.ThrowIfNull( context ); - if ( context.MaxSkip.NoLimitOrNone() ) + if ( context.MaxSkip.NoLimitOrNone ) { return ODataExpSR.SkipQueryOptionDesc; } diff --git a/src/Common/src/Common.OData.ApiExplorer/Conventions/IODataQueryOptionsConvention.cs b/src/Common/src/Common.OData.ApiExplorer/Conventions/IODataQueryOptionsConvention.cs index 2e6a2ec7..351a944a 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Conventions/IODataQueryOptionsConvention.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Conventions/IODataQueryOptionsConvention.cs @@ -20,5 +20,8 @@ public interface IODataQueryOptionsConvention /// Applies the convention to the specified API description. /// /// The API description to apply the convention to. +#if !NETFRAMEWORK + [RequiresUnreferencedCode( "MVC does not currently support trimming or native AOT. https://aka.ms/aspnet/trimming" )] +#endif void ApplyTo( ApiDescription apiDescription ); } \ No newline at end of file diff --git a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataActionQueryOptionsConventionBuilderExtensions.cs b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataActionQueryOptionsConventionBuilderExtensions.cs index 33ab2471..3ec0d287 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataActionQueryOptionsConventionBuilderExtensions.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataActionQueryOptionsConventionBuilderExtensions.cs @@ -27,203 +27,169 @@ namespace Asp.Versioning.Conventions; #endif public static class ODataActionQueryOptionsConventionBuilderExtensions { - /// - /// Allows the $orderby query option. - /// /// The extended convention builder. - /// The maximum number of expressions in the $orderby query option or zero to indicate the default. - /// The array of property names that can appear in the $orderby query option. - /// An empty array indicates that any property can appear in the $orderby query option. /// The original . - public static ODataActionQueryOptionsConventionBuilder AllowOrderBy( - this ODataActionQueryOptionsConventionBuilder builder, - int maxNodeCount, - params string[] properties ) + extension( ODataActionQueryOptionsConventionBuilder builder ) { - ArgumentNullException.ThrowIfNull( builder ); - return builder.AllowOrderBy( maxNodeCount, properties.AsEnumerable() ); - } + /// + /// Allows the $orderby query option. + /// + /// The maximum number of expressions in the $orderby query option or zero to indicate the default. + /// The array of property names that can appear in the $orderby query option. + /// An empty array indicates that any property can appear in the $orderby query option. + public ODataActionQueryOptionsConventionBuilder AllowOrderBy( int maxNodeCount, params string[] properties ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.AllowOrderBy( maxNodeCount, properties.AsEnumerable() ); + } - /// - /// Allows the $orderby query option. - /// - /// The extended convention builder. - /// The sequence of property names that can appear in the $orderby query option. - /// An empty sequence indicates that any property can appear in the $orderby query option. - /// The original . - public static ODataActionQueryOptionsConventionBuilder AllowOrderBy( - this ODataActionQueryOptionsConventionBuilder builder, - IEnumerable properties ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.AllowOrderBy( default, properties ); - } + /// + /// Allows the $orderby query option. + /// + /// The sequence of property names that can appear in the $orderby query option. + /// An empty sequence indicates that any property can appear in the $orderby query option. + public ODataActionQueryOptionsConventionBuilder AllowOrderBy( IEnumerable properties ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.AllowOrderBy( default, properties ); + } - /// - /// Allows the $orderby query option. - /// - /// The extended convention builder. - /// The array of property names that can appear in the $orderby query option. - /// An empty array indicates that any property can appear in the $orderby query option. - /// The original . - public static ODataActionQueryOptionsConventionBuilder AllowOrderBy( - this ODataActionQueryOptionsConventionBuilder builder, - params string[] properties ) - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.AllowOrderBy( default, properties.AsEnumerable() ); + /// + /// Allows the $orderby query option. + /// + /// The array of property names that can appear in the $orderby query option. + /// An empty array indicates that any property can appear in the $orderby query option. + public ODataActionQueryOptionsConventionBuilder AllowOrderBy( params string[] properties ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.AllowOrderBy( default, properties.AsEnumerable() ); + } } - /// - /// Allows the $orderby query option. - /// /// The type of controller. /// The extended convention builder. - /// The maximum number of expressions in the $orderby query option or zero to indicate the default. - /// The array of property names that can appear in the $orderby query option. - /// An empty array indicates that any property can appear in the $orderby query option. - /// The original . - public static ODataActionQueryOptionsConventionBuilder AllowOrderBy( - this ODataActionQueryOptionsConventionBuilder builder, - int maxNodeCount, - params string[] properties ) + extension( ODataActionQueryOptionsConventionBuilder builder ) where T : notnull #if NETFRAMEWORK - , IHttpController + , IHttpController #endif { - ArgumentNullException.ThrowIfNull( builder ); - return builder.AllowOrderBy( maxNodeCount, properties.AsEnumerable() ); - } + /// + /// Allows the $orderby query option. + /// + /// The maximum number of expressions in the $orderby query option or zero to indicate the default. + /// The array of property names that can appear in the $orderby query option. + /// An empty array indicates that any property can appear in the $orderby query option. + public ODataActionQueryOptionsConventionBuilder AllowOrderBy( int maxNodeCount, params string[] properties ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.AllowOrderBy( maxNodeCount, properties.AsEnumerable() ); + } - /// - /// Allows the $orderby query option. - /// - /// The type of controller. - /// The extended convention builder. - /// The sequence of property names that can appear in the $orderby query option. - /// An empty sequence indicates that any property can appear in the $orderby query option. - /// The original . - public static ODataActionQueryOptionsConventionBuilder AllowOrderBy( - this ODataActionQueryOptionsConventionBuilder builder, - IEnumerable properties ) - where T : notnull -#if NETFRAMEWORK - , IHttpController -#endif - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.AllowOrderBy( default, properties ); - } + /// + /// Allows the $orderby query option. + /// + /// The sequence of property names that can appear in the $orderby query option. + /// An empty sequence indicates that any property can appear in the $orderby query option. + public ODataActionQueryOptionsConventionBuilder AllowOrderBy( IEnumerable properties ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.AllowOrderBy( default, properties ); + } - /// - /// Allows the $orderby query option. - /// - /// The type of controller. - /// The extended convention builder. - /// The array of property names that can appear in the $orderby query option. - /// An empty array indicates that any property can appear in the $orderby query option. - /// The original . - public static ODataActionQueryOptionsConventionBuilder AllowOrderBy( - this ODataActionQueryOptionsConventionBuilder builder, - params string[] properties ) - where T : notnull -#if NETFRAMEWORK - , IHttpController -#endif - { - ArgumentNullException.ThrowIfNull( builder ); - return builder.AllowOrderBy( default, properties.AsEnumerable() ); + /// + /// Allows the $orderby query option. + /// + /// The array of property names that can appear in the $orderby query option. + /// An empty array indicates that any property can appear in the $orderby query option. + public ODataActionQueryOptionsConventionBuilder AllowOrderBy( params string[] properties ) + { + ArgumentNullException.ThrowIfNull( builder ); + return builder.AllowOrderBy( default, properties.AsEnumerable() ); + } } - /// - /// Gets or creates the convention builder for the specified controller action method. - /// /// The type of controller. /// The extended . - /// The expression representing the controller action method. - /// A new or existing . - public static ODataActionQueryOptionsConventionBuilder Action( - this IODataActionQueryOptionsConventionBuilder builder, - Expression> actionExpression ) + extension( IODataActionQueryOptionsConventionBuilder builder ) where TController : notnull #if NETFRAMEWORK , IHttpController #endif { - ArgumentNullException.ThrowIfNull( builder ); - ArgumentNullException.ThrowIfNull( actionExpression ); - return builder.Action( actionExpression.ExtractMethod() ); - } + /// + /// Gets or creates the convention builder for the specified controller action method. + /// + /// The expression representing the controller action method. + /// A new or existing . + public ODataActionQueryOptionsConventionBuilder Action( Expression> actionExpression ) + { + ArgumentNullException.ThrowIfNull( builder ); + ArgumentNullException.ThrowIfNull( actionExpression ); + return builder.Action( actionExpression.ExtractMethod() ); + } - /// - /// Gets or creates the convention builder for the specified controller action method. - /// - /// The type of controller. - /// The type of action result. - /// The extended . - /// The expression representing the controller action method. - /// A new or existing . - public static ODataActionQueryOptionsConventionBuilder Action( - this IODataActionQueryOptionsConventionBuilder builder, - Expression> actionExpression ) - where TController : notnull -#if NETFRAMEWORK - , IHttpController -#endif - { - ArgumentNullException.ThrowIfNull( builder ); - ArgumentNullException.ThrowIfNull( actionExpression ); - return builder.Action( actionExpression.ExtractMethod() ); + /// + /// Gets or creates the convention builder for the specified controller action method. + /// + /// The type of action result. + /// The expression representing the controller action method. + /// A new or existing . + public ODataActionQueryOptionsConventionBuilder Action( Expression> actionExpression ) + { + ArgumentNullException.ThrowIfNull( builder ); + ArgumentNullException.ThrowIfNull( actionExpression ); + return builder.Action( actionExpression.ExtractMethod() ); + } } - /// - /// Gets or creates the convention builder for the specified controller action method. - /// /// The extended convention builder. - /// The name of the action method. - /// The optional array of action method argument types. /// The original . - /// The specified method name must refer to a public, non-static action method. - /// If there is only one corresponding match found, then the argument types are ignored; - /// otherwise, the argument types are used for method overload resolution. Action - /// methods that have the applied will also be ignored. + extension( IODataActionQueryOptionsConventionBuilder builder ) + { + /// + /// Gets or creates the convention builder for the specified controller action method. + /// + /// The name of the action method. + /// The optional array of action method argument types. + /// The specified method name must refer to a public, non-action method. + /// If there is only one corresponding match found, then the argument types are ignored; + /// otherwise, the argument types are used for method overload resolution. Action + /// methods that have the applied will also be ignored. #if !NETFRAMEWORK - [UnconditionalSuppressMessage( "ILLink", "IL2075", Justification = "Controller types and actions are never trimmed" )] + [UnconditionalSuppressMessage( "ILLink", "IL2075", Justification = "Controller types and actions are never trimmed" )] #endif - public static ODataActionQueryOptionsConventionBuilder Action( - this IODataActionQueryOptionsConventionBuilder builder, - string methodName, - params Type[] argumentTypes ) - { - ArgumentNullException.ThrowIfNull( builder ); - ArgumentNullException.ThrowIfNull( argumentTypes ); + public ODataActionQueryOptionsConventionBuilder Action( string methodName, params Type[] argumentTypes ) + { + ArgumentNullException.ThrowIfNull( builder ); + ArgumentNullException.ThrowIfNull( argumentTypes ); - string message; - var methods = builder.ControllerType - .GetMethods( Instance | Public ) - .Where( m => m.Name == methodName && IsAction( m ) ) - .ToArray(); + string message; + var methods = builder.ControllerType + .GetMethods( Instance | Public ) + .Where( m => m.Name == methodName && IsAction( m ) ) + .ToArray(); - switch ( methods.Length ) - { - case 0: - message = string.Format( CultureInfo.CurrentCulture, Format.ActionMethodNotFound, methodName ); - throw new MissingMethodException( message ); - case 1: - return builder.Action( methods[0] ); - } + switch ( methods.Length ) + { + case 0: + message = string.Format( CultureInfo.CurrentCulture, Format.ActionMethodNotFound, methodName ); + throw new MissingMethodException( message ); + case 1: + return builder.Action( methods[0] ); + } - // perf: stop if there are 2+ matches; it's ambiguous - methods = methods.Where( m => SignatureMatches( m, argumentTypes ) ).Take( 2 ).ToArray(); + // perf: stop if there are 2+ matches; it's ambiguous + methods = [.. methods.Where( m => SignatureMatches( m, argumentTypes ) ).Take( 2 )]; - if ( methods.Length == 1 ) - { - return builder.Action( methods[0] ); - } + if ( methods.Length == 1 ) + { + return builder.Action( methods[0] ); + } - message = string.Format( CultureInfo.CurrentCulture, Format.AmbiguousActionMethod, methodName ); - throw new AmbiguousMatchException( message ); + message = string.Format( CultureInfo.CurrentCulture, Format.AmbiguousActionMethod, methodName ); + throw new AmbiguousMatchException( message ); + } } private static bool IsAction( MethodInfo method ) diff --git a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataAttributeVisitor.cs b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataAttributeVisitor.cs index fd973fe6..dcff34ad 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataAttributeVisitor.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataAttributeVisitor.cs @@ -220,7 +220,7 @@ private void VisitCount( ModelBoundQuerySettings querySettings ) private void VisitMaxTop( ModelBoundQuerySettings querySettings ) { - if ( querySettings.MaxTop.Unset() ) + if ( querySettings.MaxTop.Unset ) { return; } diff --git a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataControllerQueryOptionConvention.cs b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataControllerQueryOptionConvention.cs index 40077d14..9a921a9c 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataControllerQueryOptionConvention.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataControllerQueryOptionConvention.cs @@ -26,6 +26,9 @@ internal ODataControllerQueryOptionConvention( this.settings = settings; } +#if !NETFRAMEWORK + [RequiresUnreferencedCode( "MVC does not currently support trimming or native AOT. https://aka.ms/aspnet/trimming" )] +#endif public void ApplyTo( ApiDescription apiDescription ) { if ( apiDescription.ActionDescriptor is not ControllerActionDescriptor action ) diff --git a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs index 72556399..a95fdbb1 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataQueryOptionSettings.cs @@ -2,12 +2,6 @@ namespace Asp.Versioning.Conventions; -#if NETFRAMEWORK -using Microsoft.AspNet.OData.Query; -#else -using Microsoft.OData.ModelBuilder.Config; -#endif - /// /// Represents the settings for OData query options. /// @@ -22,12 +16,6 @@ public partial class ODataQueryOptionSettings /// value is false. public bool NoDollarPrefix { get; set; } - /// - /// Gets or sets the default OData query settings. - /// - /// The default OData query settings. - public DefaultQuerySettings? DefaultQuerySettings { get; set; } - /// /// Gets or sets the provider used to describe query options. /// diff --git a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs index 8d0921f9..56d2da99 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs @@ -121,6 +121,9 @@ public virtual ODataControllerQueryOptionsConventionBuilder Controller( Type con /// The sequence of API descriptions /// to apply configured conventions to. /// The settings used to apply OData query option conventions. +#if !NETFRAMEWORK + [RequiresUnreferencedCode( "MVC does not currently support trimming or native AOT. https://aka.ms/aspnet/trimming" )] +#endif public virtual void ApplyTo( IEnumerable apiDescriptions, ODataQueryOptionSettings queryOptionSettings ) { ArgumentNullException.ThrowIfNull( apiDescriptions ); @@ -131,7 +134,7 @@ public virtual void ApplyTo( IEnumerable apiDescriptions, ODataQ { var controller = GetController( description ); - if ( !controller.IsODataController() && !description.IsODataLike() ) + if ( !controller.IsODataController && !description.IsODataLike ) { continue; } diff --git a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs index 686a1953..da0bc71c 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs @@ -176,7 +176,7 @@ private AllowedQueryOptions GetQueryOptions( DefaultQuerySettings settings, ODat queryOptions |= Select; } - if ( settings.MaxTop.NoLimitOrSome() ) + if ( settings.MaxTop.NoLimitOrSome ) { context.MaxTop = settings.MaxTop; } diff --git a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataValidationSettingsExtensions.cs b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataValidationSettingsExtensions.cs index cd56effc..6ef9d77a 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataValidationSettingsExtensions.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Conventions/ODataValidationSettingsExtensions.cs @@ -10,41 +10,44 @@ namespace Asp.Versioning.Conventions; internal static class ODataValidationSettingsExtensions { - internal static void CopyFrom( this ODataValidationSettings original, ODataValidationSettings source ) + extension( ODataValidationSettings original ) { - original.AllowedArithmeticOperators = source.AllowedArithmeticOperators; - original.AllowedFunctions = source.AllowedFunctions; - original.AllowedLogicalOperators = source.AllowedLogicalOperators; - original.AllowedQueryOptions = source.AllowedQueryOptions; - original.MaxAnyAllExpressionDepth = source.MaxAnyAllExpressionDepth; - original.MaxExpansionDepth = source.MaxExpansionDepth; - original.MaxNodeCount = source.MaxNodeCount; - original.MaxOrderByNodeCount = source.MaxOrderByNodeCount; - - if ( source.MaxSkip.NoLimitOrNone() ) + internal void CopyFrom( ODataValidationSettings source ) { - original.MaxSkip = source.MaxSkip; - } + original.AllowedArithmeticOperators = source.AllowedArithmeticOperators; + original.AllowedFunctions = source.AllowedFunctions; + original.AllowedLogicalOperators = source.AllowedLogicalOperators; + original.AllowedQueryOptions = source.AllowedQueryOptions; + original.MaxAnyAllExpressionDepth = source.MaxAnyAllExpressionDepth; + original.MaxExpansionDepth = source.MaxExpansionDepth; + original.MaxNodeCount = source.MaxNodeCount; + original.MaxOrderByNodeCount = source.MaxOrderByNodeCount; - if ( source.MaxTop.NoLimitOrSome() ) - { - original.MaxTop = source.MaxTop; - } + if ( source.MaxSkip.NoLimitOrNone ) + { + original.MaxSkip = source.MaxSkip; + } + + if ( source.MaxTop.NoLimitOrSome ) + { + original.MaxTop = source.MaxTop; + } - var originalAllowedOrderByProperties = original.AllowedOrderByProperties; - var sourceAllowedOrderByProperties = source.AllowedOrderByProperties; + var originalAllowedOrderByProperties = original.AllowedOrderByProperties; + var sourceAllowedOrderByProperties = source.AllowedOrderByProperties; - originalAllowedOrderByProperties.Clear(); + originalAllowedOrderByProperties.Clear(); #if NETFRAMEWORK - for ( var i = 0; i < sourceAllowedOrderByProperties.Count; i++ ) - { - originalAllowedOrderByProperties.Add( sourceAllowedOrderByProperties[i] ); - } + for ( var i = 0; i < sourceAllowedOrderByProperties.Count; i++ ) + { + originalAllowedOrderByProperties.Add( sourceAllowedOrderByProperties[i] ); + } #else - foreach ( var property in sourceAllowedOrderByProperties ) - { - originalAllowedOrderByProperties.Add( property ); - } + foreach ( var property in sourceAllowedOrderByProperties ) + { + originalAllowedOrderByProperties.Add( property ); + } #endif + } } } \ No newline at end of file diff --git a/src/Common/src/Common.OData.ApiExplorer/Microsoft.OData.Edm/EdmExtensions.cs b/src/Common/src/Common.OData.ApiExplorer/Microsoft.OData.Edm/EdmExtensions.cs index f9c60670..cc9e7013 100644 --- a/src/Common/src/Common.OData.ApiExplorer/Microsoft.OData.Edm/EdmExtensions.cs +++ b/src/Common/src/Common.OData.ApiExplorer/Microsoft.OData.Edm/EdmExtensions.cs @@ -7,61 +7,62 @@ namespace Microsoft.OData.Edm; #else using Microsoft.OData.ModelBuilder; #endif -using System.Runtime.CompilerServices; internal static class EdmExtensions { - private const bool ThrowOnError = true; - - internal static Type? GetClrType( this IEdmType edmType, IEdmModel edmModel ) + extension( IEdmType edmType ) { - if ( edmType is not IEdmSchemaType schemaType ) + internal Type? GetClrType( IEdmModel edmModel ) { - return null; - } + if ( edmType is not IEdmSchemaType schemaType ) + { + return null; + } - var typeName = schemaType.FullName(); + var typeName = schemaType.FullName(); - if ( DeriveFromWellKnowPrimitive( typeName ) is Type type ) - { - return type; - } + if ( DeriveFromWellKnowPrimitive( typeName ) is Type type ) + { + return type; + } - var annotationValue = edmModel.GetAnnotationValue( schemaType ); + var annotationValue = edmModel.GetAnnotationValue( schemaType ); - if ( annotationValue != null ) - { - return annotationValue.ClrType; - } + if ( annotationValue != null ) + { + return annotationValue.ClrType; + } - return null; + return null; + } } -#if !NETFRAMEWORK - [UnconditionalSuppressMessage( "ILLink", "IL2057", Justification = "The types being referenced are well-known and will not be trimmed." )] -#endif private static Type? DeriveFromWellKnowPrimitive( string edmFullName ) => edmFullName switch { - "Edm.String" or "Edm.Byte" or "Edm.SByte" or "Edm.Int16" or "Edm.Int32" or "Edm.Int64" or - "Edm.Double" or "Edm.Single" or "Edm.Boolean" or "Edm.Decimal" or "Edm.DateTime" or "Edm.DateTimeOffset" or - "Edm.Guid" => Type.GetType( Requalify( edmFullName, "System" ), ThrowOnError ), + "Edm.String" => typeof( string ), + "Edm.Byte" => typeof( byte ), + "Edm.SByte" => typeof( sbyte ), + "Edm.Int32" => typeof( int ), + "Edm.Int64" => typeof( long ), + "Edm.Int16" => typeof( short ), + "Edm.Double" => typeof( double ), + "Edm.Single" => typeof( float ), + "Edm.Boolean" => typeof( bool ), + "Edm.Decimal" => typeof( decimal ), + "Edm.DateTime" => typeof( DateTime ), + "Edm.DateTimeOffset" => typeof( DateTimeOffset ), + "Edm.Guid" => typeof( Guid ), "Edm.Duration" => typeof( TimeSpan ), "Edm.Binary" => typeof( byte[] ), - "Edm.Geography" or "Edm.Geometry" => GetTypeFromAssembly( edmFullName, "Microsoft.Spatial" ), - "Edm.Date" or "Edm.TimeOfDay" => GetTypeFromAssembly( edmFullName, "Microsoft.OData.Edm" ), - _ => null, - }; - - [MethodImpl( MethodImplOptions.AggressiveInlining )] + "Edm.Geography" => typeof( Spatial.Geography ), + "Edm.Geometry" => typeof( Spatial.Geometry ), #if NETFRAMEWORK - private static string Requalify( string edmFullName, string @namespace ) => @namespace + edmFullName.Substring( 3 ); + "Edm.Date" => typeof( Date ), + "Edm.TimeOfDay" => typeof( TimeOfDay ), #else - private static string Requalify( string edmFullName, string @namespace ) => string.Concat( @namespace.AsSpan(), edmFullName.AsSpan()[3..] ); + "Edm.Date" => typeof( DateOnly ), + "Edm.TimeOfDay" => typeof( TimeSpan ), #endif - - private static Type? GetTypeFromAssembly( string edmFullName, string assemblyName ) - { - var typeName = Requalify( edmFullName, assemblyName ) + "," + assemblyName; - return Type.GetType( typeName, ThrowOnError ); - } + _ => default, + }; } \ No newline at end of file diff --git a/src/Common/src/Common.OData.ApiExplorer/NullableExtensions.cs b/src/Common/src/Common.OData.ApiExplorer/NullableExtensions.cs index 588a11a0..1037e62a 100644 --- a/src/Common/src/Common.OData.ApiExplorer/NullableExtensions.cs +++ b/src/Common/src/Common.OData.ApiExplorer/NullableExtensions.cs @@ -2,16 +2,14 @@ namespace System; -using System.Runtime.CompilerServices; - internal static class NullableExtensions { - [MethodImpl( MethodImplOptions.AggressiveInlining )] - public static bool Unset( this int? value ) => value.HasValue && value.Value == 0; + extension( int? value ) + { + public bool Unset => value.HasValue && value.Value == 0; - [MethodImpl( MethodImplOptions.AggressiveInlining )] - public static bool NoLimitOrSome( this int? value ) => !value.HasValue || value.Value > 0; + public bool NoLimitOrSome => !value.HasValue || value.Value > 0; - [MethodImpl( MethodImplOptions.AggressiveInlining )] - public static bool NoLimitOrNone( this int? value ) => !value.HasValue || value.Value <= 0; + public bool NoLimitOrNone => !value.HasValue || value.Value <= 0; + } } \ No newline at end of file diff --git a/src/Common/src/Common.OData.ApiExplorer/OData/ClassProperty.cs b/src/Common/src/Common.OData.ApiExplorer/OData/ClassProperty.cs index f5959f92..7d818bca 100644 --- a/src/Common/src/Common.OData.ApiExplorer/OData/ClassProperty.cs +++ b/src/Common/src/Common.OData.ApiExplorer/OData/ClassProperty.cs @@ -18,7 +18,7 @@ internal ClassProperty( PropertyInfo clrProperty, Type propertyType ) { Name = clrProperty.Name; Type = propertyType; - Attributes = clrProperty.DeclaredAttributes().ToArray(); + Attributes = [.. clrProperty.DeclaredAttributes]; } #if !NETFRAMEWORK @@ -43,7 +43,7 @@ internal ClassProperty( IEdmOperationParameter parameter, TypeSubstitutionContex Type = parameterType.SubstituteIfNecessary( context ); } - Attributes = AttributesFromOperationParameter( parameter ).ToArray(); + Attributes = [.. AttributesFromOperationParameter( parameter )]; } internal IReadOnlyList Attributes { get; } diff --git a/src/Common/src/Common.OData.ApiExplorer/OData/ClassSignature.cs b/src/Common/src/Common.OData.ApiExplorer/OData/ClassSignature.cs index 5dc090ea..eb4ae575 100644 --- a/src/Common/src/Common.OData.ApiExplorer/OData/ClassSignature.cs +++ b/src/Common/src/Common.OData.ApiExplorer/OData/ClassSignature.cs @@ -16,22 +16,22 @@ internal ClassSignature( string name, Type originalType, IEnumerable() { - new( newOriginalType, new[] { originalType } ), + new( newOriginalType, [originalType] ), }; - attributeBuilders.AddRange( originalType.DeclaredAttributes() ); + attributeBuilders.AddRange( originalType.DeclaredAttributes ); Name = name; - Attributes = attributeBuilders.ToArray(); - Properties = properties.ToArray(); + Attributes = [.. attributeBuilders]; + Properties = [.. properties]; ApiVersion = apiVersion; } internal ClassSignature( string name, IEnumerable properties, ApiVersion apiVersion ) { Name = name; - Attributes = Array.Empty(); - Properties = properties.ToArray(); + Attributes = []; + Properties = [.. properties]; ApiVersion = apiVersion; } diff --git a/src/Common/src/Common.OData.ApiExplorer/OData/DefaultModelTypeBuilder.cs b/src/Common/src/Common.OData.ApiExplorer/OData/DefaultModelTypeBuilder.cs index 76fec0ff..005bdb87 100644 --- a/src/Common/src/Common.OData.ApiExplorer/OData/DefaultModelTypeBuilder.cs +++ b/src/Common/src/Common.OData.ApiExplorer/OData/DefaultModelTypeBuilder.cs @@ -26,9 +26,9 @@ namespace Asp.Versioning.OData; /// Represents the default model type builder. /// #if !NETFRAMEWORK -[UnconditionalSuppressMessage( "ILLink", "IL2055")] -[UnconditionalSuppressMessage( "ILLink", "IL2070")] -[UnconditionalSuppressMessage( "ILLink", "IL2073")] +[UnconditionalSuppressMessage( "ILLink", "IL2055" )] +[UnconditionalSuppressMessage( "ILLink", "IL2070" )] +[UnconditionalSuppressMessage( "ILLink", "IL2073" )] #endif public sealed class DefaultModelTypeBuilder : IModelTypeBuilder { @@ -81,7 +81,7 @@ public Type NewStructuredType( { ArgumentNullException.ThrowIfNull( model ); - if ( model.IsAdHoc() ) + if ( model.IsAdHoc ) { if ( excludeAdHocModels ) { @@ -113,7 +113,7 @@ public Type NewActionParameters( IEdmModel model, IEdmAction action, string cont { ArgumentNullException.ThrowIfNull( model ); - if ( !adHoc && model.IsAdHoc() ) + if ( !adHoc && model.IsAdHoc ) { adHocBuilder ??= new( excludeAdHocModels, adHoc: true ); return adHocBuilder.NewActionParameters( model, action, controllerName, apiVersion ); @@ -260,7 +260,7 @@ private static Tuple BuildSignatureProperties( { clrTypeMatchesEdmType = false; hasUnfinishedTypes = true; - dependentProperties.Add( new PropertyDependency( elementKey, true, property.Name, property.DeclaredAttributes() ) ); + dependentProperties.Add( new PropertyDependency( elementKey, true, property.Name, property.DeclaredAttributes ) ); continue; } @@ -293,7 +293,7 @@ private static Tuple BuildSignatureProperties( { clrTypeMatchesEdmType = false; hasUnfinishedTypes = true; - dependentProperties.Add( new PropertyDependency( propertyTypeKey, false, property.Name, property.DeclaredAttributes() ) ); + dependentProperties.Add( new PropertyDependency( propertyTypeKey, false, property.Name, property.DeclaredAttributes ) ); continue; } } diff --git a/src/Common/src/Common.OData.ApiExplorer/OData/PropertyDependency.cs b/src/Common/src/Common.OData.ApiExplorer/OData/PropertyDependency.cs index bf84b902..6702a7e1 100644 --- a/src/Common/src/Common.OData.ApiExplorer/OData/PropertyDependency.cs +++ b/src/Common/src/Common.OData.ApiExplorer/OData/PropertyDependency.cs @@ -14,7 +14,7 @@ internal PropertyDependency( { DependentOnTypeKey = dependentOnTypeKey; PropertyName = propertyName; - CustomAttributes = customAttributes.ToArray(); + CustomAttributes = [.. customAttributes]; IsCollection = isCollection; } diff --git a/src/Common/src/Common.OData.ApiExplorer/OData/TypeExtensions.cs b/src/Common/src/Common.OData.ApiExplorer/OData/TypeExtensions.cs index 72c76457..54411a60 100644 --- a/src/Common/src/Common.OData.ApiExplorer/OData/TypeExtensions.cs +++ b/src/Common/src/Common.OData.ApiExplorer/OData/TypeExtensions.cs @@ -13,7 +13,6 @@ namespace Asp.Versioning.OData; #endif using System.Reflection; using System.Reflection.Emit; -using System.Runtime.CompilerServices; #if NETFRAMEWORK using IActionResult = System.Web.Http.IHttpActionResult; #else @@ -36,131 +35,234 @@ public static partial class TypeExtensions private static readonly Type ActionResultOfT = typeof( ActionResult<> ); #endif - /// - /// Substitutes the specified type, if required. - /// /// The type to be evaluated. - /// The current type substitution context. - /// The original or a substitution type based on the - /// provided . -#if !NETFRAMEWORK - [UnconditionalSuppressMessage( "ILLink", "IL2026" )] - [UnconditionalSuppressMessage( "ILLink", "IL2073" )] - [return: DynamicallyAccessedMembers( Interfaces | PublicProperties )] -#endif - public static Type SubstituteIfNecessary( + extension( #if !NETFRAMEWORK [DynamicallyAccessedMembers( Interfaces | PublicProperties )] #endif - this Type type, - TypeSubstitutionContext context ) + Type type ) { - ArgumentNullException.ThrowIfNull( type ); - ArgumentNullException.ThrowIfNull( context ); + /// + /// Substitutes the specified type, if required. + /// + /// The current type substitution context. + /// The original type or a substitution type based on the + /// provided . +#if !NETFRAMEWORK + [UnconditionalSuppressMessage( "ILLink", "IL2026" )] + [UnconditionalSuppressMessage( "ILLink", "IL2073" )] + [return: DynamicallyAccessedMembers( Interfaces | PublicProperties )] +#endif + public Type SubstituteIfNecessary( TypeSubstitutionContext context ) + { + ArgumentNullException.ThrowIfNull( type ); + ArgumentNullException.ThrowIfNull( context ); - var openTypes = new Stack(); - var apiVersion = context.ApiVersion; - var resolver = new StructuredTypeResolver( context.Model ); - IEdmStructuredType? structuredType; + var openTypes = new Stack(); + var apiVersion = context.ApiVersion; + var resolver = new StructuredTypeResolver( context.Model ); + IEdmStructuredType? structuredType; - if ( IsSubstitutableGeneric( type, openTypes, out var innerType ) ) - { - if ( ( structuredType = resolver.GetStructuredType( innerType! ) ) == null ) + if ( IsSubstitutableGeneric( type, openTypes, out var innerType ) ) { - return type; - } + if ( ( structuredType = resolver.GetStructuredType( innerType! ) ) == null ) + { + return type; + } - var newType = context.ModelTypeBuilder.NewStructuredType( context.Model, structuredType, innerType!, apiVersion ); + var newType = context.ModelTypeBuilder.NewStructuredType( context.Model, structuredType, innerType!, apiVersion ); - if ( innerType!.Equals( newType ) ) - { - return type.ShouldExtractInnerType() ? innerType : type; + if ( innerType!.Equals( newType ) ) + { + return type.ShouldExtractInnerType ? innerType : type; + } + + return CloseGeneric( openTypes, newType ); } - return CloseGeneric( openTypes, newType ); - } + if ( type.CanBeSubstituted && ( structuredType = resolver.GetStructuredType( type ) ) != null ) + { + type = context.ModelTypeBuilder.NewStructuredType( context.Model, structuredType, type, apiVersion ); + } - if ( CanBeSubstituted( type ) && ( structuredType = resolver.GetStructuredType( type ) ) != null ) - { - type = context.ModelTypeBuilder.NewStructuredType( context.Model, structuredType, type, apiVersion ); + return type; } - - return type; } - internal static IEnumerable DeclaredAttributes( this MemberInfo member ) + extension( Type type ) { - foreach ( var attribute in member.CustomAttributes ) + private bool Is( Type typeDefinition ) => type.IsGenericType && type.GetGenericTypeDefinition().Equals( typeDefinition ); + + private bool ShouldExtractInnerType => + type.IsDelta || +#if !NETFRAMEWORK + type.IsActionResult || +#endif + type.IsSingleResult; + + private bool CanBeSubstituted => + Type.GetTypeCode( type ) == TypeCode.Object && + !type.IsValueType && + !type.Equals( ActionResultType ) && +#if NETFRAMEWORK + !type.Equals( HttpResponseType ) && +#endif + !type.IsODataActionParameters; + + private bool IsSingleResult => type.Is( SingleResultOfT ); + +#if !NETFRAMEWORK + private bool IsActionResult => type.Is( ActionResultOfT ); +#endif + +#if !NETFRAMEWORK + [UnconditionalSuppressMessage( "ILLink", "IL2070" )] +#endif + internal bool IsEnumerable( [NotNullWhen( true )] out Type? itemType ) { - var ctor = attribute.Constructor; - var ctorArgs = attribute.ConstructorArguments.Select( a => a.Value ).ToArray(); - var namedProperties = new List( attribute.NamedArguments.Count ); - var propertyValues = new List( attribute.NamedArguments.Count ); - var namedFields = new List( attribute.NamedArguments.Count ); - var fieldValues = new List( attribute.NamedArguments.Count ); - - for ( var i = 0; i < attribute.NamedArguments.Count; i++ ) + var types = new Queue(); + + types.Enqueue( type ); + + while ( types.Count > 0 ) { - var argument = attribute.NamedArguments[i]; + type = types.Dequeue(); - if ( argument.IsField ) + if ( type.IsGenericType ) { - namedFields.Add( (FieldInfo) argument.MemberInfo ); - fieldValues.Add( argument.TypedValue.Value! ); + var typeDef = type.GetGenericTypeDefinition(); + + if ( typeDef.Equals( IEnumerableOfT ) +#if !NETFRAMEWORK + || typeDef.Equals( IAsyncEnumerableOfT ) +#endif + ) + { + itemType = type.GetGenericArguments()[0]; + return true; + } } - else + + var interfaces = type.GetInterfaces(); + + for ( var i = 0; i < interfaces.Length; i++ ) { - namedProperties.Add( (PropertyInfo) argument.MemberInfo ); - propertyValues.Add( argument.TypedValue.Value! ); + types.Enqueue( interfaces[i] ); } } - for ( var i = 0; i < ctorArgs.Length; i++ ) + itemType = default; + return false; + } + + internal Type ExtractInnerType() + { + if ( !type.IsGenericType ) { - if ( ctorArgs[i] is IReadOnlyCollection paramsList ) - { - ctorArgs[i] = paramsList.Select( a => a.Value ).ToArray(); - } + return type; } - yield return new CustomAttributeBuilder( - ctor, - ctorArgs, - [.. namedProperties], - [.. propertyValues], - [.. namedFields], - [.. fieldValues] ); + var typeDef = type.GetGenericTypeDefinition(); + var typeArgs = type.GetGenericArguments(); + + if ( typeArgs.Length != 1 ) + { + return type; + } + + var generic = typeDef.IsDelta || + typeDef.IsODataValue || +#if !NETFRAMEWORK + typeDef.IsActionResult || +#endif + typeDef.IsSingleResult; + + if ( generic ) + { + return typeArgs[0]; + } + + return type; } } - internal static Type ExtractInnerType( this Type type ) + extension( Type? type ) { - if ( !type.IsGenericType ) + private bool IsODataValue { - return type; - } + get + { + while ( type != null ) + { + if ( !type.IsGenericType ) + { + return false; + } - var typeDef = type.GetGenericTypeDefinition(); - var typeArgs = type.GetGenericArguments(); + var typeDef = type.GetGenericTypeDefinition(); - if ( typeArgs.Length != 1 ) - { - return type; - } + if ( typeDef.Equals( ODataValueOfT ) ) + { + return true; + } - var generic = typeDef.IsDelta() || - typeDef.IsODataValue() || -#if !NETFRAMEWORK - typeDef.IsActionResult() || -#endif - typeDef.IsSingleResult(); + type = type.BaseType; + } - if ( generic ) - { - return typeArgs[0]; + return false; + } } + } - return type; + extension( MemberInfo member ) + { + internal IEnumerable DeclaredAttributes + { + get + { + foreach ( var attribute in member.CustomAttributes ) + { + var ctor = attribute.Constructor; + var ctorArgs = attribute.ConstructorArguments.Select( a => a.Value ).ToArray(); + var namedProperties = new List( attribute.NamedArguments.Count ); + var propertyValues = new List( attribute.NamedArguments.Count ); + var namedFields = new List( attribute.NamedArguments.Count ); + var fieldValues = new List( attribute.NamedArguments.Count ); + + for ( var i = 0; i < attribute.NamedArguments.Count; i++ ) + { + var argument = attribute.NamedArguments[i]; + + if ( argument.IsField ) + { + namedFields.Add( (FieldInfo) argument.MemberInfo ); + fieldValues.Add( argument.TypedValue.Value! ); + } + else + { + namedProperties.Add( (PropertyInfo) argument.MemberInfo ); + propertyValues.Add( argument.TypedValue.Value! ); + } + } + + for ( var i = 0; i < ctorArgs.Length; i++ ) + { + if ( ctorArgs[i] is IReadOnlyCollection paramsList ) + { + ctorArgs[i] = paramsList.Select( a => a.Value ).ToArray(); + } + } + + yield return new CustomAttributeBuilder( + ctor, + ctorArgs, + [.. namedProperties], + [.. propertyValues], + [.. namedFields], + [.. fieldValues] ); + } + } + } } private static bool IsSubstitutableGeneric( @@ -190,13 +292,13 @@ private static bool IsSubstitutableGeneric( var typeArg = typeArgs[0]; var generic = typeDef.Equals( IEnumerableOfT ) || - typeDef.IsDelta() || - typeDef.IsODataValue() || + typeDef.IsDelta || + typeDef.IsODataValue || #if !NETFRAMEWORK typeDef.Equals( IAsyncEnumerableOfT ) || - typeDef.IsActionResult() || + typeDef.IsActionResult || #endif - typeDef.IsSingleResult(); + typeDef.IsSingleResult; if ( generic ) { @@ -238,7 +340,7 @@ private static Type CloseGeneric( Stack openTypes, Type innerType ) { var type = openTypes.Pop(); - if ( type.ShouldExtractInnerType() ) + if ( type.ShouldExtractInnerType ) { return innerType; } @@ -252,96 +354,4 @@ private static Type CloseGeneric( Stack openTypes, Type innerType ) return type; } - - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static bool CanBeSubstituted( Type type ) => - Type.GetTypeCode( type ) == TypeCode.Object && - !type.IsValueType && - !type.Equals( ActionResultType ) && -#if NETFRAMEWORK - !type.Equals( HttpResponseType ) && -#endif - !type.IsODataActionParameters(); - -#if !NETFRAMEWORK - [UnconditionalSuppressMessage( "ILLink", "IL2070" )] -#endif - internal static bool IsEnumerable(this Type type, [NotNullWhen( true )] out Type? itemType ) - { - var types = new Queue(); - - types.Enqueue( type ); - - while ( types.Count > 0 ) - { - type = types.Dequeue(); - - if ( type.IsGenericType ) - { - var typeDef = type.GetGenericTypeDefinition(); - - if ( typeDef.Equals( IEnumerableOfT ) -#if !NETFRAMEWORK - || typeDef.Equals( IAsyncEnumerableOfT ) -#endif - ) - { - itemType = type.GetGenericArguments()[0]; - return true; - } - } - - var interfaces = type.GetInterfaces(); - - for ( var i = 0; i < interfaces.Length; i++ ) - { - types.Enqueue( interfaces[i] ); - } - } - - itemType = default; - return false; - } - - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static bool IsSingleResult( this Type type ) => type.Is( SingleResultOfT ); - - private static bool IsODataValue( this Type? type ) - { - while ( type != null ) - { - if ( !type.IsGenericType ) - { - return false; - } - - var typeDef = type.GetGenericTypeDefinition(); - - if ( typeDef.Equals( ODataValueOfT ) ) - { - return true; - } - - type = type.BaseType; - } - - return false; - } - - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static bool Is( this Type type, Type typeDefinition ) => - type.IsGenericType && type.GetGenericTypeDefinition().Equals( typeDefinition ); - - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static bool ShouldExtractInnerType( this Type type ) => - type.IsDelta() || -#if !NETFRAMEWORK - type.IsActionResult() || -#endif - type.IsSingleResult(); - -#if !NETFRAMEWORK - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static bool IsActionResult( this Type type ) => type.Is( ActionResultOfT ); -#endif } \ No newline at end of file diff --git a/src/Common/src/Common.OData.ApiExplorer/OData/TypeSubstitutionContext.cs b/src/Common/src/Common.OData.ApiExplorer/OData/TypeSubstitutionContext.cs index b47ae724..8a2db867 100644 --- a/src/Common/src/Common.OData.ApiExplorer/OData/TypeSubstitutionContext.cs +++ b/src/Common/src/Common.OData.ApiExplorer/OData/TypeSubstitutionContext.cs @@ -36,7 +36,7 @@ public class TypeSubstitutionContext /// Gets API version associated with the source model. /// /// The associated API version. - public ApiVersion ApiVersion => apiVersion ??= Model.GetApiVersion() ?? ApiVersion.Neutral; + public ApiVersion ApiVersion => apiVersion ??= Model.ApiVersion ?? ApiVersion.Neutral; /// /// Gets the model type builder used to create substitution types. diff --git a/src/Common/src/Common.OData/Microsoft.OData.Edm/IEdmModelExtensions.cs b/src/Common/src/Common.OData/Microsoft.OData.Edm/IEdmModelExtensions.cs index 04d14dd6..50d18249 100644 --- a/src/Common/src/Common.OData/Microsoft.OData.Edm/IEdmModelExtensions.cs +++ b/src/Common/src/Common.OData/Microsoft.OData.Edm/IEdmModelExtensions.cs @@ -10,19 +10,19 @@ namespace Microsoft.OData.Edm; /// public static class IEdmModelExtensions { - /// - /// Gets the API version associated with the Entity Data Model (EDM). - /// /// The extended EDM. - /// The associated API version or null. - public static ApiVersion? GetApiVersion( this IEdmModel model ) => - model.GetAnnotationValue( model )?.ApiVersion; + extension( IEdmModel model ) + { + /// + /// Gets the API version associated with the Entity Data Model (EDM). + /// + /// The associated API version or null. + public ApiVersion? ApiVersion => model.GetAnnotationValue( model )?.ApiVersion; - /// - /// Gets a value indicating whether the Entity Data Model (EDM) is for defined ad hoc usage. - /// - /// The extended EDM. - /// True if the EDM is defined for ad hoc usage; otherwise, false. - public static bool IsAdHoc( this IEdmModel model ) => - model.GetAnnotationValue( model ) is not null; + /// + /// Gets a value indicating whether the Entity Data Model (EDM) is for defined ad hoc usage. + /// + /// True if the EDM is defined for ad hoc usage; otherwise, false. + public bool IsAdHoc => model.GetAnnotationValue( model ) is not null; + } } \ No newline at end of file diff --git a/src/Common/src/Common.OData/OData/VersionedODataModelBuilder.cs b/src/Common/src/Common.OData/OData/VersionedODataModelBuilder.cs index a18cc8dc..ffd22329 100644 --- a/src/Common/src/Common.OData/OData/VersionedODataModelBuilder.cs +++ b/src/Common/src/Common.OData/OData/VersionedODataModelBuilder.cs @@ -57,7 +57,7 @@ public partial class VersionedODataModelBuilder if ( configurations.Count == 0 ) { - return Array.Empty(); + return []; } var apiVersions = GetApiVersions(); @@ -76,7 +76,7 @@ private IReadOnlyList GetMergedConfigurations() { if ( modelConfigurations == null ) { - return Array.Empty(); + return []; } return modelConfigurations; @@ -86,7 +86,7 @@ private IReadOnlyList GetMergedConfigurations() if ( modelConfigurations == null || modelConfigurations.Count == 0 ) { - return new[] { delegatingConfiguration }; + return [delegatingConfiguration]; } var configurations = new IModelConfiguration[modelConfigurations.Count + 1]; @@ -115,7 +115,6 @@ private void BuildModelPerApiVersion( const int EntityContainerOnly = 1; var model = builder.GetEdmModel(); - var container = model.EntityContainer; var empty = model.SchemaElements.Count() == EntityContainerOnly; if ( empty ) diff --git a/src/Common/src/Common.OData/TypeExtensions.cs b/src/Common/src/Common.OData/TypeExtensions.cs index 6596cbfb..fa6ab09f 100644 --- a/src/Common/src/Common.OData/TypeExtensions.cs +++ b/src/Common/src/Common.OData/TypeExtensions.cs @@ -1,5 +1,8 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. +#pragma warning disable IDE0079 +#pragma warning disable IDE0130 + namespace System; #if NETFRAMEWORK @@ -15,7 +18,6 @@ namespace System; using ODataRoutingAttribute = Microsoft.AspNetCore.OData.Routing.Attributes.ODataAttributeRoutingAttribute; #endif using System.Reflection; -using System.Runtime.CompilerServices; internal static partial class TypeExtensions { @@ -26,42 +28,65 @@ internal static partial class TypeExtensions private static Type? odataQueryOptions; private static Type? odataActionParameters; - internal static bool IsODataController( this Type controllerType ) => controllerType.UsingOData(); - - internal static bool IsMetadataController( this Type controllerType ) + extension( Type type ) { - metadataController ??= typeof( MetadataController ); - return metadataController.IsAssignableFrom( controllerType ); - } + internal bool IsODataController => type.UsingOData; - internal static bool IsODataPath( this Type type ) - { - odataPath ??= typeof( ODataPath ); - return odataPath.IsAssignableFrom( type ); - } + internal bool IsMetadataController + { + get + { + metadataController ??= typeof( MetadataController ); + return metadataController.IsAssignableFrom( type ); + } + } - internal static bool IsODataQueryOptions( this Type type ) - { - odataQueryOptions ??= typeof( ODataQueryOptions ); - return odataQueryOptions.IsAssignableFrom( type ); - } + internal bool IsODataPath + { + get + { + odataPath ??= typeof( ODataPath ); + return odataPath.IsAssignableFrom( type ); + } + } - internal static bool IsODataActionParameters( this Type type ) - { - odataActionParameters ??= typeof( ODataActionParameters ); - return odataActionParameters.IsAssignableFrom( type ); - } + internal bool IsODataQueryOptions + { + get + { + odataQueryOptions ??= typeof( ODataQueryOptions ); + return odataQueryOptions.IsAssignableFrom( type ); + } + } - internal static bool IsDelta( this Type type ) - { - delta ??= typeof( IDelta ); - return delta.IsAssignableFrom( type ); + internal bool IsODataActionParameters + { + get + { + odataActionParameters ??= typeof( ODataActionParameters ); + return odataActionParameters.IsAssignableFrom( type ); + } + } + + internal bool IsDelta + { + get + { + delta ??= typeof( IDelta ); + return delta.IsAssignableFrom( type ); + } + } } - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static bool UsingOData( this MemberInfo member ) + extension( MemberInfo member ) { - odataRoutingAttributeType ??= typeof( ODataRoutingAttribute ); - return Attribute.IsDefined( member, odataRoutingAttributeType ); + private bool UsingOData + { + get + { + odataRoutingAttributeType ??= typeof( ODataRoutingAttribute ); + return Attribute.IsDefined( member, odataRoutingAttributeType ); + } + } } } \ No newline at end of file diff --git a/src/Common/src/Common.TypeInfo/TypeExtensions.cs b/src/Common/src/Common.TypeInfo/TypeExtensions.cs index 0e831de5..3767c65e 100644 --- a/src/Common/src/Common.TypeInfo/TypeExtensions.cs +++ b/src/Common/src/Common.TypeInfo/TypeExtensions.cs @@ -6,39 +6,47 @@ namespace Asp.Versioning; internal static partial class TypeExtensions { - internal static bool IsSimpleType( this Type type ) + extension( Type type ) { + internal bool IsSimpleType + { + get + { #if NETFRAMEWORK - return type.IsPrimitive || + return type.IsPrimitive || #else - return type.IsPrimitive() || + return type.IsPrimitive() || #endif - type.Equals( typeof( string ) ) || - type.Equals( typeof( decimal ) ) || - type.Equals( typeof( DateTime ) ) || - type.Equals( typeof( TimeSpan ) ) || - type.Equals( typeof( DateTimeOffset ) ) || + type.Equals( typeof( string ) ) || + type.Equals( typeof( decimal ) ) || + type.Equals( typeof( DateTime ) ) || + type.Equals( typeof( TimeSpan ) ) || + type.Equals( typeof( DateTimeOffset ) ) || #if !NETFRAMEWORK - type.Equals( typeof( DateOnly ) ) || - type.Equals( typeof( TimeOnly ) ) || + type.Equals( typeof( DateOnly ) ) || + type.Equals( typeof( TimeOnly ) ) || #endif - type.Equals( typeof( Guid ) ); - } - - internal static bool IsSimpleUnderlyingType( this Type type ) - { - var underlyingType = Nullable.GetUnderlyingType( type ); + type.Equals( typeof( Guid ) ); + } + } - if ( underlyingType != null ) + internal bool IsSimpleUnderlyingType { - type = underlyingType; - } + get + { + var underlyingType = Nullable.GetUnderlyingType( type ); - return type.IsSimpleType(); - } + if ( underlyingType != null ) + { + type = underlyingType; + } - internal static bool HasStringConverter( this Type type ) => - TypeDescriptor.GetConverter( type ).CanConvertFrom( typeof( string ) ); + return type.IsSimpleType; + } + } - internal static bool CanConvertFromString( this Type type ) => type.IsSimpleUnderlyingType() || type.HasStringConverter(); + internal bool HasStringConverter => TypeDescriptor.GetConverter( type ).CanConvertFrom( typeof( string ) ); + + internal bool CanConvertFromString => type.IsSimpleUnderlyingType || type.HasStringConverter; + } } \ No newline at end of file diff --git a/src/Common/src/Common/ApiVersionReader.cs b/src/Common/src/Common/ApiVersionReader.cs index 0fc2f51f..bf9672e1 100644 --- a/src/Common/src/Common/ApiVersionReader.cs +++ b/src/Common/src/Common/ApiVersionReader.cs @@ -118,7 +118,7 @@ public IReadOnlyList Read( HttpRequest request ) return version == null ? [] : [version]; } - return versions.ToArray(); + return [.. versions]; } public void AddParameters( IApiVersionParameterDescriptionContext context ) diff --git a/src/Common/src/Common/ApiVersioningPolicyBuilder.cs b/src/Common/src/Common/ApiVersioningPolicyBuilder.cs index 55687f82..58c2fbdc 100644 --- a/src/Common/src/Common/ApiVersioningPolicyBuilder.cs +++ b/src/Common/src/Common/ApiVersioningPolicyBuilder.cs @@ -24,7 +24,7 @@ public virtual IReadOnlyList OfType() where T : notnull return ( deprecationPolicies.Values.ToArray() as IReadOnlyList )!; } - return Array.Empty(); + return []; } /// diff --git a/src/Common/src/Common/CollectionExtensions.cs b/src/Common/src/Common/CollectionExtensions.cs index 4c6f1e4e..84e3f3a0 100644 --- a/src/Common/src/Common/CollectionExtensions.cs +++ b/src/Common/src/Common/CollectionExtensions.cs @@ -4,49 +4,54 @@ namespace System.Collections.Generic; internal static partial class CollectionExtensions { - internal static bool TryGetValue( - this IDictionary dictionary, - TKey key, - [MaybeNullWhen( false )] out TValue value ) - where TKey : notnull + extension( IDictionary dictionary ) where TKey : notnull { - if ( dictionary.TryGetValue( key, out var val ) && val is TValue v ) + internal bool TryGetValue( TKey key, [MaybeNullWhen( false )] out TValue value ) { - value = v; - return true; + if ( dictionary.TryGetValue( key, out var val ) && val is TValue v ) + { + value = v; + return true; + } + + value = default!; + return false; } - - value = default!; - return false; } - internal static List AsList( this IEnumerable sequence ) => ( sequence as List ) ?? sequence.ToList(); + extension( IEnumerable sequence ) + { + internal List AsList() => ( sequence as List ) ?? [.. sequence]; + } - internal static void AddRange( this ICollection collection, IEnumerable items ) + extension( ICollection collection ) { - switch ( items ) + internal void AddRange( IEnumerable items ) { - case IList list: - for ( var i = 0; i < list.Count; i++ ) - { - collection.Add( list[i] ); - } - - break; - case IReadOnlyList list: - for ( var i = 0; i < list.Count; i++ ) - { - collection.Add( list[i] ); - } - - break; - default: - foreach ( var item in items ) - { - collection.Add( item ); - } - - break; + switch ( items ) + { + case IList list: + for ( var i = 0; i < list.Count; i++ ) + { + collection.Add( list[i] ); + } + + break; + case IReadOnlyList list: + for ( var i = 0; i < list.Count; i++ ) + { + collection.Add( list[i] ); + } + + break; + default: + foreach ( var item in items ) + { + collection.Add( item ); + } + + break; + } } } } \ No newline at end of file diff --git a/src/Common/src/Common/DefaultApiVersionReporter.cs b/src/Common/src/Common/DefaultApiVersionReporter.cs index 84c71984..47d05b4f 100644 --- a/src/Common/src/Common/DefaultApiVersionReporter.cs +++ b/src/Common/src/Common/DefaultApiVersionReporter.cs @@ -37,7 +37,7 @@ public sealed partial class DefaultApiVersionReporter : IReportApiVersions /// THe HTTP header name used for deprecated API versions. /// The default value is "api-deprecated-versions". /// One or more of API versioning mappings. The default value is - /// and . + /// and . public DefaultApiVersionReporter( IPolicyManager sunsetPolicyManager, IPolicyManager deprecationPolicyManager, @@ -77,12 +77,12 @@ public void Report( HttpResponse response, ApiVersionModel apiVersionModel ) #if NETFRAMEWORK if ( response.RequestMessage is not HttpRequestMessage request || - request.GetActionDescriptor()?.GetApiVersionMetadata() is not ApiVersionMetadata metadata ) + request.GetActionDescriptor()?.ApiVersionMetadata is not ApiVersionMetadata metadata ) { return; } - var version = request.GetRequestedApiVersion(); + var version = request.RequestedApiVersion; #else var context = response.HttpContext; @@ -91,10 +91,10 @@ public void Report( HttpResponse response, ApiVersionModel apiVersionModel ) return; } - var version = context.GetRequestedApiVersion(); + var version = context.RequestedApiVersion; #endif var name = metadata.Name; - DateTimeOffset? sunsetDate = null; + var sunsetDate = default( DateTimeOffset? ); if ( sunsetPolicyManager.TryResolvePolicy( name, version, out var sunsetPolicy ) ) { @@ -104,8 +104,8 @@ public void Report( HttpResponse response, ApiVersionModel apiVersionModel ) if ( deprecationPolicyManager.TryResolvePolicy( name, version, out var deprecationPolicy ) ) { - // Only emit a deprecation header if the deprecation policy becomes effective before the sunset date. - if ( deprecationPolicy.IsEffective( sunsetDate ) ) + // only emit a deprecation header if the policy becomes effective before the sunset date + if ( !sunsetDate.HasValue || deprecationPolicy.IsEffective( sunsetDate.Value ) ) { response.WriteDeprecationPolicy( deprecationPolicy ); } diff --git a/src/Common/src/Common/DeprecationPolicyBuilder.cs b/src/Common/src/Common/DeprecationPolicyBuilder.cs index ca80bb2e..af0c7fb9 100644 --- a/src/Common/src/Common/DeprecationPolicyBuilder.cs +++ b/src/Common/src/Common/DeprecationPolicyBuilder.cs @@ -20,10 +20,7 @@ public DeprecationPolicyBuilder( string? name, ApiVersion? apiVersion ) : base( name, apiVersion ) { } /// - public virtual void SetEffectiveDate( DateTimeOffset effectiveDate ) - { - date = effectiveDate; - } + public virtual void SetEffectiveDate( DateTimeOffset effectiveDate ) => date = effectiveDate; /// public virtual ILinkBuilder Link( Uri linkTarget ) @@ -82,15 +79,9 @@ public override DeprecationPolicy Build() return policy; } - private sealed class DeprecationLinkBuilder : LinkBuilder, ILinkBuilder + private sealed class DeprecationLinkBuilder( DeprecationPolicyBuilder policyBuilder, Uri linkTarget ) : + LinkBuilder( linkTarget, "deprecation" ), ILinkBuilder { - protected override string RelationType => "deprecation"; - - private readonly DeprecationPolicyBuilder policyBuilder; - - public DeprecationLinkBuilder( DeprecationPolicyBuilder policy, Uri linkTarget ) - : base( linkTarget ) => policyBuilder = policy; - public override ILinkBuilder Link( Uri linkTarget ) => policyBuilder.Link( linkTarget ); } } \ No newline at end of file diff --git a/src/Common/src/Common/DeprecationPolicyManager.cs b/src/Common/src/Common/DeprecationPolicyManager.cs index 3c8765da..ea74a66d 100644 --- a/src/Common/src/Common/DeprecationPolicyManager.cs +++ b/src/Common/src/Common/DeprecationPolicyManager.cs @@ -5,8 +5,6 @@ namespace Asp.Versioning; /// /// Represents the default API version deprecation policy manager. /// -/// -/// This class serves as a type alias to hide the generic arguments of . -/// public partial class DeprecationPolicyManager : PolicyManager -{ } \ No newline at end of file +{ +} \ No newline at end of file diff --git a/src/Common/src/Common/LinkBuilder.cs b/src/Common/src/Common/LinkBuilder.cs index 16075de3..05d78d1c 100644 --- a/src/Common/src/Common/LinkBuilder.cs +++ b/src/Common/src/Common/LinkBuilder.cs @@ -2,20 +2,16 @@ namespace Asp.Versioning; -internal abstract class LinkBuilder : ILinkBuilder +internal abstract class LinkBuilder( Uri linkTarget, string relationType ) : ILinkBuilder { - protected abstract string RelationType { get; } private string? language; private List? languages; private string? title; private string? type; - public LinkBuilder( Uri linkTarget ) - { - LinkTarget = linkTarget; - } + protected string RelationType => relationType; - public Uri LinkTarget { get; } + public Uri LinkTarget => linkTarget; public ILinkBuilder Language( string value ) { diff --git a/src/Common/src/Common/MediaTypeApiVersionReaderBuilder.cs b/src/Common/src/Common/MediaTypeApiVersionReaderBuilder.cs index af03ef2f..7a344ba5 100644 --- a/src/Common/src/Common/MediaTypeApiVersionReaderBuilder.cs +++ b/src/Common/src/Common/MediaTypeApiVersionReaderBuilder.cs @@ -150,11 +150,11 @@ public virtual IApiVersionReader Build() => included ?? [], excluded ?? [], #elif NETFRAMEWORK - included ?? new( capacity: 0 ), - excluded ?? new( capacity: 0 ), + included ?? [], + excluded ?? [], #else - included?.ToFrozenSet( included.Comparer ) ?? FrozenSet.Empty, - excluded?.ToFrozenSet( excluded.Comparer ) ?? FrozenSet.Empty, + included?.ToFrozenSet( included.Comparer ) ?? [], + excluded?.ToFrozenSet( excluded.Comparer ) ?? [], #endif select ?? DefaultSelector, readers?.ToArray() ?? [] ); @@ -335,7 +335,7 @@ public IReadOnlyList Read( HttpRequest request ) { if ( readers.Length == 0 ) { - return Array.Empty(); + return []; } #if NETFRAMEWORK @@ -367,7 +367,7 @@ public IReadOnlyList Read( HttpRequest request ) if ( mediaTypes == null ) { - return Array.Empty(); + return []; } Filter( mediaTypes ); @@ -375,7 +375,7 @@ public IReadOnlyList Read( HttpRequest request ) switch ( mediaTypes.Count ) { case 0: - return Array.Empty(); + return []; case 1: break; default: @@ -390,7 +390,7 @@ public IReadOnlyList Read( HttpRequest request ) return version == null ? Array.Empty() : [version]; } - return selector( request, versions.ToArray() ); + return selector( request, [.. versions] ); } private void Filter( List mediaTypes ) diff --git a/src/Common/src/Common/MediaTypeApiVersionReaderBuilderExtensions.cs b/src/Common/src/Common/MediaTypeApiVersionReaderBuilderExtensions.cs index c1e99a54..6534aa70 100644 --- a/src/Common/src/Common/MediaTypeApiVersionReaderBuilderExtensions.cs +++ b/src/Common/src/Common/MediaTypeApiVersionReaderBuilderExtensions.cs @@ -7,33 +7,32 @@ namespace Asp.Versioning; /// public static class MediaTypeApiVersionReaderBuilderExtensions { - /// - /// Selects the first available API version, if there is one. - /// /// The type of builder. - /// The extended builder. - /// The current builder. - /// This will likely select the lowest API version. /// is null. - public static T SelectFirstOrDefault( this T builder ) where T : MediaTypeApiVersionReaderBuilder + extension( T builder ) where T : MediaTypeApiVersionReaderBuilder { - ArgumentNullException.ThrowIfNull( builder ); - builder.Select( static ( request, versions ) => versions.Count == 0 ? versions : [versions[0]] ); - return builder; - } + /// + /// Selects the first available API version, if there is one. + /// + /// The current builder. + /// This will likely select the lowest API version. + public T SelectFirstOrDefault() + { + ArgumentNullException.ThrowIfNull( builder ); + builder.Select( static ( request, versions ) => versions.Count == 0 ? versions : [versions[0]] ); + return builder; + } - /// - /// Selects the last available API version, if there is one. - /// - /// The type of builder. - /// The extended builder. - /// The current builder. - /// This will likely select the highest API version. - /// is null. - public static T SelectLastOrDefault( this T builder ) where T : MediaTypeApiVersionReaderBuilder - { - ArgumentNullException.ThrowIfNull( builder ); - builder.Select( static ( request, versions ) => versions.Count == 0 ? versions : [versions[versions.Count - 1]] ); - return builder; + /// + /// Selects the last available API version, if there is one. + /// + /// The current builder. + /// This will likely select the highest API version. + public T SelectLastOrDefault() + { + ArgumentNullException.ThrowIfNull( builder ); + builder.Select( static ( request, versions ) => versions.Count == 0 ? versions : [versions[versions.Count - 1]] ); + return builder; + } } } \ No newline at end of file diff --git a/src/Common/src/Common/SunsetPolicyBuilder.cs b/src/Common/src/Common/SunsetPolicyBuilder.cs index dc189696..73394f60 100644 --- a/src/Common/src/Common/SunsetPolicyBuilder.cs +++ b/src/Common/src/Common/SunsetPolicyBuilder.cs @@ -20,10 +20,7 @@ public SunsetPolicyBuilder( string? name, ApiVersion? apiVersion ) : base( name, apiVersion ) { } /// - public virtual void SetEffectiveDate( DateTimeOffset effectiveDate ) - { - date = effectiveDate; - } + public virtual void SetEffectiveDate( DateTimeOffset effectiveDate ) => date = effectiveDate; /// public virtual ILinkBuilder Link( Uri linkTarget ) @@ -82,15 +79,9 @@ public override SunsetPolicy Build() return policy; } - private sealed class SunsetLinkBuilder : LinkBuilder, ILinkBuilder + private sealed class SunsetLinkBuilder( SunsetPolicyBuilder policyBuilder, Uri linkTarget ) : + LinkBuilder( linkTarget, "sunset" ), ILinkBuilder { - protected override string RelationType => "sunset"; - - private readonly SunsetPolicyBuilder policyBuilder; - - public SunsetLinkBuilder( SunsetPolicyBuilder policy, Uri linkTarget ) - : base( linkTarget ) => policyBuilder = policy; - public override ILinkBuilder Link( Uri linkTarget ) => policyBuilder.Link( linkTarget ); } } \ No newline at end of file diff --git a/src/Common/src/Common/SunsetPolicyManager.cs b/src/Common/src/Common/SunsetPolicyManager.cs index 9cfe0c73..adfcbf27 100644 --- a/src/Common/src/Common/SunsetPolicyManager.cs +++ b/src/Common/src/Common/SunsetPolicyManager.cs @@ -5,8 +5,6 @@ namespace Asp.Versioning; /// /// Represents the default API version sunset policy manager. /// -/// -/// This class serves as a type alias to hide the generic arguments of . -/// public partial class SunsetPolicyManager : PolicyManager -{ } \ No newline at end of file +{ +} \ No newline at end of file diff --git a/src/Common/src/Common/UriExtensions.cs b/src/Common/src/Common/UriExtensions.cs index 7635a675..42a8895b 100644 --- a/src/Common/src/Common/UriExtensions.cs +++ b/src/Common/src/Common/UriExtensions.cs @@ -4,5 +4,8 @@ namespace Asp.Versioning; internal static class UriExtensions { - internal static string SafeFullPath( this Uri uri ) => uri.GetLeftPart( UriPartial.Path ); + extension( Uri uri ) + { + internal string SafePath => uri.GetLeftPart( UriPartial.Path ); + } } \ No newline at end of file diff --git a/src/Common/test/Common.Acceptance.Tests/AcceptanceTest.cs b/src/Common/test/Common.Acceptance.Tests/AcceptanceTest.cs index dc67f6f5..96eda42a 100644 --- a/src/Common/test/Common.Acceptance.Tests/AcceptanceTest.cs +++ b/src/Common/test/Common.Acceptance.Tests/AcceptanceTest.cs @@ -23,6 +23,8 @@ public abstract partial class AcceptanceTest : IDisposable protected HttpClient Client => client.Value; + protected static CancellationToken CancellationToken => TestContext.Current.CancellationToken; + public void Dispose() { Dispose( true ); @@ -76,21 +78,29 @@ private HttpRequestMessage CreateRequest( string requestUri, HttpContent content return new HttpRequestMessage( method, requestUri ) { Content = content }; } - protected virtual Task GetAsync( string requestUri ) => Client.SendAsync( CreateRequest( requestUri, default( object ), Get ) ); + protected virtual Task GetAsync( string requestUri ) => + Client.SendAsync( CreateRequest( requestUri, default( object ), Get ), CancellationToken ); - protected virtual Task PostAsync( string requestUri, TEntity entity ) => Client.SendAsync( CreateRequest( requestUri, entity, Post ) ); + protected virtual Task PostAsync( string requestUri, TEntity entity ) => + Client.SendAsync( CreateRequest( requestUri, entity, Post ), CancellationToken ); - protected virtual Task PostAsync( string requestUri, HttpContent content ) => Client.SendAsync( CreateRequest( requestUri, content, Post ) ); + protected virtual Task PostAsync( string requestUri, HttpContent content ) => + Client.SendAsync( CreateRequest( requestUri, content, Post ), CancellationToken ); - protected virtual Task PutAsync( string requestUri, TEntity entity ) => Client.SendAsync( CreateRequest( requestUri, entity, Put ) ); + protected virtual Task PutAsync( string requestUri, TEntity entity ) => + Client.SendAsync( CreateRequest( requestUri, entity, Put ), CancellationToken ); - protected virtual Task PutAsync( string requestUri, HttpContent content ) => Client.SendAsync( CreateRequest( requestUri, content, Put ) ); + protected virtual Task PutAsync( string requestUri, HttpContent content ) => + Client.SendAsync( CreateRequest( requestUri, content, Put ), CancellationToken ); - protected virtual Task PatchAsync( string requestUri, TEntity entity ) => Client.SendAsync( CreateRequest( requestUri, entity, Patch ) ); + protected virtual Task PatchAsync( string requestUri, TEntity entity ) => + Client.SendAsync( CreateRequest( requestUri, entity, Patch ), CancellationToken ); - protected virtual Task PatchAsync( string requestUri, HttpContent content ) => Client.SendAsync( CreateRequest( requestUri, content, Patch ) ); + protected virtual Task PatchAsync( string requestUri, HttpContent content ) => + Client.SendAsync( CreateRequest( requestUri, content, Patch ), CancellationToken ); - protected virtual Task DeleteAsync( string requestUri ) => Client.SendAsync( CreateRequest( requestUri, default( object ), Delete ) ); + protected virtual Task DeleteAsync( string requestUri ) => + Client.SendAsync( CreateRequest( requestUri, default( object ), Delete ), CancellationToken ); private void AddDefaultAcceptHeaderIfNecessary() { diff --git a/src/Common/test/Common.Acceptance.Tests/HttpContentExtensions.cs b/src/Common/test/Common.Acceptance.Tests/HttpContentExtensions.cs index 20418181..c3df538a 100644 --- a/src/Common/test/Common.Acceptance.Tests/HttpContentExtensions.cs +++ b/src/Common/test/Common.Acceptance.Tests/HttpContentExtensions.cs @@ -19,25 +19,23 @@ internal static class HttpContentExtensions { SupportedMediaTypes = { new( ProblemDetailsDefaults.MediaType.Json ) }, }; - private static readonly IEnumerable MediaTypeFormatters = new[] { ProblemDetailsMediaTypeFormatter }; + private static readonly IEnumerable MediaTypeFormatters = [ProblemDetailsMediaTypeFormatter]; #endif - public static Task ReadAsProblemDetailsAsync( - this HttpContent content, - CancellationToken cancellationToken = default ) => + extension( HttpContent content ) + { + public Task ReadAsProblemDetailsAsync( CancellationToken cancellationToken = default ) => #if NETFRAMEWORK - content.ReadAsAsync( MediaTypeFormatters, cancellationToken ); + content.ReadAsAsync( MediaTypeFormatters, cancellationToken ); #else - content.ReadFromJsonAsync( cancellationToken ); + content.ReadFromJsonAsync( cancellationToken ); #endif #pragma warning disable IDE0060 // Remove unused parameter #pragma warning disable IDE0079 // Remove unnecessary suppression - public static Task ReadAsExampleAsync( - this HttpContent content, - T example, - CancellationToken cancellationToken = default ) => - content.ReadAsAsync( cancellationToken ); + public Task ReadAsExampleAsync( T example, CancellationToken cancellationToken = default ) => + content.ReadAsAsync( cancellationToken ); #pragma warning restore IDE0060 // Remove unused parameter #pragma warning restore IDE0079 // Remove unnecessary suppression + } } \ No newline at end of file diff --git a/src/Common/test/Common.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderExtensionsTest.cs b/src/Common/test/Common.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderExtensionsTest.cs index f18d23df..1bf9fc47 100644 --- a/src/Common/test/Common.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderExtensionsTest.cs +++ b/src/Common/test/Common.Mvc.Tests/Conventions/ControllerApiVersionConventionBuilderExtensionsTest.cs @@ -128,7 +128,7 @@ public void has_api_versions_should_add_multiple_api_versions() controllerBuilder.HasApiVersions( apiVersions ); // assert - controllerBuilder.ProtectedSupportedVersions.Should().BeEquivalentTo( new[] { new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0 ) } ); + controllerBuilder.ProtectedSupportedVersions.Should().BeEquivalentTo( [new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0 )] ); } [Fact] @@ -248,7 +248,7 @@ public void has_deprecated_api_versions_should_add_multiple_api_versions() controllerBuilder.HasDeprecatedApiVersions( apiVersions ); // assert - controllerBuilder.ProtectedDeprecatedVersions.Should().BeEquivalentTo( new[] { new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0 ) } ); + controllerBuilder.ProtectedDeprecatedVersions.Should().BeEquivalentTo( [new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0 )] ); } [Fact] @@ -368,7 +368,7 @@ public void advertises_api_versions_should_add_multiple_api_versions() controllerBuilder.AdvertisesApiVersions( apiVersions ); // assert - controllerBuilder.ProtectedAdvertisedVersions.Should().BeEquivalentTo( new[] { new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0 ) } ); + controllerBuilder.ProtectedAdvertisedVersions.Should().BeEquivalentTo( [new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0 )] ); } [Fact] diff --git a/src/Common/test/Common.Mvc.Tests/Conventions/ControllerConventionBuilderExtensionsTest.cs b/src/Common/test/Common.Mvc.Tests/Conventions/ControllerConventionBuilderExtensionsTest.cs index 0d55d027..2c1ce23a 100644 --- a/src/Common/test/Common.Mvc.Tests/Conventions/ControllerConventionBuilderExtensionsTest.cs +++ b/src/Common/test/Common.Mvc.Tests/Conventions/ControllerConventionBuilderExtensionsTest.cs @@ -103,8 +103,8 @@ public void action_should_throw_exception_when_method_does_not_exist() #pragma warning disable IDE0060 #pragma warning disable IDE0079 -#pragma warning disable CA1822 #pragma warning disable CA1034 // Nested types should not be visible +#pragma warning disable CA1822 #if !NETFRAMEWORK [ApiController] diff --git a/src/Common/test/Common.Mvc.Tests/Conventions/DefaultControllerNameConventionTest.cs b/src/Common/test/Common.Mvc.Tests/Conventions/DefaultControllerNameConventionTest.cs index e261f4f9..fab87361 100644 --- a/src/Common/test/Common.Mvc.Tests/Conventions/DefaultControllerNameConventionTest.cs +++ b/src/Common/test/Common.Mvc.Tests/Conventions/DefaultControllerNameConventionTest.cs @@ -5,7 +5,7 @@ namespace Asp.Versioning.Conventions; public partial class DefaultControllerNameConventionTest { [Theory] - [MemberData(nameof(NormalizeNameData))] + [MemberData( nameof( NormalizeNameData ) )] public void normalize_name_should_trim_suffix( string controllerName ) { // arrange @@ -31,20 +31,14 @@ public void group_name_should_return_original_name() name.Should().Be( "Values" ); } - public static IEnumerable NormalizeNameData + public static TheoryData NormalizeNameData => new() { - get - { - return new object[][] - { #if NETFRAMEWORK - ["ValuesController"], - ["Values2Controller"], + { "ValuesController" }, + { "Values2Controller" }, #else - ["Values"], - ["Values2"], + { "Values" }, + { "Values2" }, #endif - }; - } - } + }; } \ No newline at end of file diff --git a/src/Common/test/Common.Mvc.Tests/Conventions/GroupedControllerNameConventionTest.cs b/src/Common/test/Common.Mvc.Tests/Conventions/GroupedControllerNameConventionTest.cs index a56ad0c8..447786c2 100644 --- a/src/Common/test/Common.Mvc.Tests/Conventions/GroupedControllerNameConventionTest.cs +++ b/src/Common/test/Common.Mvc.Tests/Conventions/GroupedControllerNameConventionTest.cs @@ -5,7 +5,7 @@ namespace Asp.Versioning.Conventions; public partial class GroupedControllerNameConventionTest { [Theory] - [MemberData(nameof(NormalizeNameData))] + [MemberData( nameof( NormalizeNameData ) )] public void normalize_name_should_not_trim_suffix( string controllerName ) { // arrange @@ -33,19 +33,13 @@ public void group_name_should_trim_trailing_numbers( string controllerName ) name.Should().Be( "Values" ); } - public static IEnumerable NormalizeNameData + public static TheoryData NormalizeNameData => new() { - get - { - return new object[][] - { - ["Values"], + { "Values" }, #if NETFRAMEWORK - ["ValuesController2"], + { "ValuesController2" }, #else - ["Values2"], + { "Values2" }, #endif - }; - } - } + }; } \ No newline at end of file diff --git a/src/Common/test/Common.Mvc.Tests/Conventions/OriginalControllerNameConventionTest.cs b/src/Common/test/Common.Mvc.Tests/Conventions/OriginalControllerNameConventionTest.cs index 551c2149..8ec918cd 100644 --- a/src/Common/test/Common.Mvc.Tests/Conventions/OriginalControllerNameConventionTest.cs +++ b/src/Common/test/Common.Mvc.Tests/Conventions/OriginalControllerNameConventionTest.cs @@ -31,18 +31,12 @@ public void group_name_should_return_original_name() name.Should().Be( "Values2" ); } - public static IEnumerable NormalizeNameData + public static TheoryData NormalizeNameData => new() { - get - { - return new object[][] - { - ["Values"], - ["Values2"], + { "Values" }, + { "Values2" }, #if NETFRAMEWORK - ["ValuesController2"], + { "ValuesController2" }, #endif - }; - } - } + }; } \ No newline at end of file diff --git a/src/Common/test/Common.Mvc.Tests/Conventions/VersionByNamespaceConventionTest.cs b/src/Common/test/Common.Mvc.Tests/Conventions/VersionByNamespaceConventionTest.cs index 9d370822..ab63edd5 100644 --- a/src/Common/test/Common.Mvc.Tests/Conventions/VersionByNamespaceConventionTest.cs +++ b/src/Common/test/Common.Mvc.Tests/Conventions/VersionByNamespaceConventionTest.cs @@ -6,32 +6,30 @@ namespace Asp.Versioning.Conventions; public partial class VersionByNamespaceConventionTest { - public static IEnumerable NamespaceAsVersionData + public static TheoryData NamespaceAsVersionData => new() { - get - { - yield return new object[] { "v1", "1.0" }; - yield return new object[] { "v1RC", "1.0-RC" }; - yield return new object[] { "v20180401", "2018-04-01" }; - yield return new object[] { "v20180401_Beta", "2018-04-01-Beta" }; - yield return new object[] { "v20180401Beta", "2018-04-01-Beta" }; - yield return new object[] { "Contoso.Api.v1.Controllers", "1.0" }; - yield return new object[] { "Contoso.Api.v10_0.Controllers", "10.0" }; - yield return new object[] { "Contoso.Api.v1_1.Controllers", "1.1" }; - yield return new object[] { "Contoso.Api.v0_9_Beta.Controllers", "0.9-Beta" }; - yield return new object[] { "Contoso.Api.v20180401.Controllers", "2018-04-01" }; - yield return new object[] { "Contoso.Api.v2018_04_01.Controllers", "2018-04-01" }; - yield return new object[] { "Contoso.Api.v20180401_Beta.Controllers", "2018-04-01-Beta" }; - yield return new object[] { "Contoso.Api.v2018_04_01_Beta.Controllers", "2018-04-01-Beta" }; - yield return new object[] { "Contoso.Api.v2018_04_01_1_0_Beta.Controllers", "2018-04-01.1.0-Beta" }; - yield return new object[] { "MyRestaurant.Vegetarian.Food.v1_1.Controllers", "1.1" }; - yield return new object[] { "VersioningSample.V5.Controllers", "5.0" }; - } - } + { "v1", "1.0" }, + { "v1RC", "1.0-RC" }, + { "v20180401", "2018-04-01" }, + { "v20180401_Beta", "2018-04-01-Beta" }, + { "v20180401Beta", "2018-04-01-Beta" }, + { "Contoso.Api.v1.Controllers", "1.0" }, + { "Contoso.Api.v10_0.Controllers", "10.0" }, + { "Contoso.Api.v1_1.Controllers", "1.1" }, + { "Contoso.Api.v0_9_Beta.Controllers", "0.9-Beta" }, + { "Contoso.Api.v20180401.Controllers", "2018-04-01" }, + { "Contoso.Api.v2018_04_01.Controllers", "2018-04-01" }, + { "Contoso.Api.v20180401_Beta.Controllers", "2018-04-01-Beta" }, + { "Contoso.Api.v2018_04_01_Beta.Controllers", "2018-04-01-Beta" }, + { "Contoso.Api.v2018_04_01_1_0_Beta.Controllers", "2018-04-01.1.0-Beta" }, + { "MyRestaurant.Vegetarian.Food.v1_1.Controllers", "1.1" }, + { "VersioningSample.V5.Controllers", "5.0" }, + }; private sealed class TestType : TypeDelegator { internal TestType( string @namespace ) => Namespace = @namespace; + public override string Namespace { get; } } } \ No newline at end of file diff --git a/src/Common/test/Common.OData.ApiExplorer.Tests/Conventions/DefaultODataQueryOptionDescriptionProviderTest.cs b/src/Common/test/Common.OData.ApiExplorer.Tests/Conventions/DefaultODataQueryOptionDescriptionProviderTest.cs index b9c17f4e..da930e00 100644 --- a/src/Common/test/Common.OData.ApiExplorer.Tests/Conventions/DefaultODataQueryOptionDescriptionProviderTest.cs +++ b/src/Common/test/Common.OData.ApiExplorer.Tests/Conventions/DefaultODataQueryOptionDescriptionProviderTest.cs @@ -200,61 +200,58 @@ public void describe_should_return_description_for_filter( description.Should().Be( expected ); } - public static IEnumerable FilterDescriptionData + public static TheoryData< + int, + string[], + AllowedLogicalOperators, + AllowedArithmeticOperators, + AllowedFunctions, + string> FilterDescriptionData => new() { - get { - yield return new object[] - { - 0, - Array.Empty(), - AllowedLogicalOperators.None, - AllowedArithmeticOperators.None, - AllowedFunctions.None, - "Restricts the set of items returned.", - }; - - yield return new object[] - { - 2, - Array.Empty(), - AllowedLogicalOperators.None, - AllowedArithmeticOperators.None, - AllowedFunctions.None, - "Restricts the set of items returned. The maximum number of expressions is 2.", - }; - - yield return new object[] - { - 3, - Array.Empty(), - AllowedLogicalOperators.All, - Add | Subtract, - AllowedFunctions.None, - "Restricts the set of items returned. The maximum number of expressions is 3. The allowed arithmetic operators are: add, sub.", - }; - - yield return new object[] - { - 5, - new[] { "name", "price", "quantity" }, - And, - AllowedArithmeticOperators.All, - Contains | StartsWith | EndsWith, - "Restricts the set of items returned. The maximum number of expressions is 5. The allowed logical operators are: and. The allowed functions are: startswith, endswith, contains. The allowed properties are: name, price, quantity.", - }; - - yield return new object[] - { - 0, - new[] { "category", "price", "quantity" }, - AllowedLogicalOperators.All, - AllowedArithmeticOperators.All, - AllFunctions, - "Restricts the set of items returned. The allowed properties are: category, price, quantity.", - }; - } - } + 0, + [], + AllowedLogicalOperators.None, + AllowedArithmeticOperators.None, + AllowedFunctions.None, + "Restricts the set of items returned." + }, + { + 2, + [], + AllowedLogicalOperators.None, + AllowedArithmeticOperators.None, + AllowedFunctions.None, + "Restricts the set of items returned. The maximum number of expressions is 2." + }, + { + 3, + [], + AllowedLogicalOperators.All, + Add | Subtract, + AllowedFunctions.None, + "Restricts the set of items returned. The maximum number of expressions is 3. " + + "The allowed arithmetic operators are: add, sub." + }, + { + 5, + new[] { "name", "price", "quantity" }, + And, + AllowedArithmeticOperators.All, + Contains | StartsWith | EndsWith, + "Restricts the set of items returned. The maximum number of expressions is 5. " + + "The allowed logical operators are: and. The allowed functions are: startswith, endswith, contains. " + + "The allowed properties are: name, price, quantity." + }, + { + 0, + new[] { "category", "price", "quantity" }, + AllowedLogicalOperators.All, + AllowedArithmeticOperators.All, + AllFunctions, + "Restricts the set of items returned. The allowed properties are: category, price, quantity." + }, + }; private static string FormatMessage( string message, string paramName ) => new ArgumentException( message, paramName ).Message; diff --git a/src/Common/test/Common.OData.ApiExplorer.Tests/OData/AllowedRolesAttribute.cs b/src/Common/test/Common.OData.ApiExplorer.Tests/OData/AllowedRolesAttribute.cs index caa90890..1a84f75f 100644 --- a/src/Common/test/Common.OData.ApiExplorer.Tests/OData/AllowedRolesAttribute.cs +++ b/src/Common/test/Common.OData.ApiExplorer.Tests/OData/AllowedRolesAttribute.cs @@ -7,7 +7,7 @@ public sealed class AllowedRolesAttribute : Attribute { public AllowedRolesAttribute( params string[] allowedRoles ) { - AllowedRoles = allowedRoles.ToList(); + AllowedRoles = [.. allowedRoles]; } public IReadOnlyList AllowedRoles { get; } diff --git a/src/Common/test/Common.OData.ApiExplorer.Tests/OData/DefaultModelTypeBuilderTest.cs b/src/Common/test/Common.OData.ApiExplorer.Tests/OData/DefaultModelTypeBuilderTest.cs index d5295031..f0053beb 100644 --- a/src/Common/test/Common.OData.ApiExplorer.Tests/OData/DefaultModelTypeBuilderTest.cs +++ b/src/Common/test/Common.OData.ApiExplorer.Tests/OData/DefaultModelTypeBuilderTest.cs @@ -50,7 +50,7 @@ public void substituted_type_should_be_extracted_from_parent_generic() var substitutedType = originalType.SubstituteIfNecessary( context ); // assert - substitutedType.Should().Be( typeof( Contact ) ); + substitutedType.Should().Be(); } [Fact] @@ -72,7 +72,7 @@ public void type_should_be_match_edm_when_extracted_and_substituted_from_parent_ // assert substitutedType.Should().NotBe( originalType ); - substitutedType.Should().NotBe( typeof( Contact ) ); + substitutedType.Should().NotBe(); substitutedType.GetRuntimeProperties().Should().HaveCount( 3 ); substitutedType.Should().HaveProperty( nameof( Contact.ContactId ) ); substitutedType.Should().HaveProperty( nameof( Contact.FirstName ) ); @@ -426,46 +426,25 @@ public void ignoring_property_should_force_substitution_with_valid_runtime_prope // assert substitutedType.Should().NotBe( addressType ); -#if NET452 - substitutedType.GetRuntimeProperties().Should().HaveCount( 5 ); - - foreach ( var substitutedProperty in substitutedType.GetRuntimeProperties() ) - { - substitutedProperty.Should().NotBeNull(); - substitutedProperty.GetSetMethod( true ).Should().NotBeNull() - .And.Match( p => p.ReturnType == typeof( void ) ) - .And.Match( p => p.GetParameters().Length == 1 ); - } -#else substitutedType.GetRuntimeProperties().Should().HaveCount( 5 ) .And.AllSatisfy( prop => prop.GetSetMethod( true ).Should() .NotBeNull() .And.ReturnVoid() .And.Match( setter => setter.GetParameters().Length == 1 ) ); -#endif } - public static IEnumerable SubstitutionNotRequiredData + public static TheoryData SubstitutionNotRequiredData => new() { - get - { - yield return new object[] { typeof( IEnumerable ) }; - yield return new object[] { typeof( IEnumerable ) }; - yield return new object[] { typeof( ODataValue> ) }; - } - } + { typeof( IEnumerable ) }, + { typeof( IEnumerable ) }, + { typeof( ODataValue> ) }, + }; - public static IEnumerable SubstitutionData + public static TheoryData SubstitutionData => new() { - get - { - yield return new object[] { typeof( IEnumerable ) }; - yield return new object[] { typeof( ODataValue ) }; - } - } + { typeof( IEnumerable ) }, + { typeof( ODataValue ) }, + }; - private static TypeSubstitutionContext NewContext( IEdmModel model ) - { - return new TypeSubstitutionContext( model, new DefaultModelTypeBuilder() ); - } + private static TypeSubstitutionContext NewContext( IEdmModel model ) => new( model, new DefaultModelTypeBuilder() ); } \ No newline at end of file diff --git a/src/Common/test/Common.Tests/MaxSelectVersionData.cs b/src/Common/test/Common.Tests/MaxSelectVersionData.cs index d91e1417..7149fa5a 100644 --- a/src/Common/test/Common.Tests/MaxSelectVersionData.cs +++ b/src/Common/test/Common.Tests/MaxSelectVersionData.cs @@ -2,57 +2,43 @@ namespace Asp.Versioning; -public class MaxSelectVersionData : SelectVersionData +public sealed class MaxSelectVersionData : SelectVersionData { - public override IEnumerator GetEnumerator() + public MaxSelectVersionData() { - yield return new object[] - { + Add( Supported( new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0, "Alpha" ) ), Deprecated(), - Expected( new ApiVersion( 2, 0 ) ), - }; + Expected( new ApiVersion( 2, 0 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ) ), Deprecated( new ApiVersion( 3, 0 ) ), - new ApiVersion( 3, 0 ), - }; + Expected( new ApiVersion( 3, 0 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 2, 0 ), new ApiVersion( 3, 1, "Beta" ) ), Deprecated( new ApiVersion( 1, 0 ), new ApiVersion( 3, 0 ) ), - Expected( new ApiVersion( 3, 0 ) ), - }; + Expected( new ApiVersion( 3, 0 ) ) ); - yield return new object[] - { + Add( Supported(), Deprecated(), - Expected( new ApiVersion( 42, 0 ) ), - }; + Expected( new ApiVersion( 42, 0 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 1, 1, "RC1" ) ), Deprecated(), - Expected( new ApiVersion( 42, 0 ) ), - }; + Expected( new ApiVersion( 42, 0 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 2, 5 ) ), Deprecated(), - Expected( new ApiVersion( 2, 5 ) ), - }; + Expected( new ApiVersion( 2, 5 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 0, 8, "Beta" ), new ApiVersion( 0, 9, "RC" ) ), Deprecated(), - Expected( new ApiVersion( 42, 0 ) ), - }; + Expected( new ApiVersion( 42, 0 ) ) ); } } \ No newline at end of file diff --git a/src/Common/test/Common.Tests/MinSelectVersionData.cs b/src/Common/test/Common.Tests/MinSelectVersionData.cs index 6c38d123..70408cbe 100644 --- a/src/Common/test/Common.Tests/MinSelectVersionData.cs +++ b/src/Common/test/Common.Tests/MinSelectVersionData.cs @@ -2,57 +2,43 @@ namespace Asp.Versioning; -public class MinSelectVersionData : SelectVersionData +public sealed class MinSelectVersionData : SelectVersionData { - public override IEnumerator GetEnumerator() + public MinSelectVersionData() { - yield return new object[] - { + Add( Supported( new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ), new ApiVersion( 3, 0, "Alpha" ) ), Deprecated(), - Expected( new ApiVersion( 1, 0 ) ), - }; + Expected( new ApiVersion( 1, 0 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 0, 9, "RC" ), new ApiVersion( 1, 0 ), new ApiVersion( 2, 0 ) ), Deprecated( new ApiVersion( 3, 0 ) ), - Expected( new ApiVersion( 1, 0 ) ), - }; + Expected( new ApiVersion( 1, 0 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 2, 0 ), new ApiVersion( 3, 1, "Beta" ) ), Deprecated( new ApiVersion( 1, 0 ), new ApiVersion( 3, 0 ) ), - Expected( new ApiVersion( 1, 0 ) ), - }; + Expected( new ApiVersion( 1, 0 ) ) ); - yield return new object[] - { + Add( Supported(), Deprecated(), - Expected( new ApiVersion( 42, 0 ) ), - }; + Expected( new ApiVersion( 42, 0 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 1, 1, "RC1" ) ), Deprecated(), - Expected( new ApiVersion( 42, 0 ) ), - }; + Expected( new ApiVersion( 42, 0 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 2, 5 ) ), Deprecated(), - Expected( new ApiVersion( 2, 5 ) ), - }; + Expected( new ApiVersion( 2, 5 ) ) ); - yield return new object[] - { + Add( Supported( new ApiVersion( 0, 8, "Beta" ), new ApiVersion( 0, 9, "RC" ) ), Deprecated(), - Expected( new ApiVersion( 42, 0 ) ), - }; + Expected( new ApiVersion( 42, 0 ) ) ); } } \ No newline at end of file diff --git a/src/Common/test/Common.Tests/SelectVersionData.cs b/src/Common/test/Common.Tests/SelectVersionData.cs index 998e89bc..4bccb98c 100644 --- a/src/Common/test/Common.Tests/SelectVersionData.cs +++ b/src/Common/test/Common.Tests/SelectVersionData.cs @@ -2,17 +2,13 @@ namespace Asp.Versioning; -using System.Collections; - -public abstract class SelectVersionData : IEnumerable +public abstract class SelectVersionData : TheoryData { - public abstract IEnumerator GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + protected SelectVersionData() { } - protected static IEnumerable Supported( params ApiVersion[] versions ) => versions.AsEnumerable(); + protected static ApiVersion[] Supported( params ApiVersion[] versions ) => versions; - protected static IEnumerable Deprecated( params ApiVersion[] versions ) => versions.AsEnumerable(); + protected static ApiVersion[] Deprecated( params ApiVersion[] versions ) => versions; protected static ApiVersion Expected( ApiVersion version ) => version; } \ No newline at end of file diff --git a/src/Common/test/Common.Tests/TestSerializer.cs b/src/Common/test/Common.Tests/TestSerializer.cs new file mode 100644 index 00000000..98992e5b --- /dev/null +++ b/src/Common/test/Common.Tests/TestSerializer.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +using Asp.Versioning; + +[assembly: RegisterXunitSerializer( typeof( TestSerializer ), typeof( ApiVersion ) )] + +namespace Asp.Versioning; + +public sealed class TestSerializer : XunitSerializer +{ + public override string Serialize( ApiVersion value ) => value?.ToString() ?? string.Empty; + + public override ApiVersion Deserialize( Type type, string serializedValue ) => + ApiVersionParser.Default.Parse( serializedValue ); +} \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index d36d3be8..d3f5e202 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,8 +2,8 @@ - net8.0 - 8.0 + net10.0 + 10.0 $(DotNetReleaseBasePackageVersion).0 .net $([MSBuild]::EnsureTrailingSlash($([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), .gitignore)))) diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 53ff4535..eebad0ba 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -26,7 +26,7 @@ - +