Add support for conventions in DefaultApiDescriptionProvider

This commit is contained in:
Pranav K 2018-06-07 12:06:27 -07:00
parent dc5bfd1b0b
commit 6c2ef122f8
12 changed files with 1195 additions and 238 deletions

View File

@ -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>

View File

@ -1,2 +1,2 @@
version:2.2.0-preview1-17081
commithash:73f09c256e2a54270951562ecc0ef4a953926c36
version:2.2.0-preview1-17082
commithash:13b85a32c7aa9d62f6f3cc211c5c7c566d16b3dd

View File

@ -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);
}
}
}

View File

@ -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(

View File

@ -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; }
}
}

View File

@ -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

View File

@ -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) { }
}
}

View File

@ -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)
{

View File

@ -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);

View File

@ -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>

View File

@ -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) { }
}
}
}

View File

@ -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)