Part 1 of #1712 - Remove reflection in MutableObjectModelBinder

This change introduces a new property to ModelMetadata called
IsBindingRequired, which specifies whether or not a model value must be
present on the wire during model binding.

[DataMember(IsRequired = true)] is currently the only thing that will set
this property.

Updated tests and documentation for clarity on the difference in meaning
between MM.IsRequired and MM.IsBindingRequired. Moved setting for
IsRequired to ValidationMetadata which is a better fit.

Also added functional tests for [BindingBehavior] and [DataMember] in
model binding because they were totally missing.
This commit is contained in:
Ryan Nowak 2015-04-14 13:08:37 -07:00
parent c3f10f4a0f
commit f77493dffe
19 changed files with 493 additions and 93 deletions

View File

@ -355,6 +355,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
validationInfo.RequiredProperties.Add(propertyName);
}
else if (propertyMetadata.IsBindingRequired)
{
validationInfo.RequiredProperties.Add(propertyName);
}
}
return validationInfo;

View File

@ -28,6 +28,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// </summary>
public Type BinderType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not the request must contain a value for the model.
/// Will be ignored if the model metadata being created is not a property.
/// See <see cref="ModelMetadata.IsBindingRequired"/>.
/// </summary>
public bool IsBindingRequired { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not the model is read-only. Will be ignored
/// if the model metadata being created is not a property. If <c>null</c> then
@ -36,14 +43,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// </summary>
public bool? IsReadOnly { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not the model is a required value. Will be ignored
/// if the model metadata being created is not a property. If <c>null</c> then
/// <see cref="ModelMetadata.IsRequired"/> will be computed based on the model <see cref="Type"/>.
/// See <see cref="ModelMetadata.IsRequired"/>.
/// </summary>
public bool? IsRequired { get; set; }
/// <summary>
/// Gets or sets the <see cref="ModelBinding.IPropertyBindingPredicateProvider"/>.
/// See <see cref="ModelMetadata.PropertyBindingPredicateProvider"/>.

View File

@ -22,12 +22,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// <inheritdoc />
public void GetBindingMetadata([NotNull] BindingMetadataProviderContext context)
{
var requiredAttribute = context.Attributes.OfType<RequiredAttribute>().FirstOrDefault();
if (requiredAttribute != null)
{
context.BindingMetadata.IsRequired = true;
}
var editableAttribute = context.Attributes.OfType<EditableAttribute>().FirstOrDefault();
if (editableAttribute != null)
{
@ -211,6 +205,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// <inheritdoc />
public void GetValidationMetadata([NotNull] ValidationMetadataProviderContext context)
{
// RequiredAttribute marks a property as required by validation - this means that it
// must have a non-null value on the model during validation.
var requiredAttribute = context.Attributes.OfType<RequiredAttribute>().FirstOrDefault();
if (requiredAttribute != null)
{
context.ValidationMetadata.IsRequired = true;
}
foreach (var attribute in context.Attributes.OfType<ValidationAttribute>())
{
// If another provider has already added this attribute, do not repeat it.

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
return;
}
if (context.BindingMetadata.IsRequired == true)
if (context.BindingMetadata.IsBindingRequired)
{
// This value is already required, no need to look at attributes.
return;
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
{
// We don't need to add a validator, just to set IsRequired = true. The validation
// system will do the right thing.
context.BindingMetadata.IsRequired = true;
context.BindingMetadata.IsBindingRequired = true;
}
}
}

View File

@ -255,6 +255,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
}
}
/// <inheritdoc />
public override bool IsBindingRequired
{
get
{
return BindingMetadata.IsBindingRequired;
}
}
/// <inheritdoc />
public override bool IsEnum
{
@ -305,9 +314,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
{
if (!_isRequired.HasValue)
{
if (BindingMetadata.IsRequired.HasValue)
if (ValidationMetadata.IsRequired.HasValue)
{
_isRequired = BindingMetadata.IsRequired;
_isRequired = ValidationMetadata.IsRequired;
}
else
{

View File

@ -10,6 +10,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// </summary>
public class ValidationMetadata
{
/// <summary>
/// Gets or sets a value indicating whether or not the model is a required value. Will be ignored
/// if the model metadata being created is not a property. If <c>null</c> then
/// <see cref="ModelMetadata.IsRequired"/> will be computed based on the model <see cref="System.Type"/>.
/// See <see cref="ModelMetadata.IsRequired"/>.
/// </summary>
public bool? IsRequired { get; set; }
/// <summary>
/// Gets a list of metadata items for validators.
/// </summary>

View File

@ -166,6 +166,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </remarks>
public abstract bool HideSurroundingHtml { get; }
/// <summary>
/// Gets a value indicating whether or not the model value is required by model binding. This is only
/// applicable when the current instance represents a property.
/// </summary>
/// <remarks>
/// If <c>true</c> then the model value is considered required by model-binding and must have a value
/// supplied in the request to be considered valid.
/// </remarks>
public abstract bool IsBindingRequired { get; }
/// <summary>
/// Gets a value indicating whether <see cref="ModelType"/> or <c>Nullable.GetUnderlyingType(ModelType)</c> is
/// for an <see cref="Enum"/>.
@ -197,6 +207,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// Gets a value indicating whether or not the model value is required. This is only applicable when
/// the current instance represents a property.
/// </summary>
/// <remarks>
/// <para>
/// If <c>true</c> then the model value is considered required by validators.
/// </para>
/// <para>
/// By default an implicit <see cref="System.ComponentModel.DataAnnotations.RequiredAttribute"/> will be added
/// if not present when <c>true.</c>.
/// </para>
/// </remarks>
public abstract bool IsRequired { get; }
/// <summary>

View File

@ -0,0 +1,128 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
using ModelBindingWebSite;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class ModelBindingBindingBehaviorTest
{
private const string SiteName = nameof(ModelBindingWebSite);
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
private readonly Action<IServiceCollection> _configureServices = new Startup().ConfigureServices;
[Fact]
public async Task BindingBehavior_MissingRequiredProperties_ValidationErrors()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var url = "http://localhost/BindingBehavior/EchoModelValues";
var request = new HttpRequestMessage(HttpMethod.Post, url);
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("model.BehaviourOptionalProperty", "Hi"),
};
request.Content = new FormUrlEncodedContent(formData);
// Act
var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var errors = JsonConvert.DeserializeObject<SerializableError>(body);
Assert.Equal(2, errors.Count);
var error = Assert.Single(errors, kvp => kvp.Key == "model.BehaviourRequiredProperty");
Assert.Equal("The 'BehaviourRequiredProperty' property is required.", ((JArray)error.Value)[0].Value<string>());
error = Assert.Single(errors, kvp => kvp.Key == "model.BindRequiredProperty");
Assert.Equal("The 'BindRequiredProperty' property is required.", ((JArray)error.Value)[0].Value<string>());
}
[Fact]
public async Task BindingBehavior_OptionalIsOptional()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var url = "http://localhost/BindingBehavior/EchoModelValues";
var request = new HttpRequestMessage(HttpMethod.Post, url);
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("model.BehaviourRequiredProperty", "Hello"),
new KeyValuePair<string, string>("model.BindRequiredProperty", "World!"),
};
request.Content = new FormUrlEncodedContent(formData);
// Act
var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var model = JsonConvert.DeserializeObject<BindingBehaviorModel>(body);
Assert.Null(model.BehaviourNeverProperty);
Assert.Null(model.BehaviourOptionalProperty);
Assert.Equal("Hello", model.BehaviourRequiredProperty);
Assert.Equal("World!", model.BindRequiredProperty);
Assert.Null(model.BindNeverProperty);
}
[Fact]
public async Task BindingBehavior_Never_IsNotBound()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var url = "http://localhost/BindingBehavior/EchoModelValues";
var request = new HttpRequestMessage(HttpMethod.Post, url);
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("model.BehaviourNeverProperty", "Ignored"),
new KeyValuePair<string, string>("model.BehaviourOptionalProperty", "Optional"),
new KeyValuePair<string, string>("model.BehaviourRequiredProperty", "Hello"),
new KeyValuePair<string, string>("model.BindRequiredProperty", "World!"),
new KeyValuePair<string, string>("model.BindNeverProperty", "Ignored"),
};
request.Content = new FormUrlEncodedContent(formData);
// Act
var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var model = JsonConvert.DeserializeObject<BindingBehaviorModel>(body);
Assert.Null(model.BehaviourNeverProperty);
Assert.Equal("Optional", model.BehaviourOptionalProperty);
Assert.Equal("Hello", model.BehaviourRequiredProperty);
Assert.Equal("World!", model.BindRequiredProperty);
Assert.Null(model.BindNeverProperty);
}
}
}

View File

@ -0,0 +1,87 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
using ModelBindingWebSite;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class ModelBindingDataMemberRequiredTest
{
private const string SiteName = nameof(ModelBindingWebSite);
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
private readonly Action<IServiceCollection> _configureServices = new Startup().ConfigureServices;
[Fact]
public async Task DataMember_MissingRequiredProperty_ValidationError()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var url = "http://localhost/DataMemberRequired/EchoModelValues";
var request = new HttpRequestMessage(HttpMethod.Post, url);
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("model.ExplicitlyOptionalProperty", "Hi"),
};
request.Content = new FormUrlEncodedContent(formData);
// Act
var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var errors = JsonConvert.DeserializeObject<SerializableError>(body);
Assert.Equal(1, errors.Count);
var error = Assert.Single(errors, kvp => kvp.Key == "model.RequiredProperty");
Assert.Equal("The 'RequiredProperty' property is required.", ((JArray)error.Value)[0].Value<string>());
}
[Fact]
public async Task DataMember_RequiredPropertyProvided_Success()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var url = "http://localhost/DataMemberRequired/EchoModelValues";
var request = new HttpRequestMessage(HttpMethod.Post, url);
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("model.ImplicitlyOptionalProperty", "Hello"),
new KeyValuePair<string, string>("model.ExplicitlyOptionalProperty", "World!"),
new KeyValuePair<string, string>("model.RequiredProperty", "Required!"),
};
request.Content = new FormUrlEncodedContent(formData);
// Act
var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var model = JsonConvert.DeserializeObject<DataMemberRequiredModel>(body);
Assert.Equal("Hello", model.ImplicitlyOptionalProperty);
Assert.Equal("World!", model.ExplicitlyOptionalProperty);
Assert.Equal("Required!", model.RequiredProperty);
}
}
}

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
@ -821,6 +822,56 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal("The 'Age' property is required.", modelError.ErrorMessage);
}
[Fact]
[ReplaceCulture]
public void ProcessDto_DataMemberIsRequiredFieldMissing_RaisesModelError()
{
// Arrange
var model = new ModelWithDataMemberIsRequired
{
Name = "original value",
Age = -20
};
var containerMetadata = GetMetadataForType(model.GetType());
var bindingContext = new ModelBindingContext
{
Model = model,
ModelMetadata = containerMetadata,
ModelName = "theModel",
OperationBindingContext = new OperationBindingContext
{
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(),
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
}
};
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
dto.Results[nameProperty] = new ModelBindingResult(
"John Doe",
isModelSet: true,
key: "");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.ProcessDto(bindingContext, dto);
// Assert
var modelStateDictionary = bindingContext.ModelState;
Assert.False(modelStateDictionary.IsValid);
Assert.Single(modelStateDictionary);
// Check Age error.
ModelState modelState;
Assert.True(modelStateDictionary.TryGetValue("theModel.Age", out modelState));
var modelError = Assert.Single(modelState.Errors);
Assert.Null(modelError.Exception);
Assert.NotNull(modelError.ErrorMessage);
Assert.Equal("The 'Age' property is required.", modelError.ErrorMessage);
}
[Fact]
[ReplaceCulture]
public void ProcessDto_BindRequiredFieldNull_RaisesModelError()
@ -1638,6 +1689,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public int Age { get; set; }
}
[DataContract]
private class ModelWithDataMemberIsRequired
{
public string Name { get; set; }
[DataMember(IsRequired = true)]
public int Age { get; set; }
}
[BindRequired]
private class ModelWithMixedBindingBehaviors
{

View File

@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
[Theory]
[MemberData(nameof(DisplayDetailsData))]
public void GetDisplayDetails_SimpleAttributes(
public void GetDisplayMetadata_SimpleAttributes(
object attribute,
Func<DisplayMetadata, object> accessor,
object expected)
@ -68,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
}
[Fact]
public void GetDisplayDetails_FindsDisplayFormat_FromDataType()
public void GetDisplayMetadata_FindsDisplayFormat_FromDataType()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
}
[Fact]
public void GetDisplayDetails_FindsDisplayFormat_OverridingDataType()
public void GetDisplayMetadata_FindsDisplayFormat_OverridingDataType()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
@ -111,7 +111,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
}
[Fact]
public void GetDisplayDetails_EditableAttributeFalse_SetsReadOnlyTrue()
public void GetBindingMetadata_EditableAttributeFalse_SetsReadOnlyTrue()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
@ -130,7 +130,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
}
[Fact]
public void GetDisplayDetails_EditableAttributeTrue_SetsReadOnlyFalse()
public void GetBindingMetadata_EditableAttributeTrue_SetsReadOnlyFalse()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
@ -148,28 +148,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
Assert.False(context.BindingMetadata.IsReadOnly);
}
[Fact]
public void GetDisplayDetails_RequiredAttribute_SetsRequired()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
var required = new RequiredAttribute();
var attributes = new Attribute[] { required };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new BindingMetadataProviderContext(key, attributes);
// Act
provider.GetBindingMetadata(context);
// Assert
Assert.Equal(true, context.BindingMetadata.IsRequired);
}
// This is IMPORTANT. Product code needs to use GetName() instead of .Name. It's easy to regress.
[Fact]
public void GetDisplayDetails_DisplayAttribute_NameFromResources()
public void GetDisplayMetadata_DisplayAttribute_NameFromResources()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
@ -198,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
// This is IMPORTANT. Product code needs to use GetDescription() instead of .Description. It's easy to regress.
[Fact]
public void GetDisplayDetails_DisplayAttribute_DescriptionFromResources()
public void GetDisplayMetadata_DisplayAttribute_DescriptionFromResources()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
@ -243,7 +225,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
[InlineData(typeof(StructWithFields), false)]
[InlineData(typeof(StructWithFields?), false)]
[InlineData(typeof(StructWithProperties), false)]
public void GetDisplayDetails_IsEnum_ReflectsModelType(Type type, bool expectedIsEnum)
public void GetDisplayMetadata_IsEnum_ReflectsModelType(Type type, bool expectedIsEnum)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
@ -277,7 +259,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
[InlineData(typeof(StructWithFields), false)]
[InlineData(typeof(StructWithFields?), false)]
[InlineData(typeof(StructWithProperties), false)]
public void GetDisplayDetails_IsFlagsEnum_ReflectsModelType(Type type, bool expectedIsFlagsEnum)
public void GetDisplayMetadata_IsFlagsEnum_ReflectsModelType(Type type, bool expectedIsFlagsEnum)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
@ -407,7 +389,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
[Theory]
[MemberData(nameof(EnumNamesData))]
public void GetDisplayDetails_EnumNamesAndValues_ReflectsModelType(
public void GetDisplayMetadata_EnumNamesAndValues_ReflectsModelType(
Type type,
IReadOnlyDictionary<string, string> expectedDictionary)
{
@ -541,7 +523,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
[Theory]
[MemberData(nameof(EnumDisplayNamesData))]
public void GetDisplayDetails_EnumDisplayNamesAndValues_ReflectsModelType(
public void GetDisplayMetadata_EnumDisplayNamesAndValues_ReflectsModelType(
Type type,
IEnumerable<KeyValuePair<string, string>> expectedKeyValuePairs)
{
@ -560,7 +542,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
}
[Fact]
public void GetBindingDetails_RequiredAttribute_SetsIsRequiredToTrue()
public void GetValidationMetadata_RequiredAttribute_SetsIsRequiredToTrue()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
@ -569,34 +551,55 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
var attributes = new Attribute[] { required };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
var context = new BindingMetadataProviderContext(key, attributes);
var context = new ValidationMetadataProviderContext(key, attributes);
// Act
provider.GetBindingMetadata(context);
provider.GetValidationMetadata(context);
// Assert
Assert.True(context.BindingMetadata.IsRequired);
Assert.True(context.ValidationMetadata.IsRequired);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
[InlineData(null)]
public void GetBindingDetails_NoRequiredAttribute_IsRequiredLeftAlone(bool? initialValue)
public void GetValidationMetadata_NoRequiredAttribute_IsRequiredLeftAlone(bool? initialValue)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
var attributes = new Attribute[] { };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
var context = new ValidationMetadataProviderContext(key, attributes);
context.ValidationMetadata.IsRequired = initialValue;
// Act
provider.GetValidationMetadata(context);
// Assert
Assert.Equal(initialValue, context.ValidationMetadata.IsRequired);
}
// [Required] has no effect on IsBindingRequired
[Theory]
[InlineData(true)]
[InlineData(false)]
public void GetBindingMetadata_RequiredAttribute_IsBindingRequiredLeftAlone(bool initialValue)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider();
var attributes = new Attribute[] { new RequiredAttribute() };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
var context = new BindingMetadataProviderContext(key, attributes);
context.BindingMetadata.IsRequired = initialValue;
context.BindingMetadata.IsBindingRequired = initialValue;
// Act
provider.GetBindingMetadata(context);
// Assert
Assert.Equal(initialValue, context.BindingMetadata.IsRequired);
Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired);
}
[Theory]

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
public class DataMemberRequiredBindingMetadataProviderTest
{
[Fact]
public void IsRequired_SetToTrue_WithDataMemberIsRequiredTrue()
public void IsBindingRequired_SetToTrue_WithDataMemberIsRequiredTrue()
{
// Arrange
var provider = new DataMemberRequiredBindingMetadataProvider();
@ -29,14 +29,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
provider.GetBindingMetadata(context);
// Assert
Assert.True(context.BindingMetadata.IsRequired);
Assert.True(context.BindingMetadata.IsBindingRequired);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
[InlineData(null)]
public void IsRequired_LeftAlone_DataMemberIsRequiredFalse(bool? initialValue)
public void IsBindingRequired_LeftAlone_DataMemberIsRequiredFalse(bool initialValue)
{
// Arrange
var provider = new DataMemberRequiredBindingMetadataProvider();
@ -52,20 +51,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
typeof(ClassWithDataMemberIsRequiredFalse));
var context = new BindingMetadataProviderContext(key, attributes);
context.BindingMetadata.IsRequired = initialValue;
context.BindingMetadata.IsBindingRequired = initialValue;
// Act
provider.GetBindingMetadata(context);
// Assert
Assert.Equal(initialValue, context.BindingMetadata.IsRequired);
Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
[InlineData(null)]
public void IsRequired_LeftAlone_ForNonPropertyMetadata(bool? initialValue)
public void IsBindingRequired_LeftAlone_ForNonPropertyMetadata(bool initialValue)
{
// Arrange
var provider = new DataMemberRequiredBindingMetadataProvider();
@ -78,20 +76,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
var key = ModelMetadataIdentity.ForType(typeof(ClassWithDataMemberIsRequiredTrue));
var context = new BindingMetadataProviderContext(key, attributes);
context.BindingMetadata.IsRequired = initialValue;
context.BindingMetadata.IsBindingRequired = initialValue;
// Act
provider.GetBindingMetadata(context);
// Assert
Assert.Equal(initialValue, context.BindingMetadata.IsRequired);
Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
[InlineData(null)]
public void IsRequired_LeftAlone_WithoutDataMemberAttribute(bool? initialValue)
public void IsBindingRequired_LeftAlone_WithoutDataMemberAttribute(bool initialValue)
{
// Arrange
var provider = new DataMemberRequiredBindingMetadataProvider();
@ -102,20 +99,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
typeof(ClassWithoutAttributes));
var context = new BindingMetadataProviderContext(key, attributes: new object[0]);
context.BindingMetadata.IsRequired = initialValue;
context.BindingMetadata.IsBindingRequired = initialValue;
// Act
provider.GetBindingMetadata(context);
// Assert
Assert.Equal(initialValue, context.BindingMetadata.IsRequired);
Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
[InlineData(null)]
public void IsRequired_LeftAlone_WithoutDataContractAttribute(bool? initialValue)
public void IsBindingRequired_LeftAlone_WithoutDataContractAttribute(bool initialValue)
{
// Arrange
var provider = new DataMemberRequiredBindingMetadataProvider();
@ -131,13 +127,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
typeof(ClassWithDataMemberIsRequiredTrueWithoutDataContract));
var context = new BindingMetadataProviderContext(key, attributes);
context.BindingMetadata.IsRequired = initialValue;
context.BindingMetadata.IsBindingRequired = initialValue;
// Act
provider.GetBindingMetadata(context);
// Assert
Assert.Equal(initialValue, context.BindingMetadata.IsRequired);
Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired);
}
[DataContract]

View File

@ -36,6 +36,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
Assert.False(metadata.HasNonDefaultEditFormat);
Assert.False(metadata.HideSurroundingHtml);
Assert.True(metadata.HtmlEncode);
Assert.False(metadata.IsBindingRequired);
Assert.False(metadata.IsComplexType);
Assert.False(metadata.IsCollectionType);
Assert.False(metadata.IsEnum);

View File

@ -637,7 +637,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
}
[Fact]
public void IsRequired_DataMemberIsRequiredTrue_SetsIsRequiredToTrue()
public void IsBindingRequired_DataMemberIsRequiredTrue_SetsIsBindingRequiredToTrue()
{
// Arrange
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
@ -648,11 +648,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
nameof(ClassWithDataMemberIsRequiredTrue.StringProperty));
// Assert
Assert.True(metadata.IsRequired);
Assert.True(metadata.IsBindingRequired);
}
[Fact]
public void IsRequired_DataMemberIsRequiredFalse_FalseForReferenceType()
public void IsBindingRequired_DataMemberIsRequiredFalse_False()
{
// Arrange
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
@ -663,26 +663,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
nameof(ClassWithDataMemberIsRequiredFalse.StringProperty));
// Assert
Assert.False(metadata.IsRequired);
Assert.False(metadata.IsBindingRequired);
}
[Fact]
public void IsRequired_DataMemberIsRequiredFalse_TrueForValueType()
{
// Arrange
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
// Act
var metadata = metadataProvider.GetMetadataForProperty(
typeof(ClassWithDataMemberIsRequiredFalse),
nameof(ClassWithDataMemberIsRequiredFalse.IntProperty));
// Assert
Assert.True(metadata.IsRequired);
}
[Fact]
public void IsRequired_DataMemberIsRequiredTrueWithoutDataContract_False()
public void IsBindingRequiredDataMemberIsRequiredTrueWithoutDataContract_False()
{
// Arrange
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
@ -693,7 +678,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
nameof(ClassWithDataMemberIsRequiredTrueWithoutDataContract.StringProperty));
// Assert
Assert.False(metadata.IsRequired);
Assert.False(metadata.IsBindingRequired);
}
[Fact]

