From 28e55fd22aad36c361f0fe514b1ba0f362d30fcc Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 21 Jun 2018 11:36:20 -0700 Subject: [PATCH 1/2] Add IsRequired and DefaultValue to ApiParameterDescription --- .../ApiExplorer/ApiParameterDescription.cs | 16 ++ .../ApiParameterContext.cs | 33 ++++ .../DefaultApiDescriptionProvider.cs | 84 ++++++--- .../Internal/ParameterDefaultValues.cs | 29 ++- .../Metadata/DefaultModelMetadata.cs | 2 +- .../DefaultApiDescriptionProviderTest.cs | 169 ++++++++++++++++++ .../ApiExplorerTest.cs | 74 ++++++++ .../ApiExplorerDataFilter.cs | 10 +- .../ApiExplorerParametersController.cs | 10 ++ .../ApiExplorerWebSite/Models/Product.cs | 5 + 10 files changed, 393 insertions(+), 39 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs index 1f07d2b7ab..5c345c3c2f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs @@ -41,5 +41,21 @@ public class ApiParameterDescription /// Gets or sets the parameter descriptor. /// public ParameterDescriptor ParameterDescriptor { get; set; } + + /// + /// Gets or sets a value that determines if the parameter is required. + /// + /// + /// A parameter is considered required if + /// a) If it's bound from the request body (). + /// b) If it's a required route value. + /// c) If it has annotations (e.g. BindRequiredAttribute) that indicate it's required. + /// + public bool IsRequired { get; set; } + + /// + /// Gets or sets the default value for a parameter. + /// + public object DefaultValue { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterContext.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterContext.cs new file mode 100644 index 0000000000..d88281f490 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterContext.cs @@ -0,0 +1,33 @@ +// 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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Routing.Template; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + internal class ApiParameterContext + { + public ApiParameterContext( + IModelMetadataProvider metadataProvider, + ControllerActionDescriptor actionDescriptor, + IReadOnlyList routeParameters) + { + MetadataProvider = metadataProvider; + ActionDescriptor = actionDescriptor; + RouteParameters = routeParameters; + + Results = new List(); + } + + public ControllerActionDescriptor ActionDescriptor { get; } + + public IModelMetadataProvider MetadataProvider { get; } + + public IList Results { get; } + + public IReadOnlyList RouteParameters { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index 2b89a26e2c..f00fb54032 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Formatters; @@ -23,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// Implements a provider of for actions represented /// by . /// - public class DefaultApiDescriptionProvider : IApiDescriptionProvider + public partial class DefaultApiDescriptionProvider : IApiDescriptionProvider { private readonly MvcOptions _mvcOptions; private readonly IActionResultTypeMapper _mapper; @@ -177,7 +175,7 @@ private IList GetParameters(ApiParameterContext context { var visitor = new PseudoModelBindingVisitor(context, actionParameter); - ModelMetadata metadata = null; + ModelMetadata metadata; if (_mvcOptions.AllowValidatingTopLevelNodes && actionParameter is ControllerParameterDescriptor controllerParameterDescriptor && _modelMetadataProvider is ModelMetadataProvider provider) @@ -232,6 +230,21 @@ actionParameter is ControllerParameterDescriptor controllerParameterDescriptor & } // Next, we want to join up any route parameters with those discovered from the action's parameters. + // This will result us in creating a parameter representation for each route parameter that does not + // have a mapping parameter or bound property. + ProcessRouteParameters(context); + + // Set IsRequired=true + ProcessIsRequired(context); + + // Set DefaultValue + ProcessParameterDefaultValue(context); + + return context.Results; + } + + private void ProcessRouteParameters(ApiParameterContext context) + { var routeParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var routeParameter in context.RouteParameters) { @@ -271,8 +284,46 @@ actionParameter is ControllerParameterDescriptor controllerParameterDescriptor & Source = BindingSource.Path, }); } + } - return context.Results; + internal static void ProcessIsRequired(ApiParameterContext context) + { + foreach (var parameter in context.Results) + { + if (parameter.Source == BindingSource.Body) + { + parameter.IsRequired = true; + } + + if (parameter.ModelMetadata != null && parameter.ModelMetadata.IsBindingRequired) + { + parameter.IsRequired = true; + } + + if (parameter.Source == BindingSource.Path && parameter.RouteInfo != null && !parameter.RouteInfo.IsOptional) + { + parameter.IsRequired = true; + } + } + } + + internal static void ProcessParameterDefaultValue(ApiParameterContext context) + { + foreach (var parameter in context.Results) + { + if (parameter.Source == BindingSource.Path) + { + parameter.DefaultValue = parameter.RouteInfo?.DefaultValue; + } + else + { + if (parameter.ParameterDescriptor is ControllerParameterDescriptor controllerParameter && + ParameterDefaultValues.TryGetDeclaredParameterDefaultValue(controllerParameter.ParameterInfo, out var defaultValue)) + { + parameter.DefaultValue = defaultValue; + } + } + } } private ApiParameterRouteInfo CreateRouteInfo(TemplatePart routeParameter) @@ -416,29 +467,6 @@ private IApiRequestMetadataProvider[] GetRequestMetadataAttributes(ControllerAct .ToArray(); } - private class ApiParameterContext - { - public ApiParameterContext( - IModelMetadataProvider metadataProvider, - ControllerActionDescriptor actionDescriptor, - IReadOnlyList routeParameters) - { - MetadataProvider = metadataProvider; - ActionDescriptor = actionDescriptor; - RouteParameters = routeParameters; - - Results = new List(); - } - - public ControllerActionDescriptor ActionDescriptor { get; } - - public IModelMetadataProvider MetadataProvider { get; } - - public IList Results { get; } - - public IReadOnlyList RouteParameters { get; } - } - private class ApiParameterDescriptionContext { public ModelMetadata ModelMetadata { get; set; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs index 0d4a641e5f..8263da1650 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs @@ -30,17 +30,30 @@ public static object[] GetParameterDefaultValues(MethodInfo methodInfo) private static object GetParameterDefaultValue(ParameterInfo parameterInfo) { - if (!ParameterDefaultValue.TryGetDefaultValue(parameterInfo, out var defaultValue)) + TryGetDeclaredParameterDefaultValue(parameterInfo, out var defaultValue); + if (defaultValue == null && parameterInfo.ParameterType.IsValueType) { - var defaultValueAttribute = parameterInfo.GetCustomAttribute(inherit: false); - defaultValue = defaultValueAttribute?.Value; - - if (defaultValue == null && parameterInfo.ParameterType.IsValueType) - { - defaultValue = Activator.CreateInstance(parameterInfo.ParameterType); - } + defaultValue = Activator.CreateInstance(parameterInfo.ParameterType); } + return defaultValue; } + + public static bool TryGetDeclaredParameterDefaultValue(ParameterInfo parameterInfo, out object defaultValue) + { + if (ParameterDefaultValue.TryGetDefaultValue(parameterInfo, out defaultValue)) + { + return true; + } + + var defaultValueAttribute = parameterInfo.GetCustomAttribute(inherit: false); + if (defaultValueAttribute != null) + { + defaultValue = defaultValueAttribute.Value; + return true; + } + + return false; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs index bfb1bde8b5..c2c9773625 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs @@ -324,7 +324,7 @@ public override bool IsReadOnly /// public override bool IsRequired - { + { get { if (!_isRequired.HasValue) diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index 4ceafa6995..6b76f9571b 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Constraints; +using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; using Moq; @@ -1416,6 +1417,172 @@ public void GetApiDescription_WithControllerProperties_Merges_ParameterDescripti Assert.Equal(typeof(string), comments.Type); } + [Fact] + public void ProcessIsRequired_SetsTrue_ForFromBodyParameters() + { + // Arrange + var description = new ApiParameterDescription { Source = BindingSource.Body, }; + var context = GetApiParameterContext(description); + + // Act + DefaultApiDescriptionProvider.ProcessIsRequired(context); + + // Assert + Assert.True(description.IsRequired); + } + + [Fact] + public void ProcessIsRequired_SetsTrue_ForParametersWithBindRequired() + { + // Arrange + var description = new ApiParameterDescription + { + Source = BindingSource.Query, + }; + var context = GetApiParameterContext(description); + var modelMetadataProvider = new TestModelMetadataProvider(); + modelMetadataProvider + .ForProperty(nameof(Person.Name)) + .BindingDetails(d => d.IsBindingRequired = true); + description.ModelMetadata = modelMetadataProvider.GetMetadataForProperty(typeof(Person), nameof(Person.Name)); + + // Act + DefaultApiDescriptionProvider.ProcessIsRequired(context); + + // Assert + Assert.True(description.IsRequired); + } + + [Fact] + public void ProcessIsRequired_SetsTrue_ForRequiredRouteParameters() + { + // Arrange + var description = new ApiParameterDescription + { + Source = BindingSource.Path, + RouteInfo = new ApiParameterRouteInfo(), + }; + var context = GetApiParameterContext(description); + + // Act + DefaultApiDescriptionProvider.ProcessIsRequired(context); + + // Assert + Assert.True(description.IsRequired); + } + + [Fact] + public void ProcessIsRequired_DoesNotSetToTrue_ByDefault() + { + // Arrange + var description = new ApiParameterDescription(); + var context = GetApiParameterContext(description); + + // Act + DefaultApiDescriptionProvider.ProcessIsRequired(context); + + // Assert + Assert.False(description.IsRequired); + } + + [Fact] + public void ProcessIsRequired_DoesNotSetToTrue_ForParametersWithValidationRequired() + { + // Arrange + var description = new ApiParameterDescription(); + var context = GetApiParameterContext(description); + var modelMetadataProvider = new TestModelMetadataProvider(); + modelMetadataProvider + .ForProperty(nameof(Person.Name)) + .ValidationDetails(d => d.IsRequired = true); + description.ModelMetadata = modelMetadataProvider.GetMetadataForProperty(typeof(Person), nameof(Person.Name)); + + // Act + DefaultApiDescriptionProvider.ProcessIsRequired(context); + + // Assert + Assert.False(description.IsRequired); + } + + [Fact] + public void ProcessDefaultValue_SetsDefaultRouteValue() + { + // Arrange + var methodInfo = GetType().GetMethod(nameof(ParameterDefaultValue), BindingFlags.Instance | BindingFlags.NonPublic); + var parameterInfo = methodInfo.GetParameters()[0]; + + var defaultValue = new object(); + var description = new ApiParameterDescription + { + Source = BindingSource.Path, + RouteInfo = new ApiParameterRouteInfo { DefaultValue = defaultValue }, + ParameterDescriptor = new ControllerParameterDescriptor + { + ParameterInfo = parameterInfo, + }, + }; + var context = GetApiParameterContext(description); + + // Act + DefaultApiDescriptionProvider.ProcessParameterDefaultValue(context); + + // Assert + Assert.Same(defaultValue, description.DefaultValue); + } + + [Fact] + public void ProcessDefaultValue_SetsDefaultRouteValue_FromParameterInfo() + { + // Arrange + var methodInfo = GetType().GetMethod(nameof(ParameterDefaultValue), BindingFlags.Instance | BindingFlags.NonPublic); + var parameterInfo = methodInfo.GetParameters()[0]; + var description = new ApiParameterDescription + { + Source = BindingSource.Query, + ParameterDescriptor = new ControllerParameterDescriptor + { + ParameterInfo = parameterInfo, + }, + }; + var context = GetApiParameterContext(description); + + // Act + DefaultApiDescriptionProvider.ProcessParameterDefaultValue(context); + + // Assert + Assert.Equal(10, description.DefaultValue); + } + + [Fact] + public void ProcessDefaultValue_DoesNotSpecifyDefaultValueForValueTypes_WhenNoValueIsSpecified() + { + // Arrange + var methodInfo = GetType().GetMethod(nameof(AcceptsId_Query), BindingFlags.Instance | BindingFlags.NonPublic); + var parameterInfo = methodInfo.GetParameters()[0]; + var description = new ApiParameterDescription + { + Source = BindingSource.Query, + ParameterDescriptor = new ControllerParameterDescriptor + { + ParameterInfo = parameterInfo, + }, + }; + var context = GetApiParameterContext(description); + + // Act + DefaultApiDescriptionProvider.ProcessParameterDefaultValue(context); + + // Assert + Assert.Null(description.DefaultValue); + } + + private static ApiParameterContext GetApiParameterContext(ApiParameterDescription description) + { + var context = new ApiParameterContext(new EmptyModelMetadataProvider(), new ControllerActionDescriptor(), new TemplatePart[0]); + context.Results.Add(description); + return context; + } + private IReadOnlyList GetApiDescriptions( ActionDescriptor action, List inputFormatters = null, @@ -1711,6 +1878,8 @@ private void FromBody([FromBody] int id) { } + private void ParameterDefaultValue(int value = 10) { } + private class TestController { [FromRoute] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index 774e0de862..a6a69bfccd 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1,6 +1,7 @@ // 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.Net; @@ -1018,6 +1019,75 @@ public async Task ApiExplorer_Parameters_SimpleTypes_ComplexModel() Assert.Equal(typeof(string).FullName, feedback.Type); } + [Fact] + public async Task ApiExplorer_Parameters_DefaultValue() + { + // Arrange & Act + var response = await Client.GetAsync("ApiExplorerParameters/DefaultValueParameters"); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(body); + + // Assert + var description = Assert.Single(result); + var parameters = description.ParameterDescriptions; + + Assert.Collection( + parameters, + parameter => + { + Assert.Equal("searchTerm", parameter.Name); + Assert.Null(parameter.DefaultValue); + }, + parameter => + { + Assert.Equal("top", parameter.Name); + Assert.Equal("10", parameter.DefaultValue); + }, + parameter => + { + Assert.Equal("searchDay", parameter.Name); + Assert.Equal(nameof(DayOfWeek.Wednesday), parameter.DefaultValue); + }); + } + + [Fact] + public async Task ApiExplorer_Parameters_IsRequired() + { + // Arrange & Act + var response = await Client.GetAsync("ApiExplorerParameters/IsRequiredParameters"); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(body); + + // Assert + var description = Assert.Single(result); + var parameters = description.ParameterDescriptions; + + Assert.Collection( + parameters, + parameter => + { + Assert.Equal("requiredParam", parameter.Name); + Assert.True(parameter.IsRequired); + }, + parameter => + { + Assert.Equal("notRequiredParam", parameter.Name); + Assert.False(parameter.IsRequired); + }, + parameter => + { + Assert.Equal("Id", parameter.Name); + Assert.True(parameter.IsRequired); + }, + parameter => + { + Assert.Equal("Name", parameter.Name); + Assert.False(parameter.IsRequired); + }); + } + [Fact] public async Task ApiExplorer_Updates_WhenActionDescriptorCollectionIsUpdated() { @@ -1111,6 +1181,10 @@ private class ApiExplorerParameterData public string Source { get; set; } public string Type { get; set; } + + public string DefaultValue { get; set; } + + public bool IsRequired { get; set; } } // Used to serialize data between client and server diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs index fdf64194eb..615e720689 100644 --- a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs +++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs @@ -28,8 +28,8 @@ public ApiExplorerDataFilter(IApiDescriptionGroupCollectionProvider descriptionP public void OnResourceExecuting(ResourceExecutingContext context) { - var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; - if (controllerActionDescriptor != null && controllerActionDescriptor.MethodInfo.IsDefined(typeof(PassThruAttribute))) + if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor && + controllerActionDescriptor.MethodInfo.IsDefined(typeof(PassThruAttribute))) { return; } @@ -69,6 +69,8 @@ private ApiExplorerData CreateSerializableData(ApiDescription description) Name = parameter.Name, Source = parameter.Source.Id, Type = parameter.Type?.FullName, + DefaultValue = parameter.DefaultValue?.ToString(), + IsRequired = parameter.IsRequired, }; if (parameter.RouteInfo != null) @@ -143,6 +145,10 @@ private class ApiExplorerParameterData public string Source { get; set; } public string Type { get; set; } + + public string DefaultValue { get; set; } + + public bool IsRequired { get; set; } } // Used to serialize data between client and server diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs index a422553574..e890e3d4d3 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs @@ -1,7 +1,9 @@ // 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; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace ApiExplorerWebSite.Controllers { @@ -28,5 +30,13 @@ public void SimpleModelFromBody(int id, [FromBody] Product product) public void ComplexModel([FromQuery] OrderDTO order) { } + + public void DefaultValueParameters(string searchTerm, int top = 10, DayOfWeek searchDay = DayOfWeek.Wednesday) + { + } + + public void IsRequiredParameters([BindRequired] string requiredParam, string notRequiredParam, Product product) + { + } } } \ No newline at end of file diff --git a/test/WebSites/ApiExplorerWebSite/Models/Product.cs b/test/WebSites/ApiExplorerWebSite/Models/Product.cs index 1844de7e13..245df86d8b 100644 --- a/test/WebSites/ApiExplorerWebSite/Models/Product.cs +++ b/test/WebSites/ApiExplorerWebSite/Models/Product.cs @@ -1,12 +1,17 @@ // 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.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding; + namespace ApiExplorerWebSite { public class Product { + [BindRequired] public int Id { get; set; } + [Required] public string Name { get; set; } } } \ No newline at end of file From af9924b314cc14743cfd426c73eddcd859a55330 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 21 Jun 2018 15:35:36 -0700 Subject: [PATCH 2/2] PR comments --- .../ApiExplorer/ApiParameterDescription.cs | 8 +++++--- .../DefaultApiDescriptionProvider.cs | 4 ++-- .../ModelBinding/Metadata/DefaultModelMetadata.cs | 2 +- .../DefaultApiDescriptionProviderTest.cs | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs index 5c345c3c2f..f020c6361a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs @@ -47,9 +47,11 @@ public class ApiParameterDescription /// /// /// A parameter is considered required if - /// a) If it's bound from the request body (). - /// b) If it's a required route value. - /// c) If it has annotations (e.g. BindRequiredAttribute) that indicate it's required. + /// + /// it's bound from the request body (). + /// it's a required route value. + /// it has annotations (e.g. BindRequiredAttribute) that indicate it's required. + /// /// public bool IsRequired { get; set; } diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index f00fb54032..a9d644e2a8 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// Implements a provider of for actions represented /// by . /// - public partial class DefaultApiDescriptionProvider : IApiDescriptionProvider + public class DefaultApiDescriptionProvider : IApiDescriptionProvider { private readonly MvcOptions _mvcOptions; private readonly IActionResultTypeMapper _mapper; @@ -318,7 +318,7 @@ internal static void ProcessParameterDefaultValue(ApiParameterContext context) else { if (parameter.ParameterDescriptor is ControllerParameterDescriptor controllerParameter && - ParameterDefaultValues.TryGetDeclaredParameterDefaultValue(controllerParameter.ParameterInfo, out var defaultValue)) + ParameterDefaultValues.TryGetDeclaredParameterDefaultValue(controllerParameter.ParameterInfo, out var defaultValue)) { parameter.DefaultValue = defaultValue; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs index c2c9773625..bfb1bde8b5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs @@ -324,7 +324,7 @@ public override bool IsReadOnly /// public override bool IsRequired - { + { get { if (!_isRequired.HasValue) diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index 6b76f9571b..c8d06889ec 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -1531,7 +1531,7 @@ public void ProcessDefaultValue_SetsDefaultRouteValue() } [Fact] - public void ProcessDefaultValue_SetsDefaultRouteValue_FromParameterInfo() + public void ProcessDefaultValue_SetsDefaultValue_FromParameterInfo() { // Arrange var methodInfo = GetType().GetMethod(nameof(ParameterDefaultValue), BindingFlags.Instance | BindingFlags.NonPublic);