diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Metadata/IModelBindingMessageProvider.cs b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Metadata/IModelBindingMessageProvider.cs new file mode 100644 index 0000000000..63bae2b9ab --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Metadata/IModelBindingMessageProvider.cs @@ -0,0 +1,34 @@ +// 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; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Provider for error messages the model binding system detects. + /// + public interface IModelBindingMessageProvider + { + /// + /// Error message the model binding system adds when a property with an associated + /// BindRequiredAttribute is not bound. + /// + /// Default is "A value for the '{0}' property was not provided.". + Func MissingBindRequiredValueAccessor { get; } + + /// + /// Error message the model binding system adds when either the key or the value of a + /// is bound but not both. + /// + /// Default is "A value is required.". + Func MissingKeyOrValueAccessor { get; } + + /// + /// Error message the model binding system adds when a null value is bound to a + /// non- property. + /// + /// Default is "The value '{0}' is invalid.". + Func ValueMustNotBeNullAccessor { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelMetadata.cs index 9cff64881f..53304d93ca 100644 --- a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelMetadata.cs @@ -242,6 +242,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public abstract bool IsRequired { get; } + /// + /// Gets the instance. + /// + public abstract IModelBindingMessageProvider ModelBindingMessageProvider { get; } + /// /// Gets a value indicating where the current metadata should be ordered relative to other properties /// in its containing type. diff --git a/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs index 2a461e74c8..04f22c9c92 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Metadata; @@ -23,6 +24,12 @@ namespace Microsoft.AspNet.Mvc.Internal public static void ConfigureMvc(MvcOptions options) { + // Set up default error messages + var messageProvider = options.ModelBindingMessageProvider; + messageProvider.MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember; + messageProvider.MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent; + messageProvider.ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid; + // Set up ModelBinding options.ModelBinders.Add(new BinderTypeBasedModelBinder()); options.ModelBinders.Add(new ServicesModelBinder()); @@ -48,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.Internal options.ValueProviderFactories.Add(new JQueryFormValueProviderFactory()); // Set up metadata providers - options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider()); + options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider(messageProvider)); options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider()); // Set up validators diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs index 2a27b8eea4..5be3a27dfc 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs @@ -1,5 +1,7 @@ // 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 Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding.Metadata; namespace Microsoft.AspNet.Mvc.ModelBinding @@ -7,8 +9,33 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public class EmptyModelMetadataProvider : DefaultModelMetadataProvider { public EmptyModelMetadataProvider() - : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[0])) + : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[] + { + new MessageOnlyBindingProvider() + })) { } + + private class MessageOnlyBindingProvider : IBindingMetadataProvider + { + private readonly ModelBindingMessageProvider _messageProvider = CreateMessageProvider(); + + public void GetBindingMetadata(BindingMetadataProviderContext context) + { + // Don't bother with ModelBindingMessageProvider copy constructor. No other provider can change the + // delegates. + context.BindingMetadata.ModelBindingMessageProvider = _messageProvider; + } + + private static ModelBindingMessageProvider CreateMessageProvider() + { + return new ModelBindingMessageProvider + { + MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember, + MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent, + ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid, + }; + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/KeyValuePairModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/KeyValuePairModelBinder.cs index f40f8d69a0..f64bc17fcc 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/KeyValuePairModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/KeyValuePairModelBinder.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.Core; namespace Microsoft.AspNet.Mvc.ModelBinding { @@ -30,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { bindingContext.ModelState.TryAddModelError( keyResult.Key, - Resources.KeyValuePair_BothKeyAndValueMustBePresent); + bindingContext.ModelMetadata.ModelBindingMessageProvider.MissingKeyOrValueAccessor()); // Were able to get some data for this model. // Always tell the model binding system to skip other model binders. @@ -40,7 +39,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { bindingContext.ModelState.TryAddModelError( valueResult.Key, - Resources.KeyValuePair_BothKeyAndValueMustBePresent); + bindingContext.ModelMetadata.ModelBindingMessageProvider.MissingKeyOrValueAccessor()); // Were able to get some data for this model. // Always tell the model binding system to skip other model binders. diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs index e7540642f2..9ef873b6e2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs @@ -10,6 +10,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// public class BindingMetadata { + private ModelBindingMessageProvider _messageProvider; + /// /// Gets or sets the . /// See . @@ -50,6 +52,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// public bool? IsReadOnly { get; set; } + /// + /// Gets the instance. See + /// . + /// + public ModelBindingMessageProvider ModelBindingMessageProvider + { + get + { + return _messageProvider; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _messageProvider = value; + } + } + /// /// Gets or sets the . /// See . diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultBindingMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultBindingMetadataProvider.cs index 89adba0378..d951eb61d4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultBindingMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultBindingMetadataProvider.cs @@ -14,6 +14,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// public class DefaultBindingMetadataProvider : IBindingMetadataProvider { + private readonly ModelBindingMessageProvider _messageProvider; + + public DefaultBindingMetadataProvider(ModelBindingMessageProvider messageProvider) + { + if (messageProvider == null) + { + throw new ArgumentNullException(nameof(messageProvider)); + } + + _messageProvider = messageProvider; + } + /// public void GetBindingMetadata([NotNull] BindingMetadataProviderContext context) { @@ -47,6 +59,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata } } + // ModelBindingMessageProvider + // Provide a unique instance based on one passed to the constructor. + context.BindingMetadata.ModelBindingMessageProvider = new ModelBindingMessageProvider(_messageProvider); + // PropertyBindingPredicateProvider var predicateProviders = context.Attributes.OfType().ToArray(); if (predicateProviders.Length > 0) @@ -62,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata var bindingBehavior = context.PropertyAttributes.OfType().FirstOrDefault(); if (bindingBehavior == null) { - bindingBehavior = + bindingBehavior = context.Key.ContainerType.GetTypeInfo() .GetCustomAttributes(typeof(BindingBehaviorAttribute), inherit: true) .OfType() diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs index a89c04260a..50b2f72015 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs @@ -418,6 +418,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata } } + /// + public override IModelBindingMessageProvider ModelBindingMessageProvider + { + get + { + return BindingMetadata.ModelBindingMessageProvider; + } + } + /// public override string NullDisplayText { diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultValidationMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultValidationMetadataProvider.cs index 8963f870b8..17c324586f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultValidationMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultValidationMetadataProvider.cs @@ -1,7 +1,6 @@ // 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.Linq; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.Framework.Internal; @@ -19,8 +18,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { if (attribute is IModelValidator || attribute is IClientModelValidator) { - // If another provider has already added this attribute, do not repeat it. - // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and + // If another provider has already added this attribute, do not repeat it. + // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and // IClientModelValidator) to be added to the ValidationMetadata twice. // This is to ensure we do not end up with duplication validation rules on the client side. if (!context.ValidationMetadata.ValidatorMetadata.Contains(attribute)) diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs new file mode 100644 index 0000000000..33c6dea706 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs @@ -0,0 +1,95 @@ +// 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; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Read / write implementation. + /// + public class ModelBindingMessageProvider : IModelBindingMessageProvider + { + private Func _missingBindRequiredValueAccessor; + private Func _missingKeyOrValueAccessor; + private Func _valueMustNotBeNullAccessor; + + /// + /// Initializes a new instance of the class. + /// + public ModelBindingMessageProvider() + { + } + + /// + /// Initializes a new instance of the class based on + /// . + /// + /// The to duplicate. + public ModelBindingMessageProvider(ModelBindingMessageProvider originalProvider) + { + if (originalProvider == null) + { + throw new ArgumentNullException(nameof(originalProvider)); + } + + MissingBindRequiredValueAccessor = originalProvider.MissingBindRequiredValueAccessor; + MissingKeyOrValueAccessor = originalProvider.MissingKeyOrValueAccessor; + ValueMustNotBeNullAccessor = originalProvider.ValueMustNotBeNullAccessor; + } + + /// + public Func MissingBindRequiredValueAccessor + { + get + { + return _missingBindRequiredValueAccessor; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _missingBindRequiredValueAccessor = value; + } + } + + /// + public Func MissingKeyOrValueAccessor + { + get + { + return _missingKeyOrValueAccessor; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _missingKeyOrValueAccessor = value; + } + } + + /// + public Func ValueMustNotBeNullAccessor + { + get + { + return _valueMustNotBeNullAccessor; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _valueMustNotBeNullAccessor = value; + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/MutableObjectModelBinder.cs index b917d18516..0057880cbc 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/MutableObjectModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/MutableObjectModelBinder.cs @@ -6,8 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.Core; -using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding @@ -414,7 +412,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding bindingContext.ModelState.TryAddModelError( modelStateKey, - Resources.FormatModelBinding_MissingBindRequiredMember(propertyName)); + bindingContext.ModelMetadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor( + propertyName)); } // For each property that BindPropertiesAsync() attempted to bind, call the setter, recording diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/SimpleTypeModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/SimpleTypeModelBinder.cs index 3368a48971..bc36685396 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/SimpleTypeModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/SimpleTypeModelBinder.cs @@ -3,7 +3,6 @@ using System; using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.Core; namespace Microsoft.AspNet.Mvc.ModelBinding { @@ -51,7 +50,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { bindingContext.ModelState.TryAddModelError( bindingContext.ModelName, - Resources.FormatCommon_ValueNotValidForProperty(model)); + bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor( + valueProviderResult.ToString())); return ModelBindingResult.FailedAsync(bindingContext.ModelName); } diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs index 92969b7783..fc4046d675 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs @@ -28,6 +28,7 @@ namespace Microsoft.AspNet.Mvc InputFormatters = new FormatterCollection(); OutputFormatters = new FormatterCollection(); ModelBinders = new List(); + ModelBindingMessageProvider = new ModelBindingMessageProvider(); ModelMetadataDetailsProviders = new List(); ModelValidatorProviders = new List(); ValidationExcludeFilters = new ExcludeTypeValidationFilterCollection(); @@ -87,7 +88,14 @@ namespace Microsoft.AspNet.Mvc public IList ModelBinders { get; } /// - /// Gets a list of instances that will be used to + /// Gets the default . Changes here are copied to the + /// property of all + /// instances unless overridden in a custom . + /// + public ModelBindingMessageProvider ModelBindingMessageProvider { get; } + + /// + /// Gets a list of instances that will be used to /// create instances. /// /// @@ -112,7 +120,7 @@ namespace Microsoft.AspNet.Mvc public FormatterCollection OutputFormatters { get; } /// - /// Gets or sets the flag which causes content negotiation to ignore Accept header + /// Gets or sets the flag which causes content negotiation to ignore Accept header /// when it contains the media type */*. by default. /// public bool RespectBrowserAcceptHeader { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index 1049f779ee..f866e339dd 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -74,22 +74,6 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_UnexpectedTaskInstance"), p0, p1); } - /// - /// Replacing the action context is not supported. - /// - internal static string ActionContextAccessor_SetValueNotSupported - { - get { return GetString("ActionContextAccessor_SetValueNotSupported"); } - } - - /// - /// Replacing the action context is not supported. - /// - internal static string FormatActionContextAccessor_SetValueNotSupported() - { - return GetString("ActionContextAccessor_SetValueNotSupported"); - } - /// /// An action invoker could not be created for action '{0}'. /// @@ -171,51 +155,19 @@ namespace Microsoft.AspNet.Mvc.Core } /// - /// The supplied route values are ambiguous and can select multiple sets of actions. + /// The value '{0}' is invalid. /// - internal static string ActionSelector_GetCandidateActionsIsAmbiguous + internal static string ModelBinding_NullValueNotValid { - get { return GetString("ActionSelector_GetCandidateActionsIsAmbiguous"); } - } - - /// - /// The supplied route values are ambiguous and can select multiple sets of actions. - /// - internal static string FormatActionSelector_GetCandidateActionsIsAmbiguous() - { - return GetString("ActionSelector_GetCandidateActionsIsAmbiguous"); - } - - /// - /// Property '{0}' is of type '{1}', but this method requires a value of type '{2}'. - /// - internal static string ArgumentPropertyUnexpectedType - { - get { return GetString("ArgumentPropertyUnexpectedType"); } - } - - /// - /// Property '{0}' is of type '{1}', but this method requires a value of type '{2}'. - /// - internal static string FormatArgumentPropertyUnexpectedType(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentPropertyUnexpectedType"), p0, p1, p2); + get { return GetString("ModelBinding_NullValueNotValid"); } } /// /// The value '{0}' is invalid. /// - internal static string Common_ValueNotValidForProperty + internal static string FormatModelBinding_NullValueNotValid(object p0) { - get { return GetString("Common_ValueNotValidForProperty"); } - } - - /// - /// The value '{0}' is invalid. - /// - internal static string FormatCommon_ValueNotValidForProperty(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Common_ValueNotValidForProperty"), p0); + return string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_NullValueNotValid"), p0); } /// @@ -282,38 +234,6 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("AsyncResultFilter_InvalidShortCircuit"), p0, p1, p2, p3); } - /// - /// Unable to locate an implementation of IAuthorizationService. - /// - internal static string AuthorizeAttribute_AuthorizationServiceMustBeDefined - { - get { return GetString("AuthorizeAttribute_AuthorizationServiceMustBeDefined"); } - } - - /// - /// Unable to locate an implementation of IAuthorizationService. - /// - internal static string FormatAuthorizeAttribute_AuthorizationServiceMustBeDefined() - { - return GetString("AuthorizeAttribute_AuthorizationServiceMustBeDefined"); - } - - /// - /// OnAuthorization is not implemented by this filter, use OnAuthorizationAsync instead. - /// - internal static string AuthorizeAttribute_OnAuthorizationNotImplemented - { - get { return GetString("AuthorizeAttribute_OnAuthorizationNotImplemented"); } - } - - /// - /// OnAuthorization is not implemented by this filter, use OnAuthorizationAsync instead. - /// - internal static string FormatAuthorizeAttribute_OnAuthorizationNotImplemented() - { - return GetString("AuthorizeAttribute_OnAuthorizationNotImplemented"); - } - /// /// The type provided to '{0}' must implement '{1}'. /// @@ -826,22 +746,6 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("Format_NotValid"), p0); } - /// - /// The property '{0}' on controller '{1}' cannot be activated. - /// - internal static string ControllerFactory_PropertyCannotBeActivated - { - get { return GetString("ControllerFactory_PropertyCannotBeActivated"); } - } - - /// - /// The property '{0}' on controller '{1}' cannot be activated. - /// - internal static string FormatControllerFactory_PropertyCannotBeActivated(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ControllerFactory_PropertyCannotBeActivated"), p0, p1); - } - /// /// The '{0}' cache profile is not defined. /// @@ -938,22 +842,6 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_CannotBeGreedy"), p0, p1); } - /// - /// The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request. - /// - internal static string BindingSource_MustBeFromRequest - { - get { return GetString("BindingSource_MustBeFromRequest"); } - } - - /// - /// The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request. - /// - internal static string FormatBindingSource_MustBeFromRequest(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeFromRequest"), p0, p1); - } - /// /// The property {0}.{1} could not be found. /// @@ -1082,38 +970,6 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_MissingBindRequiredMember"), p0); } - /// - /// A value is required. - /// - internal static string ModelBinding_ValueRequired - { - get { return GetString("ModelBinding_ValueRequired"); } - } - - /// - /// A value is required. - /// - internal static string FormatModelBinding_ValueRequired() - { - return GetString("ModelBinding_ValueRequired"); - } - - /// - /// More than one parameter and/or property is bound to the HTTP request's content. - /// - internal static string MultipleBodyParametersOrPropertiesAreNotAllowed - { - get { return GetString("MultipleBodyParametersOrPropertiesAreNotAllowed"); } - } - - /// - /// More than one parameter and/or property is bound to the HTTP request's content. - /// - internal static string FormatMultipleBodyParametersOrPropertiesAreNotAllowed() - { - return GetString("MultipleBodyParametersOrPropertiesAreNotAllowed"); - } - /// /// The type '{0}' does not implement the interface '{1}'. /// @@ -1130,38 +986,6 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("PropertyBindingPredicateProvider_WrongType"), p0, p1); } - /// - /// The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'. - /// - internal static string ValidatableObjectAdapter_IncompatibleType - { - get { return GetString("ValidatableObjectAdapter_IncompatibleType"); } - } - - /// - /// The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'. - /// - internal static string FormatValidatableObjectAdapter_IncompatibleType(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ValidatableObjectAdapter_IncompatibleType"), p0, p1); - } - - /// - /// Cannot convert value '{0}' to enum type '{1}'. - /// - internal static string ValueProviderResult_CannotConvertEnum - { - get { return GetString("ValueProviderResult_CannotConvertEnum"); } - } - - /// - /// Cannot convert value '{0}' to enum type '{1}'. - /// - internal static string FormatValueProviderResult_CannotConvertEnum(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ValueProviderResult_CannotConvertEnum"), p0, p1); - } - /// /// The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index b153558e3f..bedebefd24 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -129,9 +129,6 @@ The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. - - Replacing the action context is not supported. - An action invoker could not be created for action '{0}'. @@ -147,13 +144,7 @@ The '{0}' method of type '{1}' cannot return a null value. - - The supplied route values are ambiguous and can select multiple sets of actions. - - - Property '{0}' is of type '{1}', but this method requires a value of type '{2}'. - - + The value '{0}' is invalid. @@ -168,12 +159,6 @@ If an {0} cancels execution by setting the {1} property of {2} to 'true', then it cannot call the next filter by invoking {3}. - - Unable to locate an implementation of IAuthorizationService. - - - OnAuthorization is not implemented by this filter, use OnAuthorizationAsync instead. - The type provided to '{0}' must implement '{1}'. @@ -279,9 +264,6 @@ The format provided is invalid '{0}'. A format must be a non-empty file-extension, optionally prefixed with a '.' character. - - The property '{0}' on controller '{1}' cannot be activated. - The '{0}' cache profile is not defined. @@ -300,9 +282,6 @@ The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources. - - The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request. - The property {0}.{1} could not be found. @@ -327,21 +306,9 @@ A value for the '{0}' property was not provided. - - A value is required. - - - More than one parameter and/or property is bound to the HTTP request's content. - The type '{0}' does not implement the interface '{1}'. - - The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'. - - - Cannot convert value '{0}' to enum type '{1}'. - The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types. diff --git a/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs index 17dcce2206..ec7ae4a14a 100644 --- a/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs @@ -457,6 +457,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + public override IModelBindingMessageProvider ModelBindingMessageProvider + { + get + { + throw new NotImplementedException(); + } + } + public override string NullDisplayText { get diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs index 8c61635250..753b6b3cb7 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs @@ -4,7 +4,6 @@ #if DNX451 using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.ModelBinding.Validation; @@ -54,7 +53,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test Assert.Null(result.Model); Assert.False(bindingContext.ModelState.IsValid); Assert.Equal("someName", bindingContext.ModelName); - Assert.Equal(bindingContext.ModelState["someName.Value"].Errors.First().ErrorMessage, "A value is required."); + var state = bindingContext.ModelState["someName.Value"]; + Assert.NotNull(state); + var error = Assert.Single(state.Errors); + Assert.Equal("A value is required.", error.ErrorMessage); } [Fact] @@ -117,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { innerResult = ModelBindingResult.Failed("somename.key"); } - + var innerBinder = new Mock(); innerBinder .Setup(o => o.BindModelAsync(It.IsAny())) @@ -161,7 +163,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test // Assert Assert.NotEqual(ModelBindingResult.NoResult, result); - Assert.Equal(default(KeyValuePair), Assert.IsType>(result.Model)); + var model = Assert.IsType>(result.Model); + Assert.Equal(default(KeyValuePair), model); Assert.Equal("modelName", result.Key); Assert.True(result.IsModelSet); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultBindingMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultBindingMetadataProviderTest.cs index b4eb111e5d..ca982f4ba5 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultBindingMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultBindingMetadataProviderTest.cs @@ -1,6 +1,7 @@ // 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 Microsoft.AspNet.Mvc.Core; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata @@ -21,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForType(typeof(string)), new ModelAttributes(attributes)); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -45,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForType(typeof(string)), new ModelAttributes(attributes)); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -68,7 +69,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForType(typeof(string)), new ModelAttributes(attributes)); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -92,7 +93,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForType(typeof(string)), new ModelAttributes(attributes)); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -115,7 +116,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForType(typeof(string)), new ModelAttributes(attributes)); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -139,7 +140,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForType(typeof(string)), new ModelAttributes(attributes)); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -161,7 +162,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -184,7 +185,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -207,7 +208,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -230,7 +231,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -253,7 +254,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -279,7 +280,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -297,7 +298,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)), new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -315,7 +316,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)), new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -333,7 +334,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)), new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -356,7 +357,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -379,7 +380,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -402,7 +403,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -425,7 +426,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)), new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -444,7 +445,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOverridesInheritedBindNever)), new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0])); - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -473,7 +474,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata context.BindingMetadata.IsBindingAllowed = initialValue; context.BindingMetadata.IsBindingRequired = initialValue; - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -504,7 +505,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata context.BindingMetadata.IsBindingAllowed = initialValue; context.BindingMetadata.IsBindingRequired = initialValue; - var provider = new DefaultBindingMetadataProvider(); + var provider = new DefaultBindingMetadataProvider(CreateMessageProvider()); // Act provider.GetBindingMetadata(context); @@ -514,6 +515,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired); } + private static ModelBindingMessageProvider CreateMessageProvider() + { + return new ModelBindingMessageProvider + { + MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember, + MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent, + ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid, + }; + } + [BindNever] private class BindNeverOnClass { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs index 1ae12bb4a3..a110f658f7 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs @@ -787,7 +787,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding property => ModelBindingResult.Failed(property.PropertyName)); var nameProperty = containerMetadata.Properties[nameof(model.Name)]; results[nameProperty] = ModelBindingResult.Success(string.Empty, "John Doe"); - + var testableBinder = new TestableMutableObjectModelBinder(); // Act @@ -1219,7 +1219,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding results[lastNameProperty] = ModelBindingResult.Success( nameof(model.LastName), "Doe"); - + var testableBinder = new TestableMutableObjectModelBinder(); // Act diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/SimpleTypeModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/SimpleTypeModelBinderTest.cs index 602019f75c..36b9c04efd 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/SimpleTypeModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/SimpleTypeModelBinderTest.cs @@ -105,7 +105,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test Assert.Null(result.Model); var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors); - Assert.Equal(error.ErrorMessage, "The value '' is invalid.", StringComparer.Ordinal); + Assert.Equal("The value '' is invalid.", error.ErrorMessage, StringComparer.Ordinal); Assert.Null(error.Exception); } diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs index 0ececa2501..4b38426f03 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs @@ -1026,13 +1026,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata public AttributeInjectModelMetadataProvider(object[] attributes) : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[] { - new DefaultBindingMetadataProvider(), + new DefaultBindingMetadataProvider(CreateMessageProvider()), new DataAnnotationsMetadataProvider(), })) { _attributes = attributes; } + private static ModelBindingMessageProvider CreateMessageProvider() + { + return new ModelBindingMessageProvider + { + MissingBindRequiredValueAccessor = name => $"A value for the '{ name }' property was not provided.", + MissingKeyOrValueAccessor = () => $"A value is required.", + ValueMustNotBeNullAccessor = value => $"The value '{ value }' is invalid.", + }; + } + protected override DefaultMetadataDetails CreateTypeDetails(ModelMetadataIdentity key) { var entry = base.CreateTypeDetails(key); diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs index 39c0f46713..ceb01a9305 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.Controllers; using Microsoft.AspNet.Mvc.ModelBinding; using Xunit; @@ -46,13 +47,183 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Key").Value; Assert.Equal("key0", entry.AttemptedValue); - Assert.Equal("key0", entry.RawValue); + Assert.Equal("key0", entry.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Value").Value; Assert.Equal("10", entry.AttemptedValue); Assert.Equal("10", entry.RawValue); } + [Fact] + public async Task KeyValuePairModelBinder_SimpleTypes_WithNoKey_AddsError() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor + { + Name = "parameter", + ParameterType = typeof(KeyValuePair) + }; + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter.Value=10"); + }); + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.False(modelBindingResult.IsModelSet); + Assert.Equal(2, modelState.Count); + + Assert.False(modelState.IsValid); + Assert.Equal(1, modelState.ErrorCount); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Key").Value; + var error = Assert.Single(entry.Errors); + Assert.Null(error.Exception); + Assert.Equal("A value is required.", error.ErrorMessage); + + entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Value").Value; + Assert.Empty(entry.Errors); + Assert.Equal("10", entry.AttemptedValue); + Assert.Equal("10", entry.RawValue); + } + + [Fact] + public async Task KeyValuePairModelBinder_SimpleTypes_WithNoKey_AndCustomizedMessage_AddsGivenMessage() + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForType(typeof(KeyValuePair)) + .BindingDetails((System.Action)(binding => + { + // A real details provider could customize message based on BindingMetadataProviderContext. + binding.ModelBindingMessageProvider.MissingKeyOrValueAccessor = () => $"Hurts when nothing is provided."; + })); + + var argumentBinder = new DefaultControllerActionArgumentBinder( + metadataProvider, + ModelBindingTestHelper.GetObjectValidator()); + var parameter = new ParameterDescriptor + { + Name = "parameter", + ParameterType = typeof(KeyValuePair) + }; + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter.Value=10"); + }); + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.False(modelBindingResult.IsModelSet); + Assert.Equal(2, modelState.Count); + + Assert.False(modelState.IsValid); + Assert.Equal(1, modelState.ErrorCount); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Key").Value; + var error = Assert.Single(entry.Errors); + Assert.Null(error.Exception); + Assert.Equal("Hurts when nothing is provided.", error.ErrorMessage); + + entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Value").Value; + Assert.Empty(entry.Errors); + Assert.Equal("10", entry.AttemptedValue); + Assert.Equal("10", entry.RawValue); + } + + [Fact] + public async Task KeyValuePairModelBinder_SimpleTypes_WithNoValue_AddsError() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor + { + Name = "parameter", + ParameterType = typeof(KeyValuePair) + }; + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter.Key=10"); + }); + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.False(modelBindingResult.IsModelSet); + Assert.Equal(2, modelState.Count); + + Assert.False(modelState.IsValid); + Assert.Equal(1, modelState.ErrorCount); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Key").Value; + Assert.Empty(entry.Errors); + Assert.Equal("10", entry.AttemptedValue); + Assert.Equal("10", entry.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Value").Value; + var error = Assert.Single(entry.Errors); + Assert.Null(error.Exception); + Assert.Equal("A value is required.", error.ErrorMessage); + } + + [Fact] + public async Task KeyValuePairModelBinder_SimpleTypes_WithNoValue_AndCustomizedMessage_AddsGivenMessage() + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForType(typeof(KeyValuePair)) + .BindingDetails((System.Action)(binding => + { + // A real details provider could customize message based on BindingMetadataProviderContext. + binding.ModelBindingMessageProvider.MissingKeyOrValueAccessor = () => $"Hurts when nothing is provided."; + })); + var argumentBinder = new DefaultControllerActionArgumentBinder( + metadataProvider, + ModelBindingTestHelper.GetObjectValidator()); + + var parameter = new ParameterDescriptor + { + Name = "parameter", + ParameterType = typeof(KeyValuePair) + }; + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter.Key=10"); + }); + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.False(modelBindingResult.IsModelSet); + Assert.Equal(2, modelState.Count); + + Assert.False(modelState.IsValid); + Assert.Equal(1, modelState.ErrorCount); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Key").Value; + Assert.Empty(entry.Errors); + Assert.Equal("10", entry.AttemptedValue); + Assert.Equal("10", entry.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Value").Value; + var error = Assert.Single(entry.Errors); + Assert.Null(error.Exception); + Assert.Equal("Hurts when nothing is provided.", error.ErrorMessage); + } + [Fact] public async Task KeyValuePairModelBinder_BindsKeyValuePairOfSimpleType_WithExplicitPrefix_Success() { @@ -90,7 +261,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests var entry = Assert.Single(modelState, kvp => kvp.Key == "prefix.Key").Value; Assert.Equal("key0", entry.AttemptedValue); - Assert.Equal("key0", entry.RawValue); + Assert.Equal("key0", entry.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "prefix.Value").Value; Assert.Equal("10", entry.AttemptedValue); @@ -130,7 +301,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests var entry = Assert.Single(modelState, kvp => kvp.Key == "Key").Value; Assert.Equal("key0", entry.AttemptedValue); - Assert.Equal("key0", entry.RawValue); + Assert.Equal("key0", entry.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "Value").Value; Assert.Equal("10", entry.AttemptedValue); @@ -207,7 +378,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Key").Value; Assert.Equal("key0", entry.AttemptedValue); - Assert.Equal("key0", entry.RawValue); + Assert.Equal("key0", entry.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Value.Id").Value; Assert.Equal("10", entry.AttemptedValue); @@ -252,7 +423,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests var entry = Assert.Single(modelState, kvp => kvp.Key == "prefix.Key").Value; Assert.Equal("key0", entry.AttemptedValue); - Assert.Equal("key0", entry.RawValue); + Assert.Equal("key0", entry.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "prefix.Value.Id").Value; Assert.Equal("10", entry.AttemptedValue); @@ -293,7 +464,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests var entry = Assert.Single(modelState, kvp => kvp.Key == "Key").Value; Assert.Equal("key0", entry.AttemptedValue); - Assert.Equal("key0", entry.RawValue); + Assert.Equal("key0", entry.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "Value.Id").Value; Assert.Equal("10", entry.AttemptedValue); diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs index 08d10d2686..55ca5f20f9 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs @@ -10,6 +10,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features.Internal; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.Controllers; using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.Framework.Primitives; @@ -859,8 +860,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests // Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements. var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { - request.QueryString = - new QueryString("?Name=bill&ProductIds[0]=10&ProductIds[1]=11"); + request.QueryString = new QueryString("?Name=bill&ProductIds[0]=10&ProductIds[1]=11"); }); var modelState = new ModelStateDictionary(); @@ -1032,8 +1032,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests // Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements. var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { - request.QueryString = - new QueryString("?Name=bill&ProductIds[0]=10&ProductIds[1]=11"); + request.QueryString = new QueryString("?Name=bill&ProductIds[0]=10&ProductIds[1]=11"); }); var modelState = new ModelStateDictionary(); @@ -1205,8 +1204,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests // Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements. var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { - request.QueryString = - new QueryString("?Name=bill&ProductIds[0].Key=key0&ProductIds[0].Value=10"); + request.QueryString = new QueryString("?Name=bill&ProductIds[0].Key=key0&ProductIds[0].Value=10"); }); var modelState = new ModelStateDictionary(); @@ -1378,8 +1376,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests // Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements. var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { - request.QueryString = - new QueryString("?Name=bill&ProductId.Key=key0&ProductId.Value=10"); + request.QueryString = new QueryString("?Name=bill&ProductId.Key=key0&ProductId.Value=10"); }); var modelState = new ModelStateDictionary(); @@ -1563,9 +1560,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests }; // No Data - var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => - { - }); + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(); var modelState = new ModelStateDictionary(); @@ -1589,6 +1584,53 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests Assert.Equal("A value for the 'Customer' property was not provided.", error.ErrorMessage); } + [Fact] + public async Task MutableObjectModelBinder_WithBindRequired_NoData_AndCustomizedMessage_AddsGivenMessage() + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForType(typeof(Order10)) + .BindingDetails((Action)(binding => + { + // A real details provider could customize message based on BindingMetadataProviderContext. + binding.ModelBindingMessageProvider.MissingBindRequiredValueAccessor = + name => $"Hurts when '{ name }' is not provided."; + })); + var argumentBinder = new DefaultControllerActionArgumentBinder( + metadataProvider, + ModelBindingTestHelper.GetObjectValidator()); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order10) + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Null(model.Customer); + + Assert.Equal(1, modelState.Count); + Assert.Equal(1, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "Customer").Value; + Assert.Null(entry.RawValue); + Assert.Null(entry.AttemptedValue); + var error = Assert.Single(modelState["Customer"].Errors); + Assert.Equal("Hurts when 'Customer' is not provided.", error.ErrorMessage); + } + private class Order11 { public Person11 Customer { get; set; } diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs index ba2f524781..ebfed42fd2 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.Controllers; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.Framework.Primitives; using Xunit; @@ -262,7 +263,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests }; var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { - request.QueryString = QueryString.Create("Parameter1", string.Empty); + request.QueryString = QueryString.Create("Parameter1", " "); }); var modelState = new ModelStateDictionary(); @@ -277,6 +278,58 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests // Model Assert.Null(modelBindingResult.Model); + // ModelState + Assert.False(modelState.IsValid); + var key = Assert.Single(modelState.Keys); + Assert.Equal("Parameter1", key); + Assert.Equal(" ", modelState[key].AttemptedValue); + Assert.Equal(" ", modelState[key].RawValue); + var error = Assert.Single(modelState[key].Errors); + Assert.Equal("The value ' ' is invalid.", error.ErrorMessage, StringComparer.Ordinal); + Assert.Null(error.Exception); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(bool))] + public async Task BindParameter_WithEmptyData_AndPerTypeMessage_AddsGivenMessage(Type parameterType) + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForType(parameterType) + .BindingDetails((Action)(binding => + { + // A real details provider could customize message based on BindingMetadataProviderContext. + binding.ModelBindingMessageProvider.ValueMustNotBeNullAccessor = + value => $"Hurts when '{ value }' is provided."; + })); + var argumentBinder = new DefaultControllerActionArgumentBinder( + metadataProvider, + ModelBindingTestHelper.GetObjectValidator()); + var parameter = new ParameterDescriptor + { + Name = "Parameter1", + BindingInfo = new BindingInfo(), + + ParameterType = parameterType + }; + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("Parameter1", string.Empty); + }); + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + // ModelBindingResult + Assert.False(modelBindingResult.IsModelSet); + + // Model + Assert.Null(modelBindingResult.Model); + // ModelState Assert.False(modelState.IsValid); var key = Assert.Single(modelState.Keys); @@ -284,7 +337,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests Assert.Equal(string.Empty, modelState[key].AttemptedValue); Assert.Equal(string.Empty, modelState[key].RawValue); var error = Assert.Single(modelState[key].Errors); - Assert.Equal(error.ErrorMessage, "The value '' is invalid.", StringComparer.Ordinal); + Assert.Equal("Hurts when '' is provided.", error.ErrorMessage, StringComparer.Ordinal); Assert.Null(error.Exception); } diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/TestModelMetadataProvider.cs b/test/Microsoft.AspNet.Mvc.TestCommon/TestModelMetadataProvider.cs index 4fe13d7367..0c0d8dd61f 100644 --- a/test/Microsoft.AspNet.Mvc.TestCommon/TestModelMetadataProvider.cs +++ b/test/Microsoft.AspNet.Mvc.TestCommon/TestModelMetadataProvider.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { var detailsProviders = new IMetadataDetailsProvider[] { - new DefaultBindingMetadataProvider(), + new DefaultBindingMetadataProvider(CreateMessageProvider()), new DefaultValidationMetadataProvider(), new DataAnnotationsMetadataProvider(), new DataMemberRequiredBindingMetadataProvider(), @@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private TestModelMetadataProvider(TestModelMetadataDetailsProvider detailsProvider) : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[] { - new DefaultBindingMetadataProvider(), + new DefaultBindingMetadataProvider(CreateMessageProvider()), new DefaultValidationMetadataProvider(), new DataAnnotationsMetadataProvider(), detailsProvider @@ -76,6 +76,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return ForProperty(typeof(TContainer), propertyName); } + private static ModelBindingMessageProvider CreateMessageProvider() + { + return new ModelBindingMessageProvider + { + MissingBindRequiredValueAccessor = name => $"A value for the '{ name }' property was not provided.", + MissingKeyOrValueAccessor = () => $"A value is required.", + ValueMustNotBeNullAccessor = value => $"The value '{ value }' is invalid.", + }; + } + private class TestModelMetadataDetailsProvider : IBindingMetadataProvider, IDisplayMetadataProvider,