Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Add IsRequired and DefaultValue to ApiParameterDescription #7957

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,23 @@ public class ApiParameterDescription
/// Gets or sets the parameter descriptor.
/// </summary>
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; }
}
}
33 changes: 33 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterContext.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -177,7 +175,7 @@ private IList<ApiParameterDescription> 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)
Expand Down Expand Up @@ -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<string, ApiParameterRouteInfo>(StringComparer.OrdinalIgnoreCase);
foreach (var routeParameter in context.RouteParameters)
{
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would ModelMetadata property be null e.g. is this only for route parameters that don't match an actual parameter (but are added to Results)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit tests

{
parameter.IsRequired = true;
}

if (parameter.Source == BindingSource.Path && parameter.RouteInfo != null && !parameter.RouteInfo.IsOptional)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would RouteInfo property be null e.g. is this only for BindingSource.Path parameters that don't match actual route parameters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit tests

{
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)
Expand Down Expand Up @@ -416,29 +467,6 @@ private IApiRequestMetadataProvider[] GetRequestMetadataAttributes(ControllerAct
.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
{
public ModelMetadata ModelMetadata { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<DefaultValueAttribute>(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<DefaultValueAttribute>(inherit: false);
if (defaultValueAttribute != null)
{
defaultValue = defaultValueAttribute.Value;
return true;
}

return false;
}
}
}
Loading