diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Metadata/IModelBindingMessageProvider.cs b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Metadata/IModelBindingMessageProvider.cs
index 63bae2b9ab..7374a54646 100644
--- a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Metadata/IModelBindingMessageProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Metadata/IModelBindingMessageProvider.cs
@@ -30,5 +30,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
///
/// Default is "The value '{0}' is invalid.".
Func ValueMustNotBeNullAccessor { get; }
+
+ ///
+ /// Error message the model binding system adds when is of type
+ /// or and value is known.
+ ///
+ /// Default is "The value '{0}' is not valid for {1}.".
+ Func AttemptedValueIsInvalidAccessor { get; }
+
+ ///
+ /// Error message the model binding system adds when is of type
+ /// or and value is unknown.
+ ///
+ /// Default is "The supplied value is invalid for {0}.".
+ Func UnknownValueIsInvalidAccessor { get; }
+
+ ///
+ /// Fallback error message HTML and tag helpers display when a property is invalid but the
+ /// s have null s.
+ ///
+ /// Default is "The value '{0}' is invalid.".
+ Func ValueIsInvalidAccessor { get; }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs
index bb27c24dff..7f211ab12c 100644
--- a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs
+++ b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs
@@ -267,11 +267,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
string errorMessage;
if (entry == null)
{
- errorMessage = Resources.FormatModelError_InvalidValue_GenericMessage(name);
+ errorMessage = metadata.ModelBindingMessageProvider.UnknownValueIsInvalidAccessor(name);
}
else
{
- errorMessage = Resources.FormatModelError_InvalidValue_MessageWithModelValue(
+ errorMessage = metadata.ModelBindingMessageProvider.AttemptedValueIsInvalidAccessor(
entry.AttemptedValue,
name);
}
diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Properties/Resources.Designer.cs
index 3b4ee1a724..ed6b7026cd 100644
--- a/src/Microsoft.AspNet.Mvc.Abstractions/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Abstractions/Properties/Resources.Designer.cs
@@ -282,38 +282,6 @@ namespace Microsoft.AspNet.Mvc.Abstractions
return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeGreedy"), p0, p1);
}
- ///
- /// The supplied value is invalid for {0}.
- ///
- internal static string ModelError_InvalidValue_GenericMessage
- {
- get { return GetString("ModelError_InvalidValue_GenericMessage"); }
- }
-
- ///
- /// The supplied value is invalid for {0}.
- ///
- internal static string FormatModelError_InvalidValue_GenericMessage(object p0)
- {
- return string.Format(CultureInfo.CurrentCulture, GetString("ModelError_InvalidValue_GenericMessage"), p0);
- }
-
- ///
- /// The value '{0}' is not valid for {1}.
- ///
- internal static string ModelError_InvalidValue_MessageWithModelValue
- {
- get { return GetString("ModelError_InvalidValue_MessageWithModelValue"); }
- }
-
- ///
- /// The value '{0}' is not valid for {1}.
- ///
- internal static string FormatModelError_InvalidValue_MessageWithModelValue(object p0, object p1)
- {
- return string.Format(CultureInfo.CurrentCulture, GetString("ModelError_InvalidValue_MessageWithModelValue"), p0, p1);
- }
-
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Resources.resx b/src/Microsoft.AspNet.Mvc.Abstractions/Resources.resx
index d9ad44cf72..6368aa3629 100644
--- a/src/Microsoft.AspNet.Mvc.Abstractions/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Abstractions/Resources.resx
@@ -168,10 +168,4 @@
The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.
-
- The supplied value is invalid for {0}.
-
-
- The value '{0}' is not valid for {1}.
-
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
index b2c9a58e95..4354ac41b7 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
@@ -31,6 +31,9 @@ namespace Microsoft.AspNet.Mvc.Internal
messageProvider.MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember;
messageProvider.MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent;
messageProvider.ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid;
+ messageProvider.AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid;
+ messageProvider.UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid;
+ messageProvider.ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid;
// Set up ModelBinding
options.ModelBinders.Add(new BinderTypeBasedModelBinder());
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs
index 5fb48e82ca..ea8635d795 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs
@@ -40,6 +40,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember,
MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent,
ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid,
+ AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid,
+ UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid,
+ ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid,
};
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs
index 33c6dea706..957827dde8 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/ModelBindingMessageProvider.cs
@@ -13,6 +13,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
private Func _missingBindRequiredValueAccessor;
private Func _missingKeyOrValueAccessor;
private Func _valueMustNotBeNullAccessor;
+ private Func _attemptedValueIsInvalidAccessor;
+ private Func _unknownValueIsInvalidAccessor;
+ private Func _valueIsInvalidAccessor;
///
/// Initializes a new instance of the class.
@@ -36,6 +39,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
MissingBindRequiredValueAccessor = originalProvider.MissingBindRequiredValueAccessor;
MissingKeyOrValueAccessor = originalProvider.MissingKeyOrValueAccessor;
ValueMustNotBeNullAccessor = originalProvider.ValueMustNotBeNullAccessor;
+ AttemptedValueIsInvalidAccessor = originalProvider.AttemptedValueIsInvalidAccessor;
+ UnknownValueIsInvalidAccessor = originalProvider.UnknownValueIsInvalidAccessor;
+ ValueIsInvalidAccessor = originalProvider.ValueIsInvalidAccessor;
}
///
@@ -91,5 +97,59 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
_valueMustNotBeNullAccessor = value;
}
}
+
+ ///
+ public Func AttemptedValueIsInvalidAccessor
+ {
+ get
+ {
+ return _attemptedValueIsInvalidAccessor;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _attemptedValueIsInvalidAccessor = value;
+ }
+ }
+
+ ///
+ public Func UnknownValueIsInvalidAccessor
+ {
+ get
+ {
+ return _unknownValueIsInvalidAccessor;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _unknownValueIsInvalidAccessor = value;
+ }
+ }
+
+ ///
+ public Func ValueIsInvalidAccessor
+ {
+ get
+ {
+ return _valueIsInvalidAccessor;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _valueIsInvalidAccessor = value;
+ }
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
index 712a56ea3b..bfab63354e 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
@@ -475,7 +475,7 @@ namespace Microsoft.AspNet.Mvc.Core
}
///
- /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{4}' in the application startup code.
+ /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.
///
internal static string UnableToFindServices
{
@@ -483,11 +483,11 @@ namespace Microsoft.AspNet.Mvc.Core
}
///
- /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{4}' in the application startup code.
+ /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.
///
- internal static string FormatUnableToFindServices(object p0, object p1, object p4)
+ internal static string FormatUnableToFindServices(object p0, object p1, object p2)
{
- return string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, p4);
+ return string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, p2);
}
///
@@ -1066,6 +1066,54 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("AcceptHeaderParser_ParseAcceptHeader_InvalidValues"), p0);
}
+ ///
+ /// The value '{0}' is not valid for {1}.
+ ///
+ internal static string ModelState_AttemptedValueIsInvalid
+ {
+ get { return GetString("ModelState_AttemptedValueIsInvalid"); }
+ }
+
+ ///
+ /// The value '{0}' is not valid for {1}.
+ ///
+ internal static string FormatModelState_AttemptedValueIsInvalid(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ModelState_AttemptedValueIsInvalid"), p0, p1);
+ }
+
+ ///
+ /// The supplied value is invalid for {0}.
+ ///
+ internal static string ModelState_UnknownValueIsInvalid
+ {
+ get { return GetString("ModelState_UnknownValueIsInvalid"); }
+ }
+
+ ///
+ /// The supplied value is invalid for {0}.
+ ///
+ internal static string FormatModelState_UnknownValueIsInvalid(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ModelState_UnknownValueIsInvalid"), p0);
+ }
+
+ ///
+ /// The value '{0}' is invalid.
+ ///
+ internal static string HtmlGeneration_ValueIsInvalid
+ {
+ get { return GetString("HtmlGeneration_ValueIsInvalid"); }
+ }
+
+ ///
+ /// The value '{0}' is invalid.
+ ///
+ internal static string FormatHtmlGeneration_ValueIsInvalid(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("HtmlGeneration_ValueIsInvalid"), p0);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
index 16d7ca2608..cd6bcce648 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
@@ -1,17 +1,17 @@
-
@@ -325,4 +325,13 @@
"Invalid values '{0}'."
+
+ The value '{0}' is not valid for {1}.
+
+
+ The supplied value is invalid for {0}.
+
+
+ The value '{0}' is invalid.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs
index 3204c83405..89fdf1ec37 100644
--- a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs
@@ -64,11 +64,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
if (For != null)
{
- var tagBuilder = Generator.GenerateValidationMessage(ViewContext,
- For.Name,
- message: null,
- tag: null,
- htmlAttributes: null);
+ var tagBuilder = Generator.GenerateValidationMessage(
+ ViewContext,
+ For.ModelExplorer,
+ For.Name,
+ message: null,
+ tag: null,
+ htmlAttributes: null);
if (tagBuilder != null)
{
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Internal/ValidationHelpers.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Internal/ValidationHelpers.cs
index 1ea1314701..8dfae9cf1c 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Internal/ValidationHelpers.cs
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Internal/ValidationHelpers.cs
@@ -3,27 +3,44 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal
{
- internal static class ValidationHelpers
+ public static class ValidationHelpers
{
- public static string GetUserErrorMessageOrDefault(ModelError modelError, ModelStateEntry entry)
+ public static string GetModelErrorMessageOrDefault(ModelError modelError)
{
+ Debug.Assert(modelError != null);
+
if (!string.IsNullOrEmpty(modelError.ErrorMessage))
{
return modelError.ErrorMessage;
}
- if (entry == null)
+ // Default in the ValidationSummary case is no error message.
+ return string.Empty;
+ }
+
+ public static string GetModelErrorMessageOrDefault(
+ ModelError modelError,
+ ModelStateEntry containingEntry,
+ ModelExplorer modelExplorer)
+ {
+ Debug.Assert(modelError != null);
+ Debug.Assert(containingEntry != null);
+ Debug.Assert(modelExplorer != null);
+
+ if (!string.IsNullOrEmpty(modelError.ErrorMessage))
{
- return string.Empty;
+ return modelError.ErrorMessage;
}
- var attemptedValue = entry.AttemptedValue ?? "null";
- return Resources.FormatCommon_ValueNotValidForProperty(attemptedValue);
+ // Default in the ValidationMessage case is a fallback error message.
+ var attemptedValue = containingEntry.AttemptedValue ?? "null";
+ return modelExplorer.Metadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(attemptedValue);
}
// Returns non-null list of model states, which caller will render in order provided.
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs
index ffd083d356..5ce77f462a 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs
@@ -698,22 +698,6 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
return string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyNotFound"), p0, p1);
}
- ///
- /// The value '{0}' is invalid.
- ///
- internal static string Common_ValueNotValidForProperty
- {
- 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);
- }
-
///
/// No URL for remote validation could be found.
///
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx b/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx
index e389b62f3a..53e8d0d193 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx
@@ -1,17 +1,17 @@
-
@@ -247,9 +247,6 @@
The property {0}.{1} could not be found.
-
- The value '{0}' is invalid.
-
No URL for remote validation could be found.
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs
index f40f7ad036..96024abdba 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs
@@ -688,6 +688,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
///
public virtual TagBuilder GenerateValidationMessage(
ViewContext viewContext,
+ ModelExplorer modelExplorer,
string expression,
string message,
string tag,
@@ -754,8 +755,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
}
else if (modelError != null)
{
+ modelExplorer = modelExplorer ?? ExpressionMetadataProvider.FromStringExpression(
+ expression,
+ viewContext.ViewData,
+ _metadataProvider);
tagBuilder.InnerHtml.SetContent(
- ValidationHelpers.GetUserErrorMessageOrDefault(modelError, entry));
+ ValidationHelpers.GetModelErrorMessageOrDefault(modelError, entry, modelExplorer));
}
if (formContext != null)
@@ -818,7 +823,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
for (var i = 0; i < modelState.Errors.Count; i++)
{
var modelError = modelState.Errors[i];
- var errorText = ValidationHelpers.GetUserErrorMessageOrDefault(modelError, entry: null);
+ var errorText = ValidationHelpers.GetModelErrorMessageOrDefault(modelError);
if (!string.IsNullOrEmpty(errorText))
{
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs
index 1c490a3a44..7d0ce01fa7 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs
@@ -676,7 +676,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
///
public IHtmlContent ValidationMessage(string expression, string message, object htmlAttributes, string tag)
{
- return GenerateValidationMessage(expression, message, htmlAttributes, tag);
+ return GenerateValidationMessage(
+ modelExplorer: null,
+ expression: expression,
+ message: message,
+ tag: tag,
+ htmlAttributes: htmlAttributes);
}
///
@@ -1135,17 +1140,19 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
}
protected virtual IHtmlContent GenerateValidationMessage(
+ ModelExplorer modelExplorer,
string expression,
string message,
- object htmlAttributes,
- string tag)
+ string tag,
+ object htmlAttributes)
{
var tagBuilder = _htmlGenerator.GenerateValidationMessage(
ViewContext,
- expression: expression,
- message: message,
- tag: tag,
- htmlAttributes: htmlAttributes);
+ modelExplorer,
+ expression,
+ message,
+ tag,
+ htmlAttributes);
if (tagBuilder == null)
{
return HtmlString.Empty;
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs
index 35745e27e2..066f7c490f 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs
@@ -80,7 +80,10 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
}
var modelExplorer = GetModelExplorer(expression);
- return GenerateCheckBox(modelExplorer, GetExpressionName(expression), isChecked: null,
+ return GenerateCheckBox(
+ modelExplorer,
+ GetExpressionName(expression),
+ isChecked: null,
htmlAttributes: htmlAttributes);
}
@@ -96,10 +99,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
- var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider);
-
- return GenerateDropDown(modelExplorer, ExpressionHelper.GetExpressionText(expression), selectList,
- optionLabel, htmlAttributes);
+ var modelExplorer = GetModelExplorer(expression);
+ return GenerateDropDown(
+ modelExplorer,
+ GetExpressionName(expression),
+ selectList,
+ optionLabel,
+ htmlAttributes);
}
///
@@ -114,14 +120,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
- var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression,
- ViewData,
- MetadataProvider);
-
- return GenerateDisplay(modelExplorer,
- htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
- templateName,
- additionalViewData);
+ var modelExplorer = GetModelExplorer(expression);
+ return GenerateDisplay(
+ modelExplorer,
+ htmlFieldName ?? GetExpressionName(expression),
+ templateName,
+ additionalViewData);
}
///
@@ -133,7 +137,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
}
var modelExplorer = GetModelExplorer(expression);
- return GenerateDisplayName(modelExplorer, ExpressionHelper.GetExpressionText(expression));
+ return GenerateDisplayName(modelExplorer, GetExpressionName(expression));
}
///
@@ -182,11 +186,10 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
- var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider);
-
+ var modelExplorer = GetModelExplorer(expression);
return GenerateEditor(
modelExplorer,
- htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
+ htmlFieldName ?? GetExpressionName(expression),
templateName,
additionalViewData);
}
@@ -233,11 +236,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
}
var modelExplorer = GetModelExplorer(expression);
- return GenerateLabel(
- modelExplorer,
- ExpressionHelper.GetExpressionText(expression),
- labelText,
- htmlAttributes);
+ return GenerateLabel(modelExplorer, GetExpressionName(expression), labelText, htmlAttributes);
}
///
@@ -252,7 +251,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
}
var modelExplorer = GetModelExplorer(expression);
- var name = ExpressionHelper.GetExpressionText(expression);
+ var name = GetExpressionName(expression);
return GenerateListBox(modelExplorer, name, selectList, htmlAttributes);
}
@@ -365,7 +364,8 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
- var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider);
+ var modelExplorer =
+ ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider);
if (modelExplorer == null)
{
var expressionName = GetExpressionName(expression);
@@ -387,10 +387,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
throw new ArgumentNullException(nameof(expression));
}
- return GenerateValidationMessage(ExpressionHelper.GetExpressionText(expression),
+ var modelExplorer = GetModelExplorer(expression);
+ return GenerateValidationMessage(
+ modelExplorer,
+ GetExpressionName(expression),
message,
- htmlAttributes,
- tag);
+ tag,
+ htmlAttributes);
}
///
@@ -402,11 +405,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
}
var modelExplorer = GetModelExplorer(expression);
- return GenerateValue(
- ExpressionHelper.GetExpressionText(expression),
- modelExplorer.Model,
- format,
- useViewData: false);
+ return GenerateValue(GetExpressionName(expression), modelExplorer.Model, format, useViewData: false);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs
index 994a36e9a9..396df55eae 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs
@@ -69,8 +69,8 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
/// Generate a <input type="checkbox".../> element.
///
/// The instance for the current scope.
- /// The for the model.
- /// The model expression.
+ /// The for the .
+ /// Expression name, relative to the current model.
/// The initial state of the checkbox element.
///
/// An that contains the HTML attributes for the element. Alternatively, an
@@ -323,8 +323,36 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
string format,
object htmlAttributes);
+ ///
+ /// Generate a element if the 's
+ /// contains an error for the .
+ ///
+ /// A instance for the current scope.
+ /// The for the .
+ /// Expression name, relative to the current model.
+ ///
+ /// The message to be displayed. If null or empty, method extracts an error string from the
+ /// object. Message will always be visible but client-side
+ /// validation may update the associated CSS class.
+ ///
+ ///
+ /// The tag to wrap the in the generated HTML. Its default value is
+ /// .
+ ///
+ ///
+ /// An that contains the HTML attributes for the element. Alternatively, an
+ /// instance containing the HTML attributes.
+ ///
+ ///
+ /// A containing a element if the
+ /// 's contains an error for the
+ /// or (as a placeholder) if client-side validation is enabled. null if
+ /// the is valid and client-side validation is disabled.
+ ///
+ /// is "span" by default.
TagBuilder GenerateValidationMessage(
ViewContext viewContext,
+ ModelExplorer modelExplorer,
string expression,
string message,
string tag,
diff --git a/src/Microsoft.AspNet.Mvc/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc/Properties/Resources.Designer.cs
deleted file mode 100644
index e1c008b8f1..0000000000
--- a/src/Microsoft.AspNet.Mvc/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-namespace Microsoft.AspNet.Mvc
-{
- using System.Reflection;
- using System.Resources;
-
- internal static class Resources
- {
- private static readonly ResourceManager _resourceManager
- = new ResourceManager("Microsoft.AspNet.Mvc.Resources", typeof(Resources).GetTypeInfo().Assembly);
-
- ///
- /// Unable to find the required services. Please add all the required services by calling AddMvc() before calling UseMvc() in the Application Startup.
- ///
- internal static string UnableToFindServices
- {
- get { return GetString("UnableToFindServices"); }
- }
-
- ///
- /// Unable to find the required services. Please add all the required services by calling AddMvc() before calling UseMvc() in the Application Startup.
- ///
- internal static string FormatUnableToFindServices()
- {
- return GetString("UnableToFindServices");
- }
-
- private static string GetString(string name, params string[] formatterNames)
- {
- var value = _resourceManager.GetString(name);
-
- System.Diagnostics.Debug.Assert(value != null);
-
- if (formatterNames != null)
- {
- for (var i = 0; i < formatterNames.Length; i++)
- {
- value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
- }
- }
-
- return value;
- }
- }
-}
diff --git a/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs b/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs
index f99c08ff89..03820649c5 100644
--- a/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding
@@ -736,6 +737,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(expected, error.ErrorMessage);
}
+ [Fact]
+ public void ModelStateDictionary_AddsCustomErrorMessage_WhenModelStateNotSet()
+ {
+ // Arrange
+ var expected = "Hmm, the supplied value is not valid for Length.";
+ var dictionary = new ModelStateDictionary();
+
+ var messageProvider = new ModelBindingMessageProvider
+ {
+ MissingBindRequiredValueAccessor = name => "Unexpected MissingBindRequiredValueAccessor use",
+ MissingKeyOrValueAccessor = () => "Unexpected MissingKeyOrValueAccessor use",
+ ValueMustNotBeNullAccessor = value => "Unexpected ValueMustNotBeNullAccessor use",
+ AttemptedValueIsInvalidAccessor =
+ (value, name) => "Unexpected InvalidValueWithKnownAttemptedValueAccessor use",
+ UnknownValueIsInvalidAccessor = name => $"Hmm, the supplied value is not valid for { name }.",
+ ValueIsInvalidAccessor = value => "Unexpected InvalidValueWithUnknownModelErrorAccessor use",
+ };
+ var bindingMetadataProvider = new DefaultBindingMetadataProvider(messageProvider);
+ var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
+ var provider = new DefaultModelMetadataProvider(compositeProvider);
+ var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
+
+ // Act
+ dictionary.TryAddModelError("key", new FormatException(), metadata);
+
+ // Assert
+ var entry = Assert.Single(dictionary);
+ Assert.Equal("key", entry.Key);
+ var error = Assert.Single(entry.Value.Errors);
+ Assert.Equal(expected, error.ErrorMessage);
+ }
+
[Fact]
public void ModelStateDictionary_ReturnSpecificErrorMessage_WhenModelStateSet()
{
@@ -754,6 +787,39 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(expected, error.ErrorMessage);
}
+ [Fact]
+ public void ModelStateDictionary_AddsCustomErrorMessage_WhenModelStateSet()
+ {
+ // Arrange
+ var expected = "Hmm, the value 'some value' is not valid for Length.";
+ var dictionary = new ModelStateDictionary();
+ dictionary.SetModelValue("key", new string[] { "some value" }, "some value");
+
+ var messageProvider = new ModelBindingMessageProvider
+ {
+ MissingBindRequiredValueAccessor = name => "Unexpected MissingBindRequiredValueAccessor use",
+ MissingKeyOrValueAccessor = () => "Unexpected MissingKeyOrValueAccessor use",
+ ValueMustNotBeNullAccessor = value => "Unexpected ValueMustNotBeNullAccessor use",
+ AttemptedValueIsInvalidAccessor =
+ (value, name) => $"Hmm, the value '{ value }' is not valid for { name }.",
+ UnknownValueIsInvalidAccessor = name => "Unexpected InvalidValueWithUnknownAttemptedValueAccessor use",
+ ValueIsInvalidAccessor = value => "Unexpected InvalidValueWithUnknownModelErrorAccessor use",
+ };
+ var bindingMetadataProvider = new DefaultBindingMetadataProvider(messageProvider);
+ var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
+ var provider = new DefaultModelMetadataProvider(compositeProvider);
+ var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
+
+ // Act
+ dictionary.TryAddModelError("key", new FormatException(), metadata);
+
+ // Assert
+ var entry = Assert.Single(dictionary);
+ Assert.Equal("key", entry.Key);
+ var error = Assert.Single(entry.Value.Errors);
+ Assert.Equal(expected, error.ErrorMessage);
+ }
+
[Fact]
public void ModelStateDictionary_NoErrorMessage_ForNonFormatException()
{
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 6374136954..14b7963c5d 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultBindingMetadataProviderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultBindingMetadataProviderTest.cs
@@ -522,6 +522,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
MissingBindRequiredValueAccessor = Resources.FormatModelBinding_MissingBindRequiredMember,
MissingKeyOrValueAccessor = Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent,
ValueMustNotBeNullAccessor = Resources.FormatModelBinding_NullValueNotValid,
+ AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid,
+ UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid,
+ ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid,
};
}
diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs
index 4b38426f03..82895fca04 100644
--- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs
@@ -1037,9 +1037,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
{
return new ModelBindingMessageProvider
{
- MissingBindRequiredValueAccessor = name => $"A value for the '{ name }' property was not provided.",
+ MissingBindRequiredValueAccessor =
+ name => $"A value for the '{ name }' property was not provided.",
MissingKeyOrValueAccessor = () => $"A value is required.",
ValueMustNotBeNullAccessor = value => $"The value '{ value }' is invalid.",
+ AttemptedValueIsInvalidAccessor =
+ (value, name) => $"The value '{ value }' is not valid for { name }.",
+ UnknownValueIsInvalidAccessor = name => $"The supplied value is invalid for { name }.",
+ ValueIsInvalidAccessor = value => $"The value '{ value }' is invalid.",
};
}
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs
index 760403dbc2..81a077ac8f 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs
@@ -9,7 +9,6 @@ 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;
#if !DNXCORE50
using Microsoft.AspNet.Testing.xunit;
@@ -250,6 +249,64 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("The value 'abcd' is not valid for Int32.", error.ErrorMessage);
}
+ [Fact]
+ public async Task BindParameter_NonConvertableValue_GetsCustomErrorMessage()
+ {
+ // Arrange
+ var parameterType = typeof(int);
+ var metadataProvider = new TestModelMetadataProvider();
+ metadataProvider
+ .ForType(parameterType)
+ .BindingDetails(binding =>
+ {
+ // A real details provider could customize message based on BindingMetadataProviderContext.
+ binding.ModelBindingMessageProvider.AttemptedValueIsInvalidAccessor =
+ (value, name) => $"Hmm, '{ value }' is not a valid value for '{ name }'.";
+ });
+ var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
+ var parameter = new ParameterDescriptor()
+ {
+ Name = "Parameter1",
+ BindingInfo = new BindingInfo(),
+ ParameterType = parameterType
+ };
+
+ var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
+ {
+ request.QueryString = QueryString.Create("Parameter1", "abcd");
+ });
+
+ var modelState = operationContext.ActionContext.ModelState;
+
+ // Act
+ var modelBindingResult = await argumentBinder.BindModelAsync(parameter, operationContext);
+
+ // Assert
+
+ // ModelBindingResult
+ Assert.False(modelBindingResult.IsModelSet);
+
+ // Model
+ Assert.Null(modelBindingResult.Model);
+
+ // ModelState
+ Assert.False(modelState.IsValid);
+ Assert.Equal(1, modelState.Count);
+ Assert.Equal(1, modelState.ErrorCount);
+
+ var key = Assert.Single(modelState.Keys);
+ Assert.Equal("Parameter1", key);
+
+ var entry = modelState[key];
+ Assert.Equal("abcd", entry.RawValue);
+ Assert.Equal("abcd", entry.AttemptedValue);
+ Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
+
+ var error = Assert.Single(entry.Errors);
+ Assert.Null(error.Exception);
+ Assert.Equal($"Hmm, 'abcd' is not a valid value for 'Int32'.", error.ErrorMessage);
+ }
+
#if DNXCORE50
[Theory]
#else
@@ -306,12 +363,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var metadataProvider = new TestModelMetadataProvider();
metadataProvider
.ForType(parameterType)
- .BindingDetails((Action)(binding =>
+ .BindingDetails(binding =>
{
// A real details provider could customize message based on BindingMetadataProviderContext.
binding.ModelBindingMessageProvider.ValueMustNotBeNullAccessor =
value => $"Hurts when '{ value }' is provided.";
- }));
+ });
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
var parameter = new ParameterDescriptor
diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs
index 4597209eee..b62f8743ab 100644
--- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs
+++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs
@@ -93,16 +93,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
// Arrange
var expectedViewContext = CreateViewContext();
+ var modelExpression = CreateModelExpression("Hello");
var generator = new Mock();
generator
- .Setup(mock =>
- mock.GenerateValidationMessage(expectedViewContext, "Hello", null, null, null))
+ .Setup(mock => mock.GenerateValidationMessage(
+ expectedViewContext,
+ modelExpression.ModelExplorer,
+ modelExpression.Name,
+ null,
+ null,
+ null))
.Returns(new TagBuilder("span"))
.Verifiable();
var validationMessageTagHelper = new ValidationMessageTagHelper(generator.Object)
{
- For = CreateModelExpression("Hello")
+ For = modelExpression,
};
var expectedPreContent = "original pre-content";
var expectedContent = "original content";
@@ -155,6 +161,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var setup = generator
.Setup(mock => mock.GenerateValidationMessage(
It.IsAny(),
+ It.IsAny(),
It.IsAny(),
It.IsAny(),
It.IsAny(),
@@ -214,6 +221,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var setup = generator
.Setup(mock => mock.GenerateValidationMessage(
It.IsAny(),
+ It.IsAny(),
It.IsAny(),
It.IsAny(),
It.IsAny(),
diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/TestModelMetadataProvider.cs b/test/Microsoft.AspNet.Mvc.TestCommon/TestModelMetadataProvider.cs
index 79301eab25..ddb978c07f 100644
--- a/test/Microsoft.AspNet.Mvc.TestCommon/TestModelMetadataProvider.cs
+++ b/test/Microsoft.AspNet.Mvc.TestCommon/TestModelMetadataProvider.cs
@@ -111,6 +111,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
MissingBindRequiredValueAccessor = name => $"A value for the '{ name }' property was not provided.",
MissingKeyOrValueAccessor = () => $"A value is required.",
ValueMustNotBeNullAccessor = value => $"The value '{ value }' is invalid.",
+ AttemptedValueIsInvalidAccessor = (value, name) => $"The value '{ value }' is not valid for { name }.",
+ UnknownValueIsInvalidAccessor = name => $"The supplied value is invalid for { name }.",
+ ValueIsInvalidAccessor = value => $"The value '{ value }' is invalid.",
};
}
diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs
index 5e1c44833d..0aaba5757b 100644
--- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs
@@ -165,12 +165,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
// Act and assert
var ex = Assert.Throws(
"expression",
- () => htmlGenerator.GenerateValidationMessage(
- viewContext,
- null,
- "Message",
- "tag",
- null));
+ () => htmlGenerator.GenerateValidationMessage(viewContext, null, null, "Message", "tag", null));
Assert.Equal(expected, ex.Message);
}