Support user overrides of three more framework-provided `ModelState`-related messages
- #3215 - add new accessor properties to `IModelBindingMessageProvider` and plumb them through - use in `ModelStateDictionary` when handling a `FormatException` or `OverflowException` - use in `ValidationHelpers` when handling a `ModelError` with `null` `ErrorMessage` - add new `ModelExplorer` parameter to `IHtmlGenerator.GenerateValidationMessage()` - plumb through to `ValidationHelpers.GetModelErrorMessageOrDefault()` Started from work @kichalla did on the `kiran/movemessages-to-messageprovider` branch in #3775. nits: - use helper methods more consistently in `HtmlHelper<T>`; slightly improves error checking - remove unused `Resources` class from `Microsoft.AspNet.Mvc` - make `ValidationHelpers` class `public`; already in `.Internal` namespace - split `GetUserErrorMessageOrDefault()` in two; rename to `GetModelErrorMessageOrDefault()` - fix some #YOLO wrapping
This commit is contained in:
parent
1144fc0e6c
commit
04453a2b4f
|
|
@ -30,5 +30,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
|||
/// </summary>
|
||||
/// <value>Default <see cref="string"/> is "The value '{0}' is invalid.".</value>
|
||||
Func<string, string> ValueMustNotBeNullAccessor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message the model binding system adds when <see cref="ModelError.Exception"/> is of type
|
||||
/// <see cref="FormatException"/> or <see cref="OverflowException"/> and value is known.
|
||||
/// </summary>
|
||||
/// <value>Default <see cref="string"/> is "The value '{0}' is not valid for {1}.".</value>
|
||||
Func<string, string, string> AttemptedValueIsInvalidAccessor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message the model binding system adds when <see cref="ModelError.Exception"/> is of type
|
||||
/// <see cref="FormatException"/> or <see cref="OverflowException"/> and value is unknown.
|
||||
/// </summary>
|
||||
/// <value>Default <see cref="string"/> is "The supplied value is invalid for {0}.".</value>
|
||||
Func<string, string> UnknownValueIsInvalidAccessor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fallback error message HTML and tag helpers display when a property is invalid but the
|
||||
/// <see cref="ModelError"/>s have <c>null</c> <see cref="ModelError.ErrorMessage"/>s.
|
||||
/// </summary>
|
||||
/// <value>Default <see cref="string"/> is "The value '{0}' is invalid.".</value>
|
||||
Func<string, string> ValueIsInvalidAccessor { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -282,38 +282,6 @@ namespace Microsoft.AspNet.Mvc.Abstractions
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeGreedy"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The supplied value is invalid for {0}.
|
||||
/// </summary>
|
||||
internal static string ModelError_InvalidValue_GenericMessage
|
||||
{
|
||||
get { return GetString("ModelError_InvalidValue_GenericMessage"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The supplied value is invalid for {0}.
|
||||
/// </summary>
|
||||
internal static string FormatModelError_InvalidValue_GenericMessage(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ModelError_InvalidValue_GenericMessage"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is not valid for {1}.
|
||||
/// </summary>
|
||||
internal static string ModelError_InvalidValue_MessageWithModelValue
|
||||
{
|
||||
get { return GetString("ModelError_InvalidValue_MessageWithModelValue"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is not valid for {1}.
|
||||
/// </summary>
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -168,10 +168,4 @@
|
|||
<data name="BindingSource_MustBeGreedy" xml:space="preserve">
|
||||
<value>The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.</value>
|
||||
</data>
|
||||
<data name="ModelError_InvalidValue_GenericMessage" xml:space="preserve">
|
||||
<value>The supplied value is invalid for {0}.</value>
|
||||
</data>
|
||||
<data name="ModelError_InvalidValue_MessageWithModelValue" xml:space="preserve">
|
||||
<value>The value '{0}' is not valid for {1}.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
|||
private Func<string, string> _missingBindRequiredValueAccessor;
|
||||
private Func<string> _missingKeyOrValueAccessor;
|
||||
private Func<string, string> _valueMustNotBeNullAccessor;
|
||||
private Func<string, string, string> _attemptedValueIsInvalidAccessor;
|
||||
private Func<string, string> _unknownValueIsInvalidAccessor;
|
||||
private Func<string, string> _valueIsInvalidAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelBindingMessageProvider"/> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -91,5 +97,59 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
|||
_valueMustNotBeNullAccessor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Func<string, string, string> AttemptedValueIsInvalidAccessor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _attemptedValueIsInvalidAccessor;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_attemptedValueIsInvalidAccessor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Func<string, string> UnknownValueIsInvalidAccessor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _unknownValueIsInvalidAccessor;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_unknownValueIsInvalidAccessor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Func<string, string> ValueIsInvalidAccessor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _valueIsInvalidAccessor;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_valueIsInvalidAccessor = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string UnableToFindServices
|
||||
{
|
||||
|
|
@ -483,11 +483,11 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1066,6 +1066,54 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("AcceptHeaderParser_ParseAcceptHeader_InvalidValues"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is not valid for {1}.
|
||||
/// </summary>
|
||||
internal static string ModelState_AttemptedValueIsInvalid
|
||||
{
|
||||
get { return GetString("ModelState_AttemptedValueIsInvalid"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is not valid for {1}.
|
||||
/// </summary>
|
||||
internal static string FormatModelState_AttemptedValueIsInvalid(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ModelState_AttemptedValueIsInvalid"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The supplied value is invalid for {0}.
|
||||
/// </summary>
|
||||
internal static string ModelState_UnknownValueIsInvalid
|
||||
{
|
||||
get { return GetString("ModelState_UnknownValueIsInvalid"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The supplied value is invalid for {0}.
|
||||
/// </summary>
|
||||
internal static string FormatModelState_UnknownValueIsInvalid(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ModelState_UnknownValueIsInvalid"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid.
|
||||
/// </summary>
|
||||
internal static string HtmlGeneration_ValueIsInvalid
|
||||
{
|
||||
get { return GetString("HtmlGeneration_ValueIsInvalid"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid.
|
||||
/// </summary>
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -325,4 +325,13 @@
|
|||
<data name="AcceptHeaderParser_ParseAcceptHeader_InvalidValues" xml:space="preserve">
|
||||
<value>"Invalid values '{0}'."</value>
|
||||
</data>
|
||||
<data name="ModelState_AttemptedValueIsInvalid" xml:space="preserve">
|
||||
<value>The value '{0}' is not valid for {1}.</value>
|
||||
</data>
|
||||
<data name="ModelState_UnknownValueIsInvalid" xml:space="preserve">
|
||||
<value>The supplied value is invalid for {0}.</value>
|
||||
</data>
|
||||
<data name="HtmlGeneration_ValueIsInvalid" xml:space="preserve">
|
||||
<value>The value '{0}' is invalid.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -698,22 +698,6 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyNotFound"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid.
|
||||
/// </summary>
|
||||
internal static string Common_ValueNotValidForProperty
|
||||
{
|
||||
get { return GetString("Common_ValueNotValidForProperty"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid.
|
||||
/// </summary>
|
||||
internal static string FormatCommon_ValueNotValidForProperty(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Common_ValueNotValidForProperty"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No URL for remote validation could be found.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -247,9 +247,6 @@
|
|||
<data name="Common_PropertyNotFound" xml:space="preserve">
|
||||
<value>The property {0}.{1} could not be found.</value>
|
||||
</data>
|
||||
<data name="Common_ValueNotValidForProperty" xml:space="preserve">
|
||||
<value>The value '{0}' is invalid.</value>
|
||||
</data>
|
||||
<data name="RemoteAttribute_NoUrlFound" xml:space="preserve">
|
||||
<value>No URL for remote validation could be found.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -688,6 +688,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
/// <inheritdoc />
|
||||
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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -676,7 +676,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -133,7 +137,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
var modelExplorer = GetModelExplorer(expression);
|
||||
return GenerateDisplayName(modelExplorer, ExpressionHelper.GetExpressionText(expression));
|
||||
return GenerateDisplayName(modelExplorer, GetExpressionName(expression));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
/// Generate a <input type="checkbox".../> element.
|
||||
/// </summary>
|
||||
/// <param name="viewContext">The <see cref="ViewContext"/> instance for the current scope.</param>
|
||||
/// <param name="modelExplorer">The <see cref="ModelExplorer"/> for the model.</param>
|
||||
/// <param name="expression">The model expression.</param>
|
||||
/// <param name="modelExplorer">The <see cref="ModelExplorer"/> for the <paramref name="expression"/>.</param>
|
||||
/// <param name="expression">Expression name, relative to the current model.</param>
|
||||
/// <param name="isChecked">The initial state of the checkbox element.</param>
|
||||
/// <param name="htmlAttributes">
|
||||
/// An <see cref="object"/> that contains the HTML attributes for the element. Alternatively, an
|
||||
|
|
@ -323,8 +323,36 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
string format,
|
||||
object htmlAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Generate a <paramref name="tag"/> element if the <paramref name="viewContext"/>'s
|
||||
/// <see cref="ActionContext.ModelState"/> contains an error for the <paramref name="expression"/>.
|
||||
/// </summary>
|
||||
/// <param name="viewContext">A <see cref="ViewContext"/> instance for the current scope.</param>
|
||||
/// <param name="modelExplorer">The <see cref="ModelExplorer"/> for the <paramref name="expression"/>.</param>
|
||||
/// <param name="expression">Expression name, relative to the current model.</param>
|
||||
/// <param name="message">
|
||||
/// The message to be displayed. If <c>null</c> or empty, method extracts an error string from the
|
||||
/// <see cref="ModelBinding.ModelStateDictionary"/> object. Message will always be visible but client-side
|
||||
/// validation may update the associated CSS class.
|
||||
/// </param>
|
||||
/// <param name="tag">
|
||||
/// The tag to wrap the <paramref name="message"/> in the generated HTML. Its default value is
|
||||
/// <see cref="ViewContext.ValidationMessageElement"/>.
|
||||
/// </param>
|
||||
/// <param name="htmlAttributes">
|
||||
/// An <see cref="object"/> that contains the HTML attributes for the element. Alternatively, an
|
||||
/// <see cref="IDictionary{string, object}"/> instance containing the HTML attributes.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="TagBuilder"/> containing a <paramref name="tag"/> element if the
|
||||
/// <paramref name="viewContext"/>'s <see cref="ActionContext.ModelState"/> contains an error for the
|
||||
/// <paramref name="expression"/> or (as a placeholder) if client-side validation is enabled. <c>null</c> if
|
||||
/// the <paramref name="expression"/> is valid and client-side validation is disabled.
|
||||
/// </returns>
|
||||
/// <remarks><see cref="ViewContext.ValidationMessageElement"/> is <c>"span"</c> by default.</remarks>
|
||||
TagBuilder GenerateValidationMessage(
|
||||
ViewContext viewContext,
|
||||
ModelExplorer modelExplorer,
|
||||
string expression,
|
||||
string message,
|
||||
string tag,
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
// <auto-generated />
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Unable to find the required services. Please add all the required services by calling AddMvc() before calling UseMvc() in the Application Startup.
|
||||
/// </summary>
|
||||
internal static string UnableToFindServices
|
||||
{
|
||||
get { return GetString("UnableToFindServices"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to find the required services. Please add all the required services by calling AddMvc() before calling UseMvc() in the Application Startup.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ModelBinding.Metadata.BindingMetadata>)(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
|
||||
|
|
|
|||
|
|
@ -93,16 +93,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
// Arrange
|
||||
var expectedViewContext = CreateViewContext();
|
||||
var modelExpression = CreateModelExpression("Hello");
|
||||
var generator = new Mock<IHtmlGenerator>();
|
||||
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<ViewContext>(),
|
||||
It.IsAny<ModelExplorer>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
|
|
@ -214,6 +221,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var setup = generator
|
||||
.Setup(mock => mock.GenerateValidationMessage(
|
||||
It.IsAny<ViewContext>(),
|
||||
It.IsAny<ModelExplorer>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -165,12 +165,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
// Act and assert
|
||||
var ex = Assert.Throws<ArgumentException>(
|
||||
"expression",
|
||||
() => htmlGenerator.GenerateValidationMessage(
|
||||
viewContext,
|
||||
null,
|
||||
"Message",
|
||||
"tag",
|
||||
null));
|
||||
() => htmlGenerator.GenerateValidationMessage(viewContext, null, null, "Message", "tag", null));
|
||||
|
||||
Assert.Equal(expected, ex.Message);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue