Add support for conventions in DefaultApiDescriptionProvider
This commit is contained in:
parent
dc5bfd1b0b
commit
6c2ef122f8
|
|
@ -5,84 +5,84 @@
|
|||
<PropertyGroup Label="Package Versions">
|
||||
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
|
||||
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
|
||||
<InternalAspNetCoreAnalyzersPackageVersion>2.2.0-preview1-34411</InternalAspNetCoreAnalyzersPackageVersion>
|
||||
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview1-17081</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
|
||||
<MicrosoftAspNetCoreAntiforgeryPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreAntiforgeryPackageVersion>
|
||||
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
|
||||
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
|
||||
<MicrosoftAspNetCoreAuthenticationPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreAuthenticationPackageVersion>
|
||||
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
|
||||
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreCookiePolicyPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreCookiePolicyPackageVersion>
|
||||
<MicrosoftAspNetCoreCorsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreCorsPackageVersion>
|
||||
<MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreDiagnosticsPackageVersion>
|
||||
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHostingPackageVersion>
|
||||
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHttpPackageVersion>
|
||||
<MicrosoftAspNetCoreJsonPatchPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreJsonPatchPackageVersion>
|
||||
<MicrosoftAspNetCoreLocalizationPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreLocalizationPackageVersion>
|
||||
<MicrosoftAspNetCoreLocalizationRoutingPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreLocalizationRoutingPackageVersion>
|
||||
<MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreRazorDesignPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreRazorDesignPackageVersion>
|
||||
<MicrosoftAspNetCoreRazorLanguagePackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreRazorLanguagePackageVersion>
|
||||
<MicrosoftAspNetCoreRazorRuntimePackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreRazorRuntimePackageVersion>
|
||||
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreResponseCachingPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreResponseCachingPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreRoutingPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||
<MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreSessionPackageVersion>
|
||||
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
||||
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreTestingPackageVersion>
|
||||
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
|
||||
<InternalAspNetCoreAnalyzersPackageVersion>2.2.0-preview1-34425</InternalAspNetCoreAnalyzersPackageVersion>
|
||||
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview1-17082</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
|
||||
<MicrosoftAspNetCoreAntiforgeryPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreAntiforgeryPackageVersion>
|
||||
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
|
||||
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
|
||||
<MicrosoftAspNetCoreAuthenticationPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreAuthenticationPackageVersion>
|
||||
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
|
||||
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreCookiePolicyPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreCookiePolicyPackageVersion>
|
||||
<MicrosoftAspNetCoreCorsPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreCorsPackageVersion>
|
||||
<MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreDiagnosticsPackageVersion>
|
||||
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreHostingPackageVersion>
|
||||
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreHttpPackageVersion>
|
||||
<MicrosoftAspNetCoreJsonPatchPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreJsonPatchPackageVersion>
|
||||
<MicrosoftAspNetCoreLocalizationPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreLocalizationPackageVersion>
|
||||
<MicrosoftAspNetCoreLocalizationRoutingPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreLocalizationRoutingPackageVersion>
|
||||
<MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreRazorDesignPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreRazorDesignPackageVersion>
|
||||
<MicrosoftAspNetCoreRazorLanguagePackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreRazorLanguagePackageVersion>
|
||||
<MicrosoftAspNetCoreRazorRuntimePackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreRazorRuntimePackageVersion>
|
||||
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreResponseCachingPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreResponseCachingPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreRoutingPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||
<MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreSessionPackageVersion>
|
||||
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
||||
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreTestingPackageVersion>
|
||||
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.2.0-preview1-34425</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
|
||||
<MicrosoftAspNetWebApiClientPackageVersion>5.2.6</MicrosoftAspNetWebApiClientPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpPackageVersion>
|
||||
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
|
||||
<MicrosoftCodeAnalysisRazorPackageVersion>2.2.0-preview1-34411</MicrosoftCodeAnalysisRazorPackageVersion>
|
||||
<MicrosoftCodeAnalysisRazorPackageVersion>2.2.0-preview1-34425</MicrosoftCodeAnalysisRazorPackageVersion>
|
||||
<MicrosoftDiaSymReaderNativePackageVersion>1.7.0</MicrosoftDiaSymReaderNativePackageVersion>
|
||||
<MicrosoftExtensionsCachingMemoryPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsCachingMemoryPackageVersion>
|
||||
<MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>
|
||||
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsConfigurationJsonPackageVersion>
|
||||
<MicrosoftExtensionsConfigurationPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsConfigurationPackageVersion>
|
||||
<MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
||||
<MicrosoftExtensionsCachingMemoryPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsCachingMemoryPackageVersion>
|
||||
<MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>
|
||||
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsConfigurationJsonPackageVersion>
|
||||
<MicrosoftExtensionsConfigurationPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsConfigurationPackageVersion>
|
||||
<MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
||||
<MicrosoftExtensionsDependencyModelPackageVersion>2.2.0-preview1-26606-01</MicrosoftExtensionsDependencyModelPackageVersion>
|
||||
<MicrosoftExtensionsDiagnosticAdapterPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsDiagnosticAdapterPackageVersion>
|
||||
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsFileProvidersCompositePackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsFileProvidersCompositePackageVersion>
|
||||
<MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>
|
||||
<MicrosoftExtensionsFileSystemGlobbingPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsFileSystemGlobbingPackageVersion>
|
||||
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
|
||||
<MicrosoftExtensionsLocalizationPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLocalizationPackageVersion>
|
||||
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||
<MicrosoftExtensionsLoggingDebugPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLoggingDebugPackageVersion>
|
||||
<MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLoggingPackageVersion>
|
||||
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLoggingTestingPackageVersion>
|
||||
<MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>
|
||||
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsOptionsPackageVersion>
|
||||
<MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>
|
||||
<MicrosoftExtensionsPrimitivesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsPrimitivesPackageVersion>
|
||||
<MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>
|
||||
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
|
||||
<MicrosoftExtensionsSecurityHelperSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsSecurityHelperSourcesPackageVersion>
|
||||
<MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>
|
||||
<MicrosoftExtensionsValueStopwatchSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsValueStopwatchSourcesPackageVersion>
|
||||
<MicrosoftExtensionsWebEncodersPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsWebEncodersPackageVersion>
|
||||
<MicrosoftExtensionsDiagnosticAdapterPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsDiagnosticAdapterPackageVersion>
|
||||
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsFileProvidersCompositePackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsFileProvidersCompositePackageVersion>
|
||||
<MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>
|
||||
<MicrosoftExtensionsFileSystemGlobbingPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsFileSystemGlobbingPackageVersion>
|
||||
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
|
||||
<MicrosoftExtensionsLocalizationPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsLocalizationPackageVersion>
|
||||
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||
<MicrosoftExtensionsLoggingDebugPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsLoggingDebugPackageVersion>
|
||||
<MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsLoggingPackageVersion>
|
||||
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsLoggingTestingPackageVersion>
|
||||
<MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>
|
||||
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsOptionsPackageVersion>
|
||||
<MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>
|
||||
<MicrosoftExtensionsPrimitivesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsPrimitivesPackageVersion>
|
||||
<MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>
|
||||
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
|
||||
<MicrosoftExtensionsSecurityHelperSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsSecurityHelperSourcesPackageVersion>
|
||||
<MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>
|
||||
<MicrosoftExtensionsValueStopwatchSourcesPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsValueStopwatchSourcesPackageVersion>
|
||||
<MicrosoftExtensionsWebEncodersPackageVersion>2.2.0-preview1-34425</MicrosoftExtensionsWebEncodersPackageVersion>
|
||||
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
||||
<MicrosoftNETCoreApp21PackageVersion>2.1.0</MicrosoftNETCoreApp21PackageVersion>
|
||||
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26606-01</MicrosoftNETCoreApp22PackageVersion>
|
||||
<MicrosoftNetHttpHeadersPackageVersion>2.2.0-preview1-34411</MicrosoftNetHttpHeadersPackageVersion>
|
||||
<MicrosoftNETSdkRazorPackageVersion>2.2.0-preview1-34411</MicrosoftNETSdkRazorPackageVersion>
|
||||
<MicrosoftNetHttpHeadersPackageVersion>2.2.0-preview1-34425</MicrosoftNetHttpHeadersPackageVersion>
|
||||
<MicrosoftNETSdkRazorPackageVersion>2.2.0-preview1-34425</MicrosoftNETSdkRazorPackageVersion>
|
||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||
<MoqPackageVersion>4.7.49</MoqPackageVersion>
|
||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
version:2.2.0-preview1-17081
|
||||
commithash:73f09c256e2a54270951562ecc0ef4a953926c36
|
||||
version:2.2.0-preview1-17082
|
||||
commithash:13b85a32c7aa9d62f6f3cc211c5c7c566d16b3dd
|
||||
|
|
|
|||
|
|
@ -0,0 +1,334 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||
{
|
||||
internal class ApiResponseTypeProvider
|
||||
{
|
||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||
private readonly IActionResultTypeMapper _mapper;
|
||||
private readonly MvcOptions _mvcOptions;
|
||||
|
||||
public ApiResponseTypeProvider(
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
IActionResultTypeMapper mapper,
|
||||
MvcOptions mvcOptions)
|
||||
{
|
||||
_modelMetadataProvider = modelMetadataProvider;
|
||||
_mapper = mapper;
|
||||
_mvcOptions = mvcOptions;
|
||||
}
|
||||
|
||||
public IList<ApiResponseType> GetApiResponseTypes(ControllerActionDescriptor action)
|
||||
{
|
||||
// We only provide response info if we can figure out a type that is a user-data type.
|
||||
// Void /Task object/IActionResult will result in no data.
|
||||
var declaredReturnType = GetDeclaredReturnType(action);
|
||||
|
||||
var runtimeReturnType = GetRuntimeReturnType(declaredReturnType);
|
||||
|
||||
var responseMetadataAttributes = GetResponseMetadataAttributes(action);
|
||||
if (responseMetadataAttributes.Length == 0)
|
||||
{
|
||||
// Action does not have any conventions. Look for conventions on the type.
|
||||
responseMetadataAttributes = GetResponseMetadataAttributesFromConventions(action);
|
||||
}
|
||||
|
||||
var apiResponseTypes = GetApiResponseTypes(responseMetadataAttributes, runtimeReturnType);
|
||||
return apiResponseTypes;
|
||||
}
|
||||
|
||||
private IApiResponseMetadataProvider[] GetResponseMetadataAttributesFromConventions(ControllerActionDescriptor action)
|
||||
{
|
||||
if (action.FilterDescriptors == null)
|
||||
{
|
||||
return Array.Empty<IApiResponseMetadataProvider>();
|
||||
}
|
||||
|
||||
foreach (var filterDescriptor in action.FilterDescriptors)
|
||||
{
|
||||
if (!(filterDescriptor.Filter is ApiConventionAttribute apiConventionAttribute))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var method = GetConventionMethod(action.MethodInfo, apiConventionAttribute.ConventionType);
|
||||
if (method != null)
|
||||
{
|
||||
return method.GetCustomAttributes(inherit: false)
|
||||
.OfType<IApiResponseMetadataProvider>()
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
return Array.Empty<IApiResponseMetadataProvider>();
|
||||
}
|
||||
|
||||
private MethodInfo GetConventionMethod(MethodInfo methodInfo, Type conventions)
|
||||
{
|
||||
var conventionMethods = conventions.GetMethods(BindingFlags.Public | BindingFlags.Static);
|
||||
for (var i = 0; i < conventionMethods.Length; i++)
|
||||
{
|
||||
if (IsMatch(methodInfo, conventionMethods[i]))
|
||||
{
|
||||
return conventionMethods[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IApiResponseMetadataProvider[] GetResponseMetadataAttributes(ControllerActionDescriptor action)
|
||||
{
|
||||
if (action.FilterDescriptors == null)
|
||||
{
|
||||
return Array.Empty<IApiResponseMetadataProvider>();
|
||||
}
|
||||
|
||||
// This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory
|
||||
// while searching for a filter that implements IApiResponseMetadataProvider.
|
||||
//
|
||||
// The workaround for that is to implement the metadata interface on the IFilterFactory.
|
||||
return action.FilterDescriptors
|
||||
.Select(fd => fd.Filter)
|
||||
.OfType<IApiResponseMetadataProvider>()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private IList<ApiResponseType> GetApiResponseTypes(
|
||||
IApiResponseMetadataProvider[] responseMetadataAttributes,
|
||||
Type type)
|
||||
{
|
||||
var results = new List<ApiResponseType>();
|
||||
|
||||
// Build list of all possible return types (and status codes) for an action.
|
||||
var objectTypes = new Dictionary<int, Type>();
|
||||
|
||||
// Get the content type that the action explicitly set to support.
|
||||
// Walk through all 'filter' attributes in order, and allow each one to see or override
|
||||
// the results of the previous ones. This is similar to the execution path for content-negotiation.
|
||||
var contentTypes = new MediaTypeCollection();
|
||||
if (responseMetadataAttributes != null)
|
||||
{
|
||||
foreach (var metadataAttribute in responseMetadataAttributes)
|
||||
{
|
||||
metadataAttribute.SetContentTypes(contentTypes);
|
||||
|
||||
if (metadataAttribute.Type != null)
|
||||
{
|
||||
objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the default status only when no status has already been set explicitly
|
||||
if (objectTypes.Count == 0 && type != null)
|
||||
{
|
||||
objectTypes[StatusCodes.Status200OK] = type;
|
||||
}
|
||||
|
||||
if (contentTypes.Count == 0)
|
||||
{
|
||||
contentTypes.Add((string)null);
|
||||
}
|
||||
|
||||
var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType<IApiResponseTypeMetadataProvider>();
|
||||
|
||||
foreach (var objectType in objectTypes)
|
||||
{
|
||||
if (objectType.Value == null || objectType.Value == typeof(void))
|
||||
{
|
||||
results.Add(new ApiResponseType()
|
||||
{
|
||||
StatusCode = objectType.Key,
|
||||
Type = objectType.Value
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var apiResponseType = new ApiResponseType()
|
||||
{
|
||||
Type = objectType.Value,
|
||||
StatusCode = objectType.Key,
|
||||
ModelMetadata = _modelMetadataProvider.GetMetadataForType(objectType.Value)
|
||||
};
|
||||
|
||||
foreach (var contentType in contentTypes)
|
||||
{
|
||||
foreach (var responseTypeMetadataProvider in responseTypeMetadataProviders)
|
||||
{
|
||||
var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(
|
||||
contentType,
|
||||
objectType.Value);
|
||||
|
||||
if (formatterSupportedContentTypes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var formatterSupportedContentType in formatterSupportedContentTypes)
|
||||
{
|
||||
apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat()
|
||||
{
|
||||
Formatter = (IOutputFormatter)responseTypeMetadataProvider,
|
||||
MediaType = formatterSupportedContentType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(apiResponseType);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private Type GetDeclaredReturnType(ControllerActionDescriptor action)
|
||||
{
|
||||
var declaredReturnType = action.MethodInfo.ReturnType;
|
||||
if (declaredReturnType == typeof(void) ||
|
||||
declaredReturnType == typeof(Task))
|
||||
{
|
||||
return typeof(void);
|
||||
}
|
||||
|
||||
// Unwrap the type if it's a Task<T>. The Task (non-generic) case was already handled.
|
||||
Type unwrappedType = declaredReturnType;
|
||||
if (declaredReturnType.IsGenericType &&
|
||||
declaredReturnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
unwrappedType = declaredReturnType.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
// If the method is declared to return IActionResult or a derived class, that information
|
||||
// isn't valuable to the formatter.
|
||||
if (typeof(IActionResult).IsAssignableFrom(unwrappedType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we get here, the type should be a user-defined data type or an envelope type
|
||||
// like ActionResult<T>. The mapper service will unwrap envelopes.
|
||||
unwrappedType = _mapper.GetResultDataType(unwrappedType);
|
||||
return unwrappedType;
|
||||
}
|
||||
|
||||
private Type GetRuntimeReturnType(Type declaredReturnType)
|
||||
{
|
||||
// If we get here, then a filter didn't give us an answer, so we need to figure out if we
|
||||
// want to use the declared return type.
|
||||
//
|
||||
// We've already excluded Task, void, and IActionResult at this point.
|
||||
//
|
||||
// If the action might return any object, then assume we don't know anything about it.
|
||||
if (declaredReturnType == typeof(object))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return declaredReturnType;
|
||||
}
|
||||
|
||||
internal static bool IsMatch(MethodInfo methodInfo, MethodInfo conventionMethod)
|
||||
{
|
||||
if (!IsMethodNameMatch(methodInfo.Name, conventionMethod.Name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var methodParameters = methodInfo.GetParameters();
|
||||
var conventionMethodParameters = conventionMethod.GetParameters();
|
||||
if (conventionMethodParameters.Length != methodParameters.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < conventionMethodParameters.Length; i++)
|
||||
{
|
||||
if (conventionMethodParameters[i].ParameterType.IsGenericParameter)
|
||||
{
|
||||
// Use TModel as wildcard
|
||||
continue;
|
||||
}
|
||||
else if (!IsParameterNameMatch(methodParameters[i].Name, conventionMethodParameters[i].Name) ||
|
||||
!IsParameterTypeMatch(methodParameters[i].ParameterType, conventionMethodParameters[i].ParameterType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool IsMethodNameMatch(string name, string conventionName)
|
||||
{
|
||||
if (!name.StartsWith(conventionName, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.Length == conventionName.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return char.IsUpper(name[conventionName.Length]);
|
||||
}
|
||||
|
||||
internal static bool IsParameterNameMatch(string name, string conventionName)
|
||||
{
|
||||
// Leading underscores could be used to allow multiple parameter names with the same suffix e.g. GetPersonAddress(int personId, int addressId)
|
||||
// A common convention that allows targeting these category of methods would look like Get(int id, int _id)
|
||||
conventionName = conventionName.Trim('_');
|
||||
|
||||
// name = id, conventionName = id
|
||||
if (string.Equals(name, conventionName, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name.Length <= conventionName.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// name = personId, conventionName = id
|
||||
var index = name.Length - conventionName.Length - 1;
|
||||
if (!char.IsLower(name[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
index++;
|
||||
if (name[index] != char.ToUpper(conventionName[0]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
index++;
|
||||
return string.Compare(name, index, conventionName, 1, conventionName.Length - 1, StringComparison.Ordinal) == 0;
|
||||
}
|
||||
|
||||
internal static bool IsParameterTypeMatch(Type parameterType, Type conventionParameterType)
|
||||
{
|
||||
if (conventionParameterType == typeof(object))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return conventionParameterType.IsAssignableFrom(parameterType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
{
|
||||
private readonly MvcOptions _mvcOptions;
|
||||
private readonly IActionResultTypeMapper _mapper;
|
||||
private readonly ApiResponseTypeProvider _responseTypeProvider;
|
||||
private readonly IInlineConstraintResolver _constraintResolver;
|
||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||
|
||||
|
|
@ -42,10 +43,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
IOptions<MvcOptions> optionsAccessor,
|
||||
IInlineConstraintResolver constraintResolver,
|
||||
IModelMetadataProvider modelMetadataProvider)
|
||||
: this(optionsAccessor, constraintResolver, modelMetadataProvider, null)
|
||||
{
|
||||
_mvcOptions = optionsAccessor.Value;
|
||||
_constraintResolver = constraintResolver;
|
||||
_modelMetadataProvider = modelMetadataProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -66,6 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
_constraintResolver = constraintResolver;
|
||||
_modelMetadataProvider = modelMetadataProvider;
|
||||
_mapper = mapper;
|
||||
_responseTypeProvider = new ApiResponseTypeProvider(modelMetadataProvider, mapper, _mvcOptions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -127,15 +127,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
}
|
||||
|
||||
var requestMetadataAttributes = GetRequestMetadataAttributes(action);
|
||||
var responseMetadataAttributes = GetResponseMetadataAttributes(action);
|
||||
|
||||
// We only provide response info if we can figure out a type that is a user-data type.
|
||||
// Void /Task object/IActionResult will result in no data.
|
||||
var declaredReturnType = GetDeclaredReturnType(action);
|
||||
|
||||
var runtimeReturnType = GetRuntimeReturnType(declaredReturnType);
|
||||
|
||||
var apiResponseTypes = GetApiResponseTypes(responseMetadataAttributes, runtimeReturnType);
|
||||
var apiResponseTypes = _responseTypeProvider.GetApiResponseTypes(action);
|
||||
foreach (var apiResponseType in apiResponseTypes)
|
||||
{
|
||||
apiDescription.SupportedResponseTypes.Add(apiResponseType);
|
||||
|
|
@ -406,142 +399,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
return contentTypes;
|
||||
}
|
||||
|
||||
private IReadOnlyList<ApiResponseType> GetApiResponseTypes(
|
||||
IApiResponseMetadataProvider[] responseMetadataAttributes,
|
||||
Type type)
|
||||
{
|
||||
var results = new List<ApiResponseType>();
|
||||
|
||||
// Build list of all possible return types (and status codes) for an action.
|
||||
var objectTypes = new Dictionary<int, Type>();
|
||||
|
||||
// Get the content type that the action explicitly set to support.
|
||||
// Walk through all 'filter' attributes in order, and allow each one to see or override
|
||||
// the results of the previous ones. This is similar to the execution path for content-negotiation.
|
||||
var contentTypes = new MediaTypeCollection();
|
||||
if (responseMetadataAttributes != null)
|
||||
{
|
||||
foreach (var metadataAttribute in responseMetadataAttributes)
|
||||
{
|
||||
metadataAttribute.SetContentTypes(contentTypes);
|
||||
|
||||
if (metadataAttribute.Type != null)
|
||||
{
|
||||
objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the default status only when no status has already been set explicitly
|
||||
if (objectTypes.Count == 0
|
||||
&& type != null)
|
||||
{
|
||||
objectTypes[StatusCodes.Status200OK] = type;
|
||||
}
|
||||
|
||||
if (contentTypes.Count == 0)
|
||||
{
|
||||
contentTypes.Add((string)null);
|
||||
}
|
||||
|
||||
var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType<IApiResponseTypeMetadataProvider>();
|
||||
|
||||
foreach (var objectType in objectTypes)
|
||||
{
|
||||
if (objectType.Value == typeof(void))
|
||||
{
|
||||
results.Add(new ApiResponseType()
|
||||
{
|
||||
StatusCode = objectType.Key,
|
||||
Type = objectType.Value
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var apiResponseType = new ApiResponseType()
|
||||
{
|
||||
Type = objectType.Value,
|
||||
StatusCode = objectType.Key,
|
||||
ModelMetadata = _modelMetadataProvider.GetMetadataForType(objectType.Value)
|
||||
};
|
||||
|
||||
foreach (var contentType in contentTypes)
|
||||
{
|
||||
foreach (var responseTypeMetadataProvider in responseTypeMetadataProviders)
|
||||
{
|
||||
var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(
|
||||
contentType,
|
||||
objectType.Value);
|
||||
|
||||
if (formatterSupportedContentTypes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var formatterSupportedContentType in formatterSupportedContentTypes)
|
||||
{
|
||||
apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat()
|
||||
{
|
||||
Formatter = (IOutputFormatter)responseTypeMetadataProvider,
|
||||
MediaType = formatterSupportedContentType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(apiResponseType);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private Type GetDeclaredReturnType(ControllerActionDescriptor action)
|
||||
{
|
||||
var declaredReturnType = action.MethodInfo.ReturnType;
|
||||
if (declaredReturnType == typeof(void) ||
|
||||
declaredReturnType == typeof(Task))
|
||||
{
|
||||
return typeof(void);
|
||||
}
|
||||
|
||||
// Unwrap the type if it's a Task<T>. The Task (non-generic) case was already handled.
|
||||
Type unwrappedType = declaredReturnType;
|
||||
if (declaredReturnType.IsGenericType &&
|
||||
declaredReturnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
unwrappedType = declaredReturnType.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
// If the method is declared to return IActionResult or a derived class, that information
|
||||
// isn't valuable to the formatter.
|
||||
if (typeof(IActionResult).IsAssignableFrom(unwrappedType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we get here, the type should be a user-defined data type or an envelope type
|
||||
// like ActionResult<T>. The mapper service will unwrap envelopes.
|
||||
unwrappedType = _mapper.GetResultDataType(unwrappedType);
|
||||
return unwrappedType;
|
||||
}
|
||||
|
||||
private Type GetRuntimeReturnType(Type declaredReturnType)
|
||||
{
|
||||
// If we get here, then a filter didn't give us an answer, so we need to figure out if we
|
||||
// want to use the declared return type.
|
||||
//
|
||||
// We've already excluded Task, void, and IActionResult at this point.
|
||||
//
|
||||
// If the action might return any object, then assume we don't know anything about it.
|
||||
if (declaredReturnType == typeof(object))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return declaredReturnType;
|
||||
}
|
||||
|
||||
private IApiRequestMetadataProvider[] GetRequestMetadataAttributes(ControllerActionDescriptor action)
|
||||
{
|
||||
if (action.FilterDescriptors == null)
|
||||
|
|
@ -559,23 +416,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
.ToArray();
|
||||
}
|
||||
|
||||
private IApiResponseMetadataProvider[] GetResponseMetadataAttributes(ControllerActionDescriptor action)
|
||||
{
|
||||
if (action.FilterDescriptors == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory
|
||||
// while searching for a filter that implements IApiResponseMetadataProvider.
|
||||
//
|
||||
// The workaround for that is to implement the metadata interface on the IFilterFactory.
|
||||
return action.FilterDescriptors
|
||||
.Select(fd => fd.Filter)
|
||||
.OfType<IApiResponseMetadataProvider>()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private class ApiParameterContext
|
||||
{
|
||||
public ApiParameterContext(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||
public sealed class ApiConventionAttribute : Attribute, IFilterMetadata
|
||||
{
|
||||
public ApiConventionAttribute(Type conventionType)
|
||||
{
|
||||
ConventionType = conventionType ?? throw new ArgumentNullException(nameof(conventionType));
|
||||
|
||||
if (!ConventionType.IsSealed || !ConventionType.IsAbstract)
|
||||
{
|
||||
// Conventions must be static viz abstract + sealed.
|
||||
throw new ArgumentException(Resources.FormatApiConventionMustBeStatic(conventionType), nameof(conventionType));
|
||||
}
|
||||
}
|
||||
|
||||
public Type ConventionType { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +84,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
|
||||
public IReadOnlyList<object> Attributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ControllerModel"/>.
|
||||
/// </summary>
|
||||
public ControllerModel Controller { get; set; }
|
||||
|
||||
public IList<IFilterMetadata> Filters { get; }
|
||||
|
|
@ -123,6 +126,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
|
||||
string ICommonModel.Name => ActionName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="SelectorModel"/> instances.
|
||||
/// </summary>
|
||||
public IList<SelectorModel> Selectors { get; }
|
||||
|
||||
public string DisplayName
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
public static class DefaultApiConventions
|
||||
{
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public static void Get<TModel>(object id) { }
|
||||
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public static void Get<TModel>() { }
|
||||
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public static void Post<TModel>(TModel model) { }
|
||||
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public static void Put<TModel>(object id, TModel model) { }
|
||||
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public static void Delete<TModel>(object id) { }
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
|
|
@ -67,6 +68,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
if (isApiController)
|
||||
{
|
||||
InferBoundPropertyModelPrefixes(controllerModel);
|
||||
|
||||
AddGloballyConfiguredApiConventions(controllerModel);
|
||||
}
|
||||
|
||||
var controllerHasSelectorModel = controllerModel.Selectors.Any(s => s.AttributeRouteModel != null);
|
||||
|
|
@ -91,6 +94,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
}
|
||||
|
||||
internal static void AddGloballyConfiguredApiConventions(ControllerModel controllerModel)
|
||||
{
|
||||
if (controllerModel.Filters.OfType<ApiConventionAttribute>().Any())
|
||||
{
|
||||
// ApiControllerAttribute is already associated with controller. Do not look for conventions configured at assembly.
|
||||
return;
|
||||
}
|
||||
|
||||
var assembly = controllerModel.ControllerType.Assembly;
|
||||
foreach (var attribute in assembly.GetCustomAttributes<ApiConventionAttribute>())
|
||||
{
|
||||
controllerModel.Filters.Add(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for unit testing
|
||||
internal void AddMultipartFormDataConsumesAttribute(ActionModel actionModel)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1453,7 +1453,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
=> string.Format(CultureInfo.CurrentCulture, GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForParameter"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// Action '{0}' has more than one parameter that were specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify query string bound, '{2}' to specify route bound, and '{3}' for parameters to be bound from body:
|
||||
/// Action '{0}' has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify bound from query, '{2}' to specify bound from route, and '{3}' for parameters to be bound from body:
|
||||
/// </summary>
|
||||
internal static string ApiController_MultipleBodyParametersFound
|
||||
{
|
||||
|
|
@ -1461,11 +1461,25 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action '{0}' has more than one parameter that were specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify query string bound, '{2}' to specify route bound, and '{3}' for parameters to be bound from body:
|
||||
/// Action '{0}' has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify bound from query, '{2}' to specify bound from route, and '{3}' for parameters to be bound from body:
|
||||
/// </summary>
|
||||
internal static string FormatApiController_MultipleBodyParametersFound(object p0, object p1, object p2, object p3)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ApiController_MultipleBodyParametersFound"), p0, p1, p2, p3);
|
||||
|
||||
/// <summary>
|
||||
/// API convention type '{0}' must be a static type.
|
||||
/// </summary>
|
||||
internal static string ApiConventionMustBeStatic
|
||||
{
|
||||
get => GetString("ApiConventionMustBeStatic");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API convention type '{0}' must be a static type.
|
||||
/// </summary>
|
||||
internal static string FormatApiConventionMustBeStatic(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMustBeStatic"), p0);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -442,4 +442,7 @@
|
|||
<data name="ApiController_MultipleBodyParametersFound" xml:space="preserve">
|
||||
<value>Action '{0}' has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify bound from query, '{2}' to specify bound from route, and '{3}' for parameters to be bound from body:</value>
|
||||
</data>
|
||||
<data name="ApiConventionMustBeStatic" xml:space="preserve">
|
||||
<value>API convention type '{0}' must be a static type.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,631 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||
{
|
||||
public class ApiResponseTypeProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("id", "model")]
|
||||
[InlineData("id", "person")]
|
||||
[InlineData("id", "i")]
|
||||
public void IsParameterNameMatch_ReturnsFalse_IfConventionNameIsNotSuffix(string parameterName, string conventionName)
|
||||
{
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsParameterNameMatch_ReturnsFalse_IfConventionNameIsNotExactCaseSensitiveMatch()
|
||||
{
|
||||
// Arrange
|
||||
var parameterName = "Id";
|
||||
var conventionName = "id";
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("rid", "id")]
|
||||
[InlineData("candid", "id")]
|
||||
[InlineData("colocation", "location")]
|
||||
public void IsParamterNameMatch_ReturnsFalse_IfConventionNameIsNotProperSuffix(string parameterName, string conventionName)
|
||||
{
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("id", "id")]
|
||||
[InlineData("model", "model")]
|
||||
public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsExactMatch(string parameterName, string conventionName)
|
||||
{
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("id", "_id")]
|
||||
[InlineData("model", "_model")]
|
||||
public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsExactMatchIgnoringLeadingUnderscores(string parameterName, string conventionName)
|
||||
{
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("personId", "id")]
|
||||
[InlineData("userModel", "model")]
|
||||
[InlineData("beaconLocation", "Location")]
|
||||
public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsProperSuffix(string parameterName, string conventionName)
|
||||
{
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("personId", "_id")]
|
||||
[InlineData("userModel", "_model")]
|
||||
[InlineData("userModel", "__model")]
|
||||
public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsProperSuffixIgnoringLeadingUnderscores(string parameterName, string conventionName)
|
||||
{
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsParameterTypeMatch_ReturnsFalse_ForUnrelatedTypes()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(string);
|
||||
var conventionType = typeof(int);
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsParameterTypeMatch_ReturnsFalse_IfTypeIsBaseClassOfConvention()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(BaseModel);
|
||||
var conventionType = typeof(DerivedModel);
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsParameterTypeMatch_ReturnsTrue_IfTypeIsExact()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(Uri);
|
||||
var conventionType = typeof(Uri);
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsParameterTypeMatch_ReturnsTrue_IfTypeIsSubtypeOfConvention()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(DerivedModel);
|
||||
var conventionType = typeof(BaseModel);
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(int))]
|
||||
[InlineData(typeof(DerivedModel))]
|
||||
public void IsParameterTypeMatch_ReturnsTrue_IfConventionTypeIsObject(Type type)
|
||||
{
|
||||
// Arrange
|
||||
var conventionType = typeof(object);
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Get", "Post")]
|
||||
[InlineData("Post", "Get")]
|
||||
[InlineData("PostPerson", "Put")]
|
||||
public void IsMethodNameMatch_ReturnsFalse_IfMethodIsNotPrefix(string methodName, string conventionMethodName)
|
||||
{
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PostalService", "Post")]
|
||||
[InlineData("Listings", "List")]
|
||||
[InlineData("Putt", "Put")]
|
||||
public void IsMethodNameMatch_ReturnsFalse_IfMethodIsNotProperPrefix(string methodName, string conventionMethodName)
|
||||
{
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMethodNameMatch_ReturnsTrue_IfMethodNameIsExactMatch()
|
||||
{
|
||||
// Arrange
|
||||
var methodName = "Post";
|
||||
var conventionMethodName = "Post";
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMethodNameMatch_ReturnsFalse_IfMethodNameIsExactMatchWithDifferentCasing()
|
||||
{
|
||||
// Arrange
|
||||
var methodName = "post";
|
||||
var conventionMethodName = "Post";
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PostPerson", "Post")]
|
||||
[InlineData("GetById", "Get")]
|
||||
[InlineData("SearchList", "Search")]
|
||||
public void IsMethodNameMatch_ReturnsTrue_IfMethodNameIsProperSuffix(string methodName, string conventionMethodName)
|
||||
{
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMethodNameMatch_ReturnsFalse_IfMethodNameIsProperSuffix_WithDifferentCasing()
|
||||
{
|
||||
// Arrange
|
||||
var methodName = "getById";
|
||||
var conventionMethodName = "Get";
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMatch_ReturnsFalse_IfMethodNamesAreNotMatches()
|
||||
{
|
||||
// Arrange
|
||||
var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Post));
|
||||
var method = typeof(TestController).GetMethod(nameof(TestController.GetUser));
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMatch_ReturnsFalse_IfParameterCountsDoNotMatch()
|
||||
{
|
||||
// Arrange
|
||||
var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Get), new[] { typeof(object) });
|
||||
var method = typeof(TestController).GetMethod(nameof(TestController.GetUserLocation));
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMatch_ReturnsTrue_ForMethodWithObjectParameter()
|
||||
{
|
||||
// Arrange
|
||||
var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Get), new[] { typeof(object) });
|
||||
var method = typeof(TestController).GetMethod(nameof(TestController.GetUser));
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMatch_ReturnsTrue_ForConventionWithGenericParameter()
|
||||
{
|
||||
// Arrange
|
||||
var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Put));
|
||||
var method = typeof(TestController).GetMethod(nameof(TestController.PutModel));
|
||||
|
||||
// Act
|
||||
var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresent()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController.Get));
|
||||
|
||||
var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller);
|
||||
actionDescriptor.FilterDescriptors.Add(filter);
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(301, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(404, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
});
|
||||
}
|
||||
|
||||
[ApiConvention(typeof(DefaultApiConventions))]
|
||||
public class GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController : ControllerBase
|
||||
{
|
||||
[Produces(typeof(BaseModel))]
|
||||
[ProducesResponseType(301)]
|
||||
[ProducesResponseType(404)]
|
||||
public Task<ActionResult<BaseModel>> Get(int id) => null;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventions()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase));
|
||||
var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller);
|
||||
actionDescriptor.FilterDescriptors.Add(filter);
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(404, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
});
|
||||
}
|
||||
|
||||
[ApiConvention(typeof(DefaultApiConventions))]
|
||||
public class GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController : ControllerBase
|
||||
{
|
||||
public Task<ActionResult<BaseModel>> DeleteBase(int id) => null;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_ReturnsResponseTypesFromCustomConventions()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromCustomConventionsController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromCustomConventionsController.SearchModel));
|
||||
var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(SearchApiConventions)), FilterScope.Controller);
|
||||
actionDescriptor.FilterDescriptors.Add(filter);
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(206, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(406, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
});
|
||||
}
|
||||
|
||||
[ApiConvention(typeof(SearchApiConventions))]
|
||||
public class GetApiResponseTypes_ReturnsResponseTypesFromCustomConventionsController : ControllerBase
|
||||
{
|
||||
public Task<ActionResult<BaseModel>> SearchModel(string searchTerm, int page) => null;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConvention_WhenMultipleConventionsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController.SearchModel));
|
||||
var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller);
|
||||
actionDescriptor.FilterDescriptors.Add(filter);
|
||||
filter = new FilterDescriptor(new ApiConventionAttribute(typeof(SearchApiConventions)), FilterScope.Controller);
|
||||
actionDescriptor.FilterDescriptors.Add(filter);
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(206, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(406, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
});
|
||||
}
|
||||
|
||||
[ApiConvention(typeof(DefaultApiConventions))]
|
||||
[ApiConvention(typeof(SearchApiConventions))]
|
||||
public class GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController : ControllerBase
|
||||
{
|
||||
public Task<ActionResult<BaseModel>> Get(int id) => null;
|
||||
|
||||
public Task<ActionResult<BaseModel>> SearchModel(string searchTerm, int page) => null;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_ReturnsResponseTypesFromDefaultConvention_WhenMultipleConventionsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController.Get));
|
||||
var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller);
|
||||
actionDescriptor.FilterDescriptors.Add(filter);
|
||||
filter = new FilterDescriptor(new ApiConventionAttribute(typeof(SearchApiConventions)), FilterScope.Controller);
|
||||
actionDescriptor.FilterDescriptors.Add(filter);
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(404, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatch()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatchController),
|
||||
nameof(GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatchController.PostModel));
|
||||
var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller);
|
||||
actionDescriptor.FilterDescriptors.Add(filter);
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
});
|
||||
}
|
||||
|
||||
[ApiConvention(typeof(DefaultApiConventions))]
|
||||
public class GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatchController : ControllerBase
|
||||
{
|
||||
public Task<ActionResult<BaseModel>> PostModel(int id, BaseModel model) => null;
|
||||
}
|
||||
|
||||
private static ApiResponseTypeProvider GetProvider()
|
||||
{
|
||||
var mvcOptions = new MvcOptions
|
||||
{
|
||||
OutputFormatters = { new TestOutputFormatter() },
|
||||
};
|
||||
var provider = new ApiResponseTypeProvider(new EmptyModelMetadataProvider(), new ActionResultTypeMapper(), mvcOptions);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static ControllerActionDescriptor GetControllerActionDescriptor(Type type, string name)
|
||||
{
|
||||
var method = type.GetMethod(name);
|
||||
var actionDescriptor = new ControllerActionDescriptor
|
||||
{
|
||||
MethodInfo = method,
|
||||
FilterDescriptors = new List<FilterDescriptor>(),
|
||||
};
|
||||
|
||||
foreach (var filterAttribute in method.GetCustomAttributes().OfType<IFilterMetadata>())
|
||||
{
|
||||
actionDescriptor.FilterDescriptors.Add(new FilterDescriptor(filterAttribute, FilterScope.Action));
|
||||
}
|
||||
|
||||
return actionDescriptor;
|
||||
}
|
||||
|
||||
public class BaseModel { }
|
||||
|
||||
public class DerivedModel : BaseModel { }
|
||||
|
||||
public class TestController
|
||||
{
|
||||
public ActionResult<DerivedModel> GetUser(int id) => null;
|
||||
|
||||
public ActionResult<DerivedModel> GetUserLocation(int a, int b) => null;
|
||||
|
||||
public ActionResult<DerivedModel> PutModel(string userId, DerivedModel model) => null;
|
||||
}
|
||||
|
||||
private class TestOutputFormatter : OutputFormatter
|
||||
{
|
||||
public TestOutputFormatter()
|
||||
{
|
||||
SupportedMediaTypes.Add(new Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||
}
|
||||
|
||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static class SearchApiConventions
|
||||
{
|
||||
[ProducesResponseType(206)]
|
||||
[ProducesResponseType(406)]
|
||||
public static void Search(object searchTerm, int page) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -873,6 +874,59 @@ Environment.NewLine + "int b";
|
|||
Assert.Equal("multipart/form-data", Assert.Single(consumesAttribute.ContentTypes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApiConventionAttributeIsNotAdded_IfModelAlreadyHasAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var attribute = new ApiConventionAttribute(typeof(DefaultApiConventions));
|
||||
var controllerType = CreateTestControllerType();
|
||||
|
||||
var model = new ControllerModel(controllerType.GetTypeInfo(), new[] { attribute })
|
||||
{
|
||||
Filters = { attribute, },
|
||||
};
|
||||
|
||||
// Act
|
||||
ApiBehaviorApplicationModelProvider.AddGloballyConfiguredApiConventions(model);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
model.Filters,
|
||||
filter => Assert.Same(attribute, filter));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApiConventionAttributeIsAdded_IfAttributeExistsInAssembly()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = CreateTestControllerType();
|
||||
var model = new ControllerModel(controllerType.GetTypeInfo(), Array.Empty<object>());
|
||||
|
||||
// Act
|
||||
ApiBehaviorApplicationModelProvider.AddGloballyConfiguredApiConventions(model);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
model.Filters,
|
||||
filter => Assert.IsType<ApiConventionAttribute>(filter));
|
||||
}
|
||||
|
||||
// A dynamically generated type in an assembly that has an ApiConventionAttribute.
|
||||
private static TypeBuilder CreateTestControllerType()
|
||||
{
|
||||
var attributeBuilder = new CustomAttributeBuilder(
|
||||
typeof(ApiConventionAttribute).GetConstructor(new[] { typeof(Type) }),
|
||||
new[] { typeof(DefaultApiConventions) });
|
||||
|
||||
var assemblyName = new AssemblyName("TestAssembly");
|
||||
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
|
||||
assemblyBuilder.SetCustomAttribute(attributeBuilder);
|
||||
|
||||
var module = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
|
||||
var controllerType = module.DefineType("TestController");
|
||||
return controllerType;
|
||||
}
|
||||
|
||||
private static ApiBehaviorApplicationModelProvider GetProvider(
|
||||
ApiBehaviorOptions options = null,
|
||||
IModelMetadataProvider modelMetadataProvider = null)
|
||||
|
|
|
|||
Loading…
Reference in New Issue