Add IsRequired and DefaultValue to ApiParameterDescription
This commit is contained in:
parent
17d2545b55
commit
6911e192e4
|
|
@ -41,5 +41,23 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
/// Gets or sets the parameter descriptor.
|
/// Gets or sets the parameter descriptor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ParameterDescriptor ParameterDescriptor { get; set; }
|
public ParameterDescriptor ParameterDescriptor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value that determines if the parameter is required.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A parameter is considered required if
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>it's bound from the request body (<see cref="BindingSource.Body"/>).</item>
|
||||||
|
/// <item>it's a required route value.</item>
|
||||||
|
/// <item>it has annotations (e.g. BindRequiredAttribute) that indicate it's required.</item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
|
public bool IsRequired { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default value for a parameter.
|
||||||
|
/// </summary>
|
||||||
|
public object DefaultValue { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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<TemplatePart> routeParameters)
|
||||||
|
{
|
||||||
|
MetadataProvider = metadataProvider;
|
||||||
|
ActionDescriptor = actionDescriptor;
|
||||||
|
RouteParameters = routeParameters;
|
||||||
|
|
||||||
|
Results = new List<ApiParameterDescription>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerActionDescriptor ActionDescriptor { get; }
|
||||||
|
|
||||||
|
public IModelMetadataProvider MetadataProvider { get; }
|
||||||
|
|
||||||
|
public IList<ApiParameterDescription> Results { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<TemplatePart> RouteParameters { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
|
|
@ -177,7 +175,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
{
|
{
|
||||||
var visitor = new PseudoModelBindingVisitor(context, actionParameter);
|
var visitor = new PseudoModelBindingVisitor(context, actionParameter);
|
||||||
|
|
||||||
ModelMetadata metadata = null;
|
ModelMetadata metadata;
|
||||||
if (_mvcOptions.AllowValidatingTopLevelNodes &&
|
if (_mvcOptions.AllowValidatingTopLevelNodes &&
|
||||||
actionParameter is ControllerParameterDescriptor controllerParameterDescriptor &&
|
actionParameter is ControllerParameterDescriptor controllerParameterDescriptor &&
|
||||||
_modelMetadataProvider is ModelMetadataProvider provider)
|
_modelMetadataProvider is ModelMetadataProvider provider)
|
||||||
|
|
@ -232,6 +230,21 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, we want to join up any route parameters with those discovered from the action's parameters.
|
// 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<string, ApiParameterRouteInfo>(StringComparer.OrdinalIgnoreCase);
|
var routeParameters = new Dictionary<string, ApiParameterRouteInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var routeParameter in context.RouteParameters)
|
foreach (var routeParameter in context.RouteParameters)
|
||||||
{
|
{
|
||||||
|
|
@ -271,8 +284,46 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
Source = BindingSource.Path,
|
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)
|
private ApiParameterRouteInfo CreateRouteInfo(TemplatePart routeParameter)
|
||||||
|
|
@ -416,29 +467,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ApiParameterContext
|
|
||||||
{
|
|
||||||
public ApiParameterContext(
|
|
||||||
IModelMetadataProvider metadataProvider,
|
|
||||||
ControllerActionDescriptor actionDescriptor,
|
|
||||||
IReadOnlyList<TemplatePart> routeParameters)
|
|
||||||
{
|
|
||||||
MetadataProvider = metadataProvider;
|
|
||||||
ActionDescriptor = actionDescriptor;
|
|
||||||
RouteParameters = routeParameters;
|
|
||||||
|
|
||||||
Results = new List<ApiParameterDescription>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControllerActionDescriptor ActionDescriptor { get; }
|
|
||||||
|
|
||||||
public IModelMetadataProvider MetadataProvider { get; }
|
|
||||||
|
|
||||||
public IList<ApiParameterDescription> Results { get; }
|
|
||||||
|
|
||||||
public IReadOnlyList<TemplatePart> RouteParameters { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ApiParameterDescriptionContext
|
private class ApiParameterDescriptionContext
|
||||||
{
|
{
|
||||||
public ModelMetadata ModelMetadata { get; set; }
|
public ModelMetadata ModelMetadata { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
private static object GetParameterDefaultValue(ParameterInfo parameterInfo)
|
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<DefaultValueAttribute>(inherit: false);
|
defaultValue = Activator.CreateInstance(parameterInfo.ParameterType);
|
||||||
defaultValue = defaultValueAttribute?.Value;
|
|
||||||
|
|
||||||
if (defaultValue == null && parameterInfo.ParameterType.IsValueType)
|
|
||||||
{
|
|
||||||
defaultValue = Activator.CreateInstance(parameterInfo.ParameterType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryGetDeclaredParameterDefaultValue(ParameterInfo parameterInfo, out object defaultValue)
|
||||||
|
{
|
||||||
|
if (ParameterDefaultValue.TryGetDefaultValue(parameterInfo, out defaultValue))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultValueAttribute = parameterInfo.GetCustomAttribute<DefaultValueAttribute>(inherit: false);
|
||||||
|
if (defaultValueAttribute != null)
|
||||||
|
{
|
||||||
|
defaultValue = defaultValueAttribute.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Constraints;
|
using Microsoft.AspNetCore.Routing.Constraints;
|
||||||
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -1416,6 +1417,172 @@ namespace Microsoft.AspNetCore.Mvc.Description
|
||||||
Assert.Equal(typeof(string), comments.Type);
|
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_ForParameterDescriptorsWithBindRequired()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var description = new ApiParameterDescription
|
||||||
|
{
|
||||||
|
Source = BindingSource.Query,
|
||||||
|
};
|
||||||
|
var context = GetApiParameterContext(description);
|
||||||
|
var modelMetadataProvider = new TestModelMetadataProvider();
|
||||||
|
modelMetadataProvider
|
||||||
|
.ForProperty<Person>(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_ForRequiredRouteParameterDescriptors()
|
||||||
|
{
|
||||||
|
// 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_ForParameterDescriptorsWithValidationRequired()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var description = new ApiParameterDescription();
|
||||||
|
var context = GetApiParameterContext(description);
|
||||||
|
var modelMetadataProvider = new TestModelMetadataProvider();
|
||||||
|
modelMetadataProvider
|
||||||
|
.ForProperty<Person>(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_SetsDefaultValue_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<ApiDescription> GetApiDescriptions(
|
private IReadOnlyList<ApiDescription> GetApiDescriptions(
|
||||||
ActionDescriptor action,
|
ActionDescriptor action,
|
||||||
List<MockInputFormatter> inputFormatters = null,
|
List<MockInputFormatter> inputFormatters = null,
|
||||||
|
|
@ -1711,6 +1878,8 @@ namespace Microsoft.AspNetCore.Mvc.Description
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ParameterDefaultValue(int value = 10) { }
|
||||||
|
|
||||||
private class TestController
|
private class TestController
|
||||||
{
|
{
|
||||||
[FromRoute]
|
[FromRoute]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
@ -1018,6 +1019,75 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Equal(typeof(string).FullName, feedback.Type);
|
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<List<ApiExplorerData>>(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<List<ApiExplorerData>>(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]
|
[Fact]
|
||||||
public async Task ApiExplorer_Updates_WhenActionDescriptorCollectionIsUpdated()
|
public async Task ApiExplorer_Updates_WhenActionDescriptorCollectionIsUpdated()
|
||||||
{
|
{
|
||||||
|
|
@ -1111,6 +1181,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
public string Source { get; set; }
|
public string Source { get; set; }
|
||||||
|
|
||||||
public string Type { 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
|
// Used to serialize data between client and server
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ namespace ApiExplorerWebSite
|
||||||
|
|
||||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||||
{
|
{
|
||||||
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
|
if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor &&
|
||||||
if (controllerActionDescriptor != null && controllerActionDescriptor.MethodInfo.IsDefined(typeof(PassThruAttribute)))
|
controllerActionDescriptor.MethodInfo.IsDefined(typeof(PassThruAttribute)))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -69,6 +69,8 @@ namespace ApiExplorerWebSite
|
||||||
Name = parameter.Name,
|
Name = parameter.Name,
|
||||||
Source = parameter.Source.Id,
|
Source = parameter.Source.Id,
|
||||||
Type = parameter.Type?.FullName,
|
Type = parameter.Type?.FullName,
|
||||||
|
DefaultValue = parameter.DefaultValue?.ToString(),
|
||||||
|
IsRequired = parameter.IsRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (parameter.RouteInfo != null)
|
if (parameter.RouteInfo != null)
|
||||||
|
|
@ -143,6 +145,10 @@ namespace ApiExplorerWebSite
|
||||||
public string Source { get; set; }
|
public string Source { get; set; }
|
||||||
|
|
||||||
public string Type { 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
|
// Used to serialize data between client and server
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace ApiExplorerWebSite.Controllers
|
namespace ApiExplorerWebSite.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -28,5 +30,13 @@ namespace ApiExplorerWebSite.Controllers
|
||||||
public void ComplexModel([FromQuery] OrderDTO order)
|
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)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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
|
namespace ApiExplorerWebSite
|
||||||
{
|
{
|
||||||
public class Product
|
public class Product
|
||||||
{
|
{
|
||||||
|
[BindRequired]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue