Add support for optional FromBody parameters (#22634)
* Add support for optional FromBody parameters Fixes https://github.com/dotnet/aspnetcore/issues/6878 * Fixup nullable * Changes per API review
This commit is contained in:
parent
c2bfbf5a04
commit
f7d2fac8a2
|
|
@ -160,6 +160,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
public partial class ApiParameterDescription
|
||||
{
|
||||
public ApiParameterDescription() { }
|
||||
public Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo BindingInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public object DefaultValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public bool IsRequired { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata ModelMetadata { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
|
|
@ -462,6 +463,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public string BinderModelName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public System.Type BinderType { get { throw null; } set { } }
|
||||
public Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource BindingSource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public Microsoft.AspNetCore.Mvc.ModelBinding.EmptyBodyBehavior EmptyBodyBehavior { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider PropertyFilterProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public System.Func<Microsoft.AspNetCore.Mvc.ActionContext, bool> RequestPredicate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public static Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo GetBindingInfo(System.Collections.Generic.IEnumerable<object> attributes) { throw null; }
|
||||
|
|
@ -500,6 +502,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public override bool CanAcceptDataFrom(Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource bindingSource) { throw null; }
|
||||
public static Microsoft.AspNetCore.Mvc.ModelBinding.CompositeBindingSource Create(System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource> bindingSources, string displayName) { throw null; }
|
||||
}
|
||||
public enum EmptyBodyBehavior
|
||||
{
|
||||
Default = 0,
|
||||
Allow = 1,
|
||||
Disallow = 2,
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct EnumGroupAndName
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
/// </summary>
|
||||
public BindingSource Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="BindingInfo"/>.
|
||||
/// </summary>
|
||||
public BindingInfo BindingInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameter type.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
BinderType = other.BinderType;
|
||||
PropertyFilterProvider = other.PropertyFilterProvider;
|
||||
RequestPredicate = other.RequestPredicate;
|
||||
EmptyBodyBehavior = other.EmptyBodyBehavior;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -87,6 +88,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </summary>
|
||||
public Func<ActionContext, bool> RequestPredicate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value which decides if empty bodies are treated as valid inputs.
|
||||
/// </summary>
|
||||
public EmptyBodyBehavior EmptyBodyBehavior { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="BindingInfo"/> from the given <paramref name="attributes"/>.
|
||||
/// <para>
|
||||
|
|
@ -160,6 +166,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var configureEmptyBodyBehavior in attributes.OfType<IConfigureEmptyBodyBehavior>())
|
||||
{
|
||||
isBindingInfoPresent = true;
|
||||
bindingInfo.EmptyBodyBehavior = configureEmptyBodyBehavior.EmptyBodyBehavior;
|
||||
break;
|
||||
}
|
||||
|
||||
return isBindingInfoPresent ? bindingInfo : null;
|
||||
}
|
||||
|
||||
|
|
@ -235,6 +248,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
PropertyFilterProvider = modelMetadata.PropertyFilterProvider;
|
||||
}
|
||||
|
||||
// There isn't a ModelMetadata feature to configure AllowEmptyInputInBodyModelBinding,
|
||||
// so nothing to infer from it.
|
||||
|
||||
return isBindingInfoPresent;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines the behavior for processing empty bodies during input formatting.
|
||||
/// </summary>
|
||||
public enum EmptyBodyBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses the framework default behavior for processing empty bodies.
|
||||
/// This is typically configured using <c>MvcOptions.AllowEmptyInputInBodyModelBinding</c>
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Empty bodies are treated as valid inputs.
|
||||
/// </summary>
|
||||
Allow,
|
||||
|
||||
/// <summary>
|
||||
/// Empty bodies are treated as invalid inputs.
|
||||
/// </summary>
|
||||
Disallow,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
internal interface IConfigureEmptyBodyBehavior
|
||||
{
|
||||
public EmptyBodyBehavior EmptyBodyBehavior { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
ProcessRouteParameters(context);
|
||||
|
||||
// Set IsRequired=true
|
||||
ProcessIsRequired(context);
|
||||
ProcessIsRequired(context, _mvcOptions);
|
||||
|
||||
// Set DefaultValue
|
||||
ProcessParameterDefaultValue(context);
|
||||
|
|
@ -273,13 +273,20 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
}
|
||||
}
|
||||
|
||||
internal static void ProcessIsRequired(ApiParameterContext context)
|
||||
internal static void ProcessIsRequired(ApiParameterContext context, MvcOptions mvcOptions)
|
||||
{
|
||||
foreach (var parameter in context.Results)
|
||||
{
|
||||
if (parameter.Source == BindingSource.Body)
|
||||
{
|
||||
parameter.IsRequired = true;
|
||||
if (parameter.BindingInfo == null || parameter.BindingInfo.EmptyBodyBehavior == EmptyBodyBehavior.Default)
|
||||
{
|
||||
parameter.IsRequired = !mvcOptions.AllowEmptyInputInBodyModelBinding;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameter.IsRequired = !(parameter.BindingInfo.EmptyBodyBehavior == EmptyBodyBehavior.Allow);
|
||||
}
|
||||
}
|
||||
|
||||
if (parameter.ModelMetadata != null && parameter.ModelMetadata.IsBindingRequired)
|
||||
|
|
@ -466,6 +473,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
public BindingInfo BindingInfo { get; set; }
|
||||
|
||||
public static ApiParameterDescriptionContext GetContext(
|
||||
ModelMetadata metadata,
|
||||
BindingInfo bindingInfo,
|
||||
|
|
@ -478,6 +487,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
BinderModelName = bindingInfo?.BinderModelName,
|
||||
BindingSource = bindingInfo?.BindingSource,
|
||||
PropertyName = propertyName ?? metadata.Name,
|
||||
BindingInfo = bindingInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -607,6 +617,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
Source = source,
|
||||
Type = bindingContext.ModelMetadata.ModelType,
|
||||
ParameterDescriptor = Parameter,
|
||||
BindingInfo = bindingContext.BindingInfo
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1725,12 +1725,47 @@ namespace Microsoft.AspNetCore.Mvc.Description
|
|||
var context = GetApiParameterContext(description);
|
||||
|
||||
// Act
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context);
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context, new MvcOptions());
|
||||
|
||||
// Assert
|
||||
Assert.True(description.IsRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessIsRequired_SetsFalse_IfAllowEmptyInputInBodyModelBinding_IsSetInMvcOptions()
|
||||
{
|
||||
// Arrange
|
||||
var description = new ApiParameterDescription { Source = BindingSource.Body, };
|
||||
var context = GetApiParameterContext(description);
|
||||
|
||||
// Act
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context, new MvcOptions { AllowEmptyInputInBodyModelBinding = true });
|
||||
|
||||
// Assert
|
||||
Assert.False(description.IsRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessIsRequired_SetsFalse_IfEmptyBodyBehaviorIsAllowedInBindingInfo()
|
||||
{
|
||||
// Arrange
|
||||
var description = new ApiParameterDescription
|
||||
{
|
||||
Source = BindingSource.Body,
|
||||
BindingInfo = new BindingInfo
|
||||
{
|
||||
EmptyBodyBehavior = EmptyBodyBehavior.Allow,
|
||||
}
|
||||
};
|
||||
var context = GetApiParameterContext(description);
|
||||
|
||||
// Act
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context, new MvcOptions());
|
||||
|
||||
// Assert
|
||||
Assert.False(description.IsRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessIsRequired_SetsTrue_ForParameterDescriptorsWithBindRequired()
|
||||
{
|
||||
|
|
@ -1747,7 +1782,7 @@ namespace Microsoft.AspNetCore.Mvc.Description
|
|||
description.ModelMetadata = modelMetadataProvider.GetMetadataForProperty(typeof(Person), nameof(Person.Name));
|
||||
|
||||
// Act
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context);
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context, new MvcOptions());
|
||||
|
||||
// Assert
|
||||
Assert.True(description.IsRequired);
|
||||
|
|
@ -1765,7 +1800,7 @@ namespace Microsoft.AspNetCore.Mvc.Description
|
|||
var context = GetApiParameterContext(description);
|
||||
|
||||
// Act
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context);
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context, new MvcOptions());
|
||||
|
||||
// Assert
|
||||
Assert.True(description.IsRequired);
|
||||
|
|
@ -1779,7 +1814,7 @@ namespace Microsoft.AspNetCore.Mvc.Description
|
|||
var context = GetApiParameterContext(description);
|
||||
|
||||
// Act
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context);
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context, new MvcOptions());
|
||||
|
||||
// Assert
|
||||
Assert.False(description.IsRequired);
|
||||
|
|
@ -1798,7 +1833,7 @@ namespace Microsoft.AspNetCore.Mvc.Description
|
|||
description.ModelMetadata = modelMetadataProvider.GetMetadataForProperty(typeof(Person), nameof(Person.Name));
|
||||
|
||||
// Act
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context);
|
||||
DefaultApiDescriptionProvider.ProcessIsRequired(context, new MvcOptions());
|
||||
|
||||
// Assert
|
||||
Assert.False(description.IsRequired);
|
||||
|
|
|
|||
|
|
@ -745,6 +745,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
{
|
||||
public FromBodyAttribute() { }
|
||||
public Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource BindingSource { get { throw null; } }
|
||||
public Microsoft.AspNetCore.Mvc.ModelBinding.EmptyBodyBehavior EmptyBodyBehavior { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
}
|
||||
[System.AttributeUsageAttribute(System.AttributeTargets.Parameter | System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
|
||||
public partial class FromFormAttribute : System.Attribute, Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata, Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider
|
||||
|
|
|
|||
|
|
@ -10,9 +10,19 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// Specifies that a parameter or property should be bound using the request body.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public class FromBodyAttribute : Attribute, IBindingSourceMetadata
|
||||
public class FromBodyAttribute : Attribute, IBindingSourceMetadata, IConfigureEmptyBodyBehavior
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public BindingSource BindingSource => BindingSource.Body;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value which decides whether body model binding should treat empty
|
||||
/// input as valid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The default behavior is to use framework defaults as configured by <see cref="MvcOptions.AllowEmptyInputInBodyModelBinding"/>.
|
||||
/// Specifying <see cref="EmptyBodyBehavior.Allow"/> or <see cref="EmptyBodyBehavior.Disallow" /> will override the framework defaults.
|
||||
/// </remarks>
|
||||
public EmptyBodyBehavior EmptyBodyBehavior { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
_options = options;
|
||||
}
|
||||
|
||||
internal bool AllowEmptyBody { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
|
|
@ -116,15 +118,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
var httpContext = bindingContext.HttpContext;
|
||||
|
||||
var allowEmptyInputInModelBinding = _options?.AllowEmptyInputInBodyModelBinding == true;
|
||||
|
||||
var formatterContext = new InputFormatterContext(
|
||||
httpContext,
|
||||
modelBindingKey,
|
||||
bindingContext.ModelState,
|
||||
bindingContext.ModelMetadata,
|
||||
_readerFactory,
|
||||
allowEmptyInputInModelBinding);
|
||||
AllowEmptyBody);
|
||||
|
||||
var formatter = (IInputFormatter)null;
|
||||
for (var i = 0; i < _formatters.Count; i++)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.Core;
|
|||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
|
|
@ -26,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
/// <param name="formatters">The list of <see cref="IInputFormatter"/>.</param>
|
||||
/// <param name="readerFactory">The <see cref="IHttpRequestStreamReaderFactory"/>.</param>
|
||||
public BodyModelBinderProvider(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
|
||||
: this(formatters, readerFactory, loggerFactory: null)
|
||||
: this(formatters, readerFactory, loggerFactory: NullLoggerFactory.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -89,10 +90,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
typeof(IInputFormatter).FullName));
|
||||
}
|
||||
|
||||
return new BodyModelBinder(_formatters, _readerFactory, _loggerFactory, _options);
|
||||
var treatEmptyInputAsDefaultValue = CalculateAllowEmptyBody(context.BindingInfo.EmptyBodyBehavior, _options);
|
||||
|
||||
return new BodyModelBinder(_formatters, _readerFactory, _loggerFactory, _options)
|
||||
{
|
||||
AllowEmptyBody = treatEmptyInputAsDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static bool CalculateAllowEmptyBody(EmptyBodyBehavior emptyBodyBehavior, MvcOptions options)
|
||||
{
|
||||
if (emptyBodyBehavior == EmptyBodyBehavior.Default)
|
||||
{
|
||||
return options?.AllowEmptyInputInBodyModelBinding ?? false;
|
||||
}
|
||||
|
||||
return emptyBodyBehavior == EmptyBodyBehavior.Allow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,53 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
provider.GetBinder(context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateAllowEmptyBody_EmptyBodyBehaviorIsDefaultValue_UsesMvcOptions()
|
||||
{
|
||||
// Arrange
|
||||
var options = new MvcOptions { AllowEmptyInputInBodyModelBinding = true };
|
||||
|
||||
// Act
|
||||
var allowEmpty = BodyModelBinderProvider.CalculateAllowEmptyBody(EmptyBodyBehavior.Default, options);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowEmpty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateAllowEmptyBody_EmptyBodyBehaviorIsDefaultValue_DefaultsToFalseWhenOptionsIsUnavailable()
|
||||
{
|
||||
// Act
|
||||
var allowEmpty = BodyModelBinderProvider.CalculateAllowEmptyBody(EmptyBodyBehavior.Default, options: null);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowEmpty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateAllowEmptyBody_EmptyBodyBehaviorIsAllow()
|
||||
{
|
||||
// Act
|
||||
var allowEmpty = BodyModelBinderProvider.CalculateAllowEmptyBody(EmptyBodyBehavior.Allow, options: new MvcOptions());
|
||||
|
||||
// Assert
|
||||
Assert.True(allowEmpty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateAllowEmptyBody_EmptyBodyBehaviorIsDisallowed()
|
||||
{
|
||||
// Arrange
|
||||
// MvcOptions.AllowEmptyInputInBodyModelBinding should be ignored if EmptyBodyBehavior disallows it
|
||||
var options = new MvcOptions { AllowEmptyInputInBodyModelBinding = true };
|
||||
|
||||
// Act
|
||||
var allowEmpty = BodyModelBinderProvider.CalculateAllowEmptyBody(EmptyBodyBehavior.Disallow, options);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowEmpty);
|
||||
}
|
||||
|
||||
private static BodyModelBinderProvider CreateProvider(params IInputFormatter[] formatters)
|
||||
{
|
||||
var sink = new TestSink();
|
||||
|
|
|
|||
|
|
@ -657,8 +657,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
|
||||
private static BodyModelBinder CreateBinder(IList<IInputFormatter> formatters, bool treatEmptyInputAsDefaultValueOption = false)
|
||||
{
|
||||
var options = new MvcOptions { AllowEmptyInputInBodyModelBinding = treatEmptyInputAsDefaultValueOption };
|
||||
return CreateBinder(formatters, options);
|
||||
var options = new MvcOptions();
|
||||
var binder = CreateBinder(formatters, options);
|
||||
binder.AllowEmptyBody = treatEmptyInputAsDefaultValueOption;
|
||||
|
||||
return binder;
|
||||
}
|
||||
|
||||
private static BodyModelBinder CreateBinder(IList<IInputFormatter> formatters, MvcOptions mvcOptions)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FormatterWebSite.Controllers;
|
||||
using FormatterWebSite.Models;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -22,9 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[ConditionalFact]
|
||||
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||
[Fact]
|
||||
public async Task CheckIfXmlInputFormatterIsBeingCalled()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -168,5 +168,33 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("The DerivedProperty field is required.", value.First);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BodyIsRequiredByDefault()
|
||||
{
|
||||
// Act
|
||||
var response = await Client.PostAsJsonAsync<object>($"Home/{nameof(HomeController.DefaultBody)}", value: null);
|
||||
|
||||
// Assert
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||
var problemDetails = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>();
|
||||
Assert.Collection(
|
||||
problemDetails.Errors,
|
||||
kvp =>
|
||||
{
|
||||
Assert.Empty(kvp.Key);
|
||||
Assert.Equal("A non-empty request body is required.", Assert.Single(kvp.Value));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OptionalFromBodyWorks()
|
||||
{
|
||||
// Act
|
||||
var response = await Client.PostAsJsonAsync<object>($"Home/{nameof(HomeController.OptionalBody)}", value: null);
|
||||
|
||||
// Assert
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace FormatterWebSite.Controllers
|
||||
{
|
||||
|
|
@ -34,5 +35,13 @@ namespace FormatterWebSite.Controllers
|
|||
SampleIntInDerived = 50
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult DefaultBody([FromBody] DummyClass dummy)
|
||||
=> ModelState.IsValid ? Ok() : ValidationProblem();
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult OptionalBody([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] DummyClass dummy)
|
||||
=> ModelState.IsValid ? Ok() : ValidationProblem();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue