diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/TypeConverterModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/TypeConverterModelBinder.cs index c0d5a59f96..af07c39073 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/TypeConverterModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/TypeConverterModelBinder.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Reflection; using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Core; namespace Microsoft.AspNet.Mvc.ModelBinding { @@ -34,12 +36,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding bindingContext.ModelName, bindingContext.ModelMetadata, newModel); + var isModelSet = true; - return new ModelBindingResult( - newModel, - bindingContext.ModelName, - isModelSet: true, - validationNode: validationNode); + // When converting newModel a null value may indicate a failed conversion for an otherwise required + // model (can't set a ValueType to null). This detects if a null model value is acceptable given the + // current bindingContext. If not, an error is logged. + if (newModel == null && !AllowsNullValue(bindingContext.ModelType)) + { + bindingContext.ModelState.TryAddModelError( + bindingContext.ModelName, + Resources.FormatCommon_ValueNotValidForProperty(newModel)); + + isModelSet = false; + } + + return new ModelBindingResult(newModel, bindingContext.ModelName, isModelSet, validationNode); } catch (Exception ex) { @@ -50,5 +61,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Tell the model binding system to skip other model binders i.e. return non-null. return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false); } + + private static bool AllowsNullValue(Type type) + { + return !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null; + } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/TypeConverterModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/TypeConverterModelBinderTest.cs index 3c6e139c41..2a3206b938 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/TypeConverterModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/TypeConverterModelBinderTest.cs @@ -67,6 +67,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test Assert.NotNull(retVal); } + [Theory] + [InlineData(typeof(byte))] + [InlineData(typeof(short))] + [InlineData(typeof(int))] + [InlineData(typeof(long))] + [InlineData(typeof(Guid))] + [InlineData(typeof(double))] + [InlineData(typeof(DayOfWeek))] + public async Task BindModel_CreatesError_WhenTypeConversionIsNull(Type destinationType) + { + // Arrange + var bindingContext = GetBindingContext(destinationType); + bindingContext.ValueProvider = new SimpleHttpValueProvider + { + { "theModelName", string.Empty } + }; + var binder = new TypeConverterModelBinder(); + + // Act + var result = await binder.BindModelAsync(bindingContext); + + // Assert + Assert.False(result.IsModelSet); + Assert.NotNull(result.ValidationNode); + var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors); + Assert.Equal(error.ErrorMessage, "The value '' is invalid.", StringComparer.Ordinal); + Assert.Null(error.Exception); + } + [Fact] public async Task BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState() { @@ -113,7 +142,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test var bindingContext = GetBindingContext(typeof(string)); bindingContext.ValueProvider = new SimpleHttpValueProvider { - { "theModelName", "" } + { "theModelName", string.Empty } }; var binder = new TypeConverterModelBinder(); @@ -144,7 +173,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test // Assert Assert.NotNull(retVal); - Assert.Equal(42, retVal.Model);; + Assert.Equal(42, retVal.Model); Assert.True(bindingContext.ModelState.ContainsKey("theModelName")); } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html index 631e57ecad..7d6cf27c87 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html @@ -30,7 +30,8 @@ Female -