View File

@ -309,6 +309,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
public override bool IsBindingRequired
{
get
{
throw new NotImplementedException();
}
}
public override bool IsEnum
{
get

View File

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace ModelBindingWebSite
{
[Route("BindingBehavior")]
public class BindingBehaviorController : Controller
{
// If the results are valid you get back the model, otherwise you get the ModelState errors.
[HttpPost("EchoModelValues")]
public object EchoModelValues(BindingBehaviorModel model)
{
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
return model;
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace ModelBindingWebSite
{
[Route("DataMemberRequired")]
public class DataMemberRequiredController : Controller
{
// If the results are valid you get back the model, otherwise you get the ModelState errors.
[HttpPost("EchoModelValues")]
public object EchoModelValues(DataMemberRequiredModel model)
{
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
return model;
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.ModelBinding;
namespace ModelBindingWebSite
{
public class BindingBehaviorModel
{
[BindingBehavior(BindingBehavior.Never)]
public string BehaviourNeverProperty { get; set; }
[BindingBehavior(BindingBehavior.Optional)]
public string BehaviourOptionalProperty { get; set; }
[BindingBehavior(BindingBehavior.Required)]
public string BehaviourRequiredProperty { get; set; }
[BindRequired]
public string BindRequiredProperty { get; set; }
[BindNever]
public string BindNeverProperty { get; set; }
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.Serialization;
namespace ModelBindingWebSite
{
[DataContract]
public class DataMemberRequiredModel
{
[DataMember]
public string ImplicitlyOptionalProperty { get; set; }
[DataMember(IsRequired = false)]
public string ExplicitlyOptionalProperty { get; set; }
[DataMember(IsRequired = true)]
public string RequiredProperty { get; set; }
}
}