Support overrides for client-side validation messages
- #2969 - `RemoteAttribute` did not support `IStringLocalizer` overrides - use same `MvcDataAnnotationsLocalizationOptions` property as for other `ValidationAttribute`s - error message `NumericClientModelValidator` added could not be overridden - not related to `IStringLocalizer` because users have no way to set the resource lookup key - extend `IModelBindingMessageProvider` to add the necessary `Func<,>` - also correct problem using resources with `RemoteAttribute` and add lots of tests
This commit is contained in:
parent
d6843b5a9d
commit
26a14a25ab
|
|
@ -51,5 +51,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>Default <see cref="string"/> is "The value '{0}' is invalid.".</value>
|
/// <value>Default <see cref="string"/> is "The value '{0}' is invalid.".</value>
|
||||||
Func<string, string> ValueIsInvalidAccessor { get; }
|
Func<string, string> ValueIsInvalidAccessor { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error message HTML and tag helpers add for client-side validation of numeric formats. Visible in the
|
||||||
|
/// browser if the field for a <c>float</c> property (for example) does not have a correctly-formatted value.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Default <see cref="string"/> is "The field {0} must be a number.".</value>
|
||||||
|
Func<string, string> ValueMustBeANumberAccessor { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
messageProvider.AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid;
|
messageProvider.AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid;
|
||||||
messageProvider.UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid;
|
messageProvider.UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid;
|
||||||
messageProvider.ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid;
|
messageProvider.ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid;
|
||||||
|
messageProvider.ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber;
|
||||||
|
|
||||||
// Set up ModelBinding
|
// Set up ModelBinding
|
||||||
options.ModelBinders.Add(new BinderTypeBasedModelBinder());
|
options.ModelBinders.Add(new BinderTypeBasedModelBinder());
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid,
|
AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid,
|
||||||
UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid,
|
UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid,
|
||||||
ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid,
|
ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid,
|
||||||
|
ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
private Func<string, string, string> _attemptedValueIsInvalidAccessor;
|
private Func<string, string, string> _attemptedValueIsInvalidAccessor;
|
||||||
private Func<string, string> _unknownValueIsInvalidAccessor;
|
private Func<string, string> _unknownValueIsInvalidAccessor;
|
||||||
private Func<string, string> _valueIsInvalidAccessor;
|
private Func<string, string> _valueIsInvalidAccessor;
|
||||||
|
private Func<string, string> _valueMustBeANumberAccessor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ModelBindingMessageProvider"/> class.
|
/// Initializes a new instance of the <see cref="ModelBindingMessageProvider"/> class.
|
||||||
|
|
@ -42,6 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
AttemptedValueIsInvalidAccessor = originalProvider.AttemptedValueIsInvalidAccessor;
|
AttemptedValueIsInvalidAccessor = originalProvider.AttemptedValueIsInvalidAccessor;
|
||||||
UnknownValueIsInvalidAccessor = originalProvider.UnknownValueIsInvalidAccessor;
|
UnknownValueIsInvalidAccessor = originalProvider.UnknownValueIsInvalidAccessor;
|
||||||
ValueIsInvalidAccessor = originalProvider.ValueIsInvalidAccessor;
|
ValueIsInvalidAccessor = originalProvider.ValueIsInvalidAccessor;
|
||||||
|
ValueMustBeANumberAccessor = originalProvider.ValueMustBeANumberAccessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -151,5 +153,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
_valueIsInvalidAccessor = value;
|
_valueIsInvalidAccessor = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Func<string, string> ValueMustBeANumberAccessor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _valueMustBeANumberAccessor;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
_valueMustBeANumberAccessor = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1002,38 +1002,6 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
return GetString("UrlNotLocal");
|
return GetString("UrlNotLocal");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The stream must support reading.
|
|
||||||
/// </summary>
|
|
||||||
internal static string HttpRequestStreamReader_StreamNotReadable
|
|
||||||
{
|
|
||||||
get { return GetString("HttpRequestStreamReader_StreamNotReadable"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The stream must support reading.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatHttpRequestStreamReader_StreamNotReadable()
|
|
||||||
{
|
|
||||||
return GetString("HttpRequestStreamReader_StreamNotReadable");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The stream must support writing.
|
|
||||||
/// </summary>
|
|
||||||
internal static string HttpResponseStreamWriter_StreamNotWritable
|
|
||||||
{
|
|
||||||
get { return GetString("HttpResponseStreamWriter_StreamNotWritable"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The stream must support writing.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatHttpResponseStreamWriter_StreamNotWritable()
|
|
||||||
{
|
|
||||||
return GetString("HttpResponseStreamWriter_StreamNotWritable");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The argument '{0}' is invalid. Empty or null formats are not supported.
|
/// The argument '{0}' is invalid. Empty or null formats are not supported.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -1114,6 +1082,22 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlGeneration_ValueIsInvalid"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlGeneration_ValueIsInvalid"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The field {0} must be a number.
|
||||||
|
/// </summary>
|
||||||
|
internal static string HtmlGeneration_ValueMustBeNumber
|
||||||
|
{
|
||||||
|
get { return GetString("HtmlGeneration_ValueMustBeNumber"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The field {0} must be a number.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatHtmlGeneration_ValueMustBeNumber(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlGeneration_ValueMustBeNumber"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -328,4 +328,7 @@
|
||||||
<data name="HtmlGeneration_ValueIsInvalid" xml:space="preserve">
|
<data name="HtmlGeneration_ValueIsInvalid" xml:space="preserve">
|
||||||
<value>The value '{0}' is invalid.</value>
|
<value>The value '{0}' is invalid.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="HtmlGeneration_ValueMustBeNumber" xml:space="preserve">
|
||||||
|
<value>The field {0} must be a number.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -44,7 +44,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
throw new ArgumentNullException(nameof(modelMetadata));
|
throw new ArgumentNullException(nameof(modelMetadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Resources.FormatNumericClientModelValidator_FieldMustBeNumber(modelMetadata.GetDisplayName());
|
return modelMetadata.ModelBindingMessageProvider.ValueMustBeANumberAccessor(
|
||||||
|
modelMetadata.GetDisplayName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,22 +42,6 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
|
||||||
return GetString("ArgumentCannotBeNullOrEmpty");
|
return GetString("ArgumentCannotBeNullOrEmpty");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The field {0} must be a number.
|
|
||||||
/// </summary>
|
|
||||||
internal static string NumericClientModelValidator_FieldMustBeNumber
|
|
||||||
{
|
|
||||||
get { return GetString("NumericClientModelValidator_FieldMustBeNumber"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The field {0} must be a number.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatNumericClientModelValidator_FieldMustBeNumber(object p0)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("NumericClientModelValidator_FieldMustBeNumber"), p0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The '{0}' property of '{1}' must not be null.
|
/// The '{0}' property of '{1}' must not be null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -123,9 +123,6 @@
|
||||||
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||||
<value>Value cannot be null or empty.</value>
|
<value>Value cannot be null or empty.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NumericClientModelValidator_FieldMustBeNumber" xml:space="preserve">
|
|
||||||
<value>The field {0} must be a number.</value>
|
|
||||||
</data>
|
|
||||||
<data name="PropertyOfTypeCannotBeNull" xml:space="preserve">
|
<data name="PropertyOfTypeCannotBeNull" xml:space="preserve">
|
||||||
<value>The '{0}' property of '{1}' must not be null.</value>
|
<value>The '{0}' property of '{1}' must not be null.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
|
|
@ -25,6 +27,8 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
private string _additionalFields = string.Empty;
|
private string _additionalFields = string.Empty;
|
||||||
private string[] _additionalFieldsSplit = new string[0];
|
private string[] _additionalFieldsSplit = new string[0];
|
||||||
|
private bool _checkedForLocalizer;
|
||||||
|
private IStringLocalizer _stringLocalizer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
|
/// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
|
||||||
|
|
@ -33,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// Intended for subclasses that support URL generation with no route, action, or controller names.
|
/// Intended for subclasses that support URL generation with no route, action, or controller names.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected RemoteAttribute()
|
protected RemoteAttribute()
|
||||||
: base(Resources.RemoteAttribute_RemoteValidationFailed)
|
: base(errorMessageAccessor: () => Resources.RemoteAttribute_RemoteValidationFailed)
|
||||||
{
|
{
|
||||||
RouteData = new RouteValueDictionary();
|
RouteData = new RouteValueDictionary();
|
||||||
}
|
}
|
||||||
|
|
@ -241,8 +245,10 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
MergeAttribute(context.Attributes, "data-val", "true");
|
MergeAttribute(context.Attributes, "data-val", "true");
|
||||||
|
|
||||||
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
|
CheckForLocalizer(context);
|
||||||
|
var errorMessage = GetErrorMessage(context.ModelMetadata.GetDisplayName());
|
||||||
MergeAttribute(context.Attributes, "data-val-remote", errorMessage);
|
MergeAttribute(context.Attributes, "data-val-remote", errorMessage);
|
||||||
|
|
||||||
MergeAttribute(context.Attributes, "data-val-remote-url", GetUrl(context));
|
MergeAttribute(context.Attributes, "data-val-remote-url", GetUrl(context));
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(HttpMethod))
|
if (!string.IsNullOrEmpty(HttpMethod))
|
||||||
|
|
@ -272,10 +278,45 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
return new string[0];
|
return new string[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
var split = original.Split(',')
|
var split = original
|
||||||
.Select(piece => piece.Trim())
|
.Split(',')
|
||||||
.Where(trimmed => !string.IsNullOrEmpty(trimmed));
|
.Select(piece => piece.Trim())
|
||||||
|
.Where(trimmed => !string.IsNullOrEmpty(trimmed));
|
||||||
|
|
||||||
return split;
|
return split;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CheckForLocalizer(ClientModelValidationContext context)
|
||||||
|
{
|
||||||
|
if (!_checkedForLocalizer)
|
||||||
|
{
|
||||||
|
_checkedForLocalizer = true;
|
||||||
|
|
||||||
|
var services = context.ActionContext.HttpContext.RequestServices;
|
||||||
|
var options = services.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
var factory = services.GetService<IStringLocalizerFactory>();
|
||||||
|
|
||||||
|
var provider = options.Value.DataAnnotationLocalizerProvider;
|
||||||
|
if (factory != null && provider != null)
|
||||||
|
{
|
||||||
|
_stringLocalizer = provider(
|
||||||
|
context.ModelMetadata.ContainerType ?? context.ModelMetadata.ModelType,
|
||||||
|
factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetErrorMessage(string displayName)
|
||||||
|
{
|
||||||
|
if (_stringLocalizer != null &&
|
||||||
|
!string.IsNullOrEmpty(ErrorMessage) &&
|
||||||
|
string.IsNullOrEmpty(ErrorMessageResourceName) &&
|
||||||
|
ErrorMessageResourceType == null)
|
||||||
|
{
|
||||||
|
return _stringLocalizer[ErrorMessage, displayName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormatErrorMessage(displayName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -754,6 +754,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
(value, name) => "Unexpected InvalidValueWithKnownAttemptedValueAccessor use",
|
(value, name) => "Unexpected InvalidValueWithKnownAttemptedValueAccessor use",
|
||||||
UnknownValueIsInvalidAccessor = name => $"Hmm, the supplied value is not valid for { name }.",
|
UnknownValueIsInvalidAccessor = name => $"Hmm, the supplied value is not valid for { name }.",
|
||||||
ValueIsInvalidAccessor = value => "Unexpected InvalidValueWithUnknownModelErrorAccessor use",
|
ValueIsInvalidAccessor = value => "Unexpected InvalidValueWithUnknownModelErrorAccessor use",
|
||||||
|
ValueMustBeANumberAccessor = name => "Unexpected ValueMustBeANumberAccessor use",
|
||||||
};
|
};
|
||||||
var bindingMetadataProvider = new DefaultBindingMetadataProvider(messageProvider);
|
var bindingMetadataProvider = new DefaultBindingMetadataProvider(messageProvider);
|
||||||
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
||||||
|
|
@ -805,6 +806,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
(value, name) => $"Hmm, the value '{ value }' is not valid for { name }.",
|
(value, name) => $"Hmm, the value '{ value }' is not valid for { name }.",
|
||||||
UnknownValueIsInvalidAccessor = name => "Unexpected InvalidValueWithUnknownAttemptedValueAccessor use",
|
UnknownValueIsInvalidAccessor = name => "Unexpected InvalidValueWithUnknownAttemptedValueAccessor use",
|
||||||
ValueIsInvalidAccessor = value => "Unexpected InvalidValueWithUnknownModelErrorAccessor use",
|
ValueIsInvalidAccessor = value => "Unexpected InvalidValueWithUnknownModelErrorAccessor use",
|
||||||
|
ValueMustBeANumberAccessor = name => "Unexpected ValueMustBeANumberAccessor use",
|
||||||
};
|
};
|
||||||
var bindingMetadataProvider = new DefaultBindingMetadataProvider(messageProvider);
|
var bindingMetadataProvider = new DefaultBindingMetadataProvider(messageProvider);
|
||||||
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
||||||
|
|
|
||||||
|
|
@ -527,6 +527,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid,
|
AttemptedValueIsInvalidAccessor = Resources.FormatModelState_AttemptedValueIsInvalid,
|
||||||
UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid,
|
UnknownValueIsInvalidAccessor = Resources.FormatModelState_UnknownValueIsInvalid,
|
||||||
ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid,
|
ValueIsInvalidAccessor = Resources.FormatHtmlGeneration_ValueIsInvalid,
|
||||||
|
ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1067,6 +1067,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
(value, name) => $"The value '{ value }' is not valid for { name }.",
|
(value, name) => $"The value '{ value }' is not valid for { name }.",
|
||||||
UnknownValueIsInvalidAccessor = name => $"The supplied value is invalid for { name }.",
|
UnknownValueIsInvalidAccessor = name => $"The supplied value is invalid for { name }.",
|
||||||
ValueIsInvalidAccessor = value => $"The value '{ value }' is invalid.",
|
ValueIsInvalidAccessor = value => $"The value '{ value }' is invalid.",
|
||||||
|
ValueMustBeANumberAccessor = name => $"The field { name } must be a number.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,35 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
kvp => { Assert.Equal("data-val-number", kvp.Key); Assert.Equal(expectedMessage, kvp.Value); });
|
kvp => { Assert.Equal("data-val-number", kvp.Key); Assert.Equal(expectedMessage, kvp.Value); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_CorrectValidationTypeAndOverriddenErrorMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedMessage = "Error message about 'DisplayId' from override.";
|
||||||
|
var provider = new TestModelMetadataProvider();
|
||||||
|
provider
|
||||||
|
.ForProperty(typeof(TypeWithNumericProperty), nameof(TypeWithNumericProperty.Id))
|
||||||
|
.BindingDetails(d => d.ModelBindingMessageProvider.ValueMustBeANumberAccessor =
|
||||||
|
name => $"Error message about '{ name }' from override.");
|
||||||
|
var metadata = provider.GetMetadataForProperty(
|
||||||
|
typeof(TypeWithNumericProperty),
|
||||||
|
nameof(TypeWithNumericProperty.Id));
|
||||||
|
|
||||||
|
var adapter = new NumericClientModelValidator();
|
||||||
|
|
||||||
|
var actionContext = new ActionContext();
|
||||||
|
var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
adapter.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-number", kvp.Key); Assert.Equal(expectedMessage, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
[ReplaceCulture]
|
||||||
public void AddValidation_DoesNotTrounceExistingAttributes()
|
public void AddValidation_DoesNotTrounceExistingAttributes()
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
AttemptedValueIsInvalidAccessor = (value, name) => $"The value '{ value }' is not valid for { name }.",
|
AttemptedValueIsInvalidAccessor = (value, name) => $"The value '{ value }' is not valid for { name }.",
|
||||||
UnknownValueIsInvalidAccessor = name => $"The supplied value is invalid for { name }.",
|
UnknownValueIsInvalidAccessor = name => $"The supplied value is invalid for { name }.",
|
||||||
ValueIsInvalidAccessor = value => $"The value '{ value }' is invalid.",
|
ValueIsInvalidAccessor = value => $"The value '{ value }' is invalid.",
|
||||||
|
ValueMustBeANumberAccessor = name => $"The field { name } must be a number.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,22 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
||||||
return GetString("DisplayAttribute_Name");
|
return GetString("DisplayAttribute_Name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error about '{0}' from resources.
|
||||||
|
/// </summary>
|
||||||
|
internal static string RemoteAttribute_Error
|
||||||
|
{
|
||||||
|
get { return GetString("RemoteAttribute_Error"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error about '{0}' from resources.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatRemoteAttribute_Error(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("RemoteAttribute_Error"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,22 @@ using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Extensions.WebEncoders.Testing;
|
using Microsoft.Extensions.WebEncoders.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Test.Resources;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
|
|
@ -26,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
private static readonly IModelMetadataProvider _metadataProvider = new EmptyModelMetadataProvider();
|
private static readonly IModelMetadataProvider _metadataProvider = new EmptyModelMetadataProvider();
|
||||||
private static readonly ModelMetadata _metadata = _metadataProvider.GetMetadataForProperty(
|
private static readonly ModelMetadata _metadata = _metadataProvider.GetMetadataForProperty(
|
||||||
typeof(string),
|
typeof(string),
|
||||||
"Length");
|
nameof(string.Length));
|
||||||
|
|
||||||
public static TheoryData<string> SomeNames
|
public static TheoryData<string> SomeNames
|
||||||
{
|
{
|
||||||
|
|
@ -59,8 +63,8 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
public void IsValidAlwaysReturnsTrue()
|
public void IsValidAlwaysReturnsTrue()
|
||||||
{
|
{
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
Assert.True(new RemoteAttribute("RouteName", "ParameterName").IsValid(null));
|
Assert.True(new RemoteAttribute("RouteName", "ParameterName").IsValid(value: null));
|
||||||
Assert.True(new RemoteAttribute("ActionName", "ControllerName", "ParameterName").IsValid(null));
|
Assert.True(new RemoteAttribute("ActionName", "ControllerName", "ParameterName").IsValid(value: null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -155,6 +159,70 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
Assert.Null(attribute.RouteName);
|
Assert.Null(attribute.RouteName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ErrorMessageProperties_HaveExpectedDefaultValues()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(attribute.ErrorMessage);
|
||||||
|
Assert.Null(attribute.ErrorMessageResourceName);
|
||||||
|
Assert.Null(attribute.ErrorMessageResourceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void FormatErrorMessage_ReturnsDefaultErrorMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// See ViewFeatures.Resources.RemoteAttribute_RemoteValidationFailed.
|
||||||
|
var expected = "'Property1' is invalid.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var message = attribute.FormatErrorMessage("Property1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FormatErrorMessage_UsesOverriddenErrorMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Property1' from override.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller")
|
||||||
|
{
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var message = attribute.FormatErrorMessage("Property1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void FormatErrorMessage_UsesErrorMessageFromResource()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Property1' from resources.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller")
|
||||||
|
{
|
||||||
|
ErrorMessageResourceName = nameof(Resources.RemoteAttribute_Error),
|
||||||
|
ErrorMessageResourceType = typeof(Resources),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var message = attribute.FormatErrorMessage("Property1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, message);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(NullOrEmptyNames))]
|
[MemberData(nameof(NullOrEmptyNames))]
|
||||||
public void FormatAdditionalFieldsForClientValidation_WithInvalidPropertyName_Throws(string property)
|
public void FormatAdditionalFieldsForClientValidation_WithInvalidPropertyName_Throws(string property)
|
||||||
|
|
@ -197,6 +265,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithRoute_CallsUrlHelperWithExpectedValues()
|
public void AddValidation_WithRoute_CallsUrlHelperWithExpectedValues()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -222,6 +291,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionController_CallsUrlHelperWithExpectedValues()
|
public void AddValidation_WithActionController_CallsUrlHelperWithExpectedValues()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -248,6 +318,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionController_PropertiesSet_CallsUrlHelperWithExpectedValues()
|
public void AddValidation_WithActionController_PropertiesSet_CallsUrlHelperWithExpectedValues()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -283,6 +354,294 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessage_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from override.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller")
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var context = GetValidationContext(url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageAndLocalizerFactory_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from override.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller")
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var context = GetValidationContextWithLocalizerFactory(url);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
// IStringLocalizerFactory existence alone is insufficient to change error message.
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageAndLocalizerProvider_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from override.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller")
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var context = GetValidationContext(url);
|
||||||
|
|
||||||
|
var options = context.ActionContext.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
||||||
|
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
// Non-null DataAnnotationLocalizerProvider alone is insufficient to change error message.
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageLocalizerFactoryAndLocalizerProvider_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from localizer.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller")
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var context = GetValidationContextWithLocalizerFactory(url);
|
||||||
|
|
||||||
|
var localizedString = new LocalizedString("Fred", expected);
|
||||||
|
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
||||||
|
localizer
|
||||||
|
.Setup(l => l["Error about '{0}' from override.", "Length"])
|
||||||
|
.Returns(localizedString)
|
||||||
|
.Verifiable();
|
||||||
|
var options = context.ActionContext.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
localizer.VerifyAll();
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void AddValidation_WithErrorResourcesLocalizerFactoryAndLocalizerProvider_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from resources.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller")
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessageResourceName = nameof(Resources.RemoteAttribute_Error),
|
||||||
|
ErrorMessageResourceType = typeof(Resources),
|
||||||
|
};
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var context = GetValidationContextWithLocalizerFactory(url);
|
||||||
|
|
||||||
|
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
||||||
|
var options = context.ActionContext.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
// Configuring the attribute using ErrorMessageResource* trumps available IStringLocalizer etc.
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageAndDisplayName_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Display Length' from override.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller")
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
metadataProvider
|
||||||
|
.ForProperty(typeof(string), nameof(string.Length))
|
||||||
|
.DisplayDetails(d => d.DisplayName = () => "Display Length");
|
||||||
|
var context = GetValidationContext(url, metadataProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_WithErrorMessageLocalizerFactoryLocalizerProviderAndDisplayName_SetsAttributesAsExpected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Error about 'Length' from localizer.";
|
||||||
|
var attribute = new RemoteAttribute("Action", "Controller")
|
||||||
|
{
|
||||||
|
HttpMethod = "POST",
|
||||||
|
ErrorMessage = "Error about '{0}' from override.",
|
||||||
|
};
|
||||||
|
|
||||||
|
var url = "/Controller/Action";
|
||||||
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
metadataProvider
|
||||||
|
.ForProperty(typeof(string), nameof(string.Length))
|
||||||
|
.DisplayDetails(d => d.DisplayName = () => "Display Length");
|
||||||
|
var context = GetValidationContextWithLocalizerFactory(url, metadataProvider);
|
||||||
|
|
||||||
|
var localizedString = new LocalizedString("Fred", expected);
|
||||||
|
var localizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
|
||||||
|
localizer
|
||||||
|
.Setup(l => l["Error about '{0}' from override.", "Display Length"])
|
||||||
|
.Returns(localizedString)
|
||||||
|
.Verifiable();
|
||||||
|
var options = context.ActionContext.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<IOptions<MvcDataAnnotationsLocalizationOptions>>();
|
||||||
|
options.Value.DataAnnotationLocalizerProvider = (type, factory) => localizer.Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
localizer.VerifyAll();
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote", kvp.Key);
|
||||||
|
Assert.Equal(expected, kvp.Value);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("data-val-remote-additionalfields", kvp.Key);
|
||||||
|
Assert.Equal("*.Length", kvp.Value);
|
||||||
|
},
|
||||||
|
kvp => { Assert.Equal("data-val-remote-type", kvp.Key); Assert.Equal("POST", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal(url, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionControllerArea_CallsUrlHelperWithExpectedValues()
|
public void AddValidation_WithActionControllerArea_CallsUrlHelperWithExpectedValues()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -319,6 +678,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
// Root area is current in this case.
|
// Root area is current in this case.
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionController_FindsControllerInCurrentArea()
|
public void AddValidation_WithActionController_FindsControllerInCurrentArea()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -343,6 +703,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
// Test area is current in this case.
|
// Test area is current in this case.
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionControllerInArea_FindsControllerInCurrentArea()
|
public void AddValidation_WithActionControllerInArea_FindsControllerInCurrentArea()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -368,6 +729,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
// Explicit reference to the (current) root area.
|
// Explicit reference to the (current) root area.
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(NullOrEmptyNames))]
|
[MemberData(nameof(NullOrEmptyNames))]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionControllerArea_FindsControllerInRootArea(string areaName)
|
public void AddValidation_WithActionControllerArea_FindsControllerInRootArea(string areaName)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -393,6 +755,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
// Test area is current in this case.
|
// Test area is current in this case.
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(NullOrEmptyNames))]
|
[MemberData(nameof(NullOrEmptyNames))]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionControllerAreaInArea_FindsControllerInRootArea(string areaName)
|
public void AddValidation_WithActionControllerAreaInArea_FindsControllerInRootArea(string areaName)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -417,6 +780,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
// Root area is current in this case.
|
// Root area is current in this case.
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionControllerArea_FindsControllerInNamedArea()
|
public void AddValidation_WithActionControllerArea_FindsControllerInNamedArea()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -441,6 +805,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
// Explicit reference to the current (Test) area.
|
// Explicit reference to the current (Test) area.
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionControllerAreaInArea_FindsControllerInNamedArea()
|
public void AddValidation_WithActionControllerAreaInArea_FindsControllerInNamedArea()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -465,6 +830,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
// Test area is current in this case.
|
// Test area is current in this case.
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
public void AddValidation_WithActionControllerAreaInArea_FindsControllerInDifferentArea()
|
public void AddValidation_WithActionControllerAreaInArea_FindsControllerInDifferentArea()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -518,35 +884,59 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal("original", kvp.Value); });
|
kvp => { Assert.Equal("data-val-remote-url", kvp.Key); Assert.Equal("original", kvp.Value); });
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClientModelValidationContext GetValidationContext(IUrlHelper urlHelper)
|
private static ClientModelValidationContext GetValidationContext(
|
||||||
|
string url,
|
||||||
|
IModelMetadataProvider metadataProvider = null)
|
||||||
{
|
{
|
||||||
var factory = new Mock<IUrlHelperFactory>();
|
var urlHelper = new MockUrlHelper(url, routeName: null);
|
||||||
|
return GetValidationContext(urlHelper, localizerFactory: null, metadataProvider: metadataProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClientModelValidationContext GetValidationContextWithLocalizerFactory(
|
||||||
|
string url,
|
||||||
|
IModelMetadataProvider metadataProvider = null)
|
||||||
|
{
|
||||||
|
var urlHelper = new MockUrlHelper(url, routeName: null);
|
||||||
|
var localizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict);
|
||||||
|
return GetValidationContext(urlHelper, localizerFactory.Object, metadataProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClientModelValidationContext GetValidationContext(
|
||||||
|
IUrlHelper urlHelper,
|
||||||
|
IStringLocalizerFactory localizerFactory = null,
|
||||||
|
IModelMetadataProvider metadataProvider = null)
|
||||||
|
{
|
||||||
|
var serviceCollection = GetServiceCollection(localizerFactory);
|
||||||
|
var factory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
|
serviceCollection.AddSingleton<IUrlHelperFactory>(factory.Object);
|
||||||
|
|
||||||
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
var actionContext = GetActionContext(serviceProvider, routeData: null);
|
||||||
|
|
||||||
factory
|
factory
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
.Setup(f => f.GetUrlHelper(actionContext))
|
||||||
.Returns(urlHelper);
|
.Returns(urlHelper);
|
||||||
|
|
||||||
var serviceCollection = GetServiceCollection();
|
var metadata = _metadata;
|
||||||
serviceCollection.AddSingleton<IUrlHelperFactory>(factory.Object);
|
if (metadataProvider == null)
|
||||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
|
||||||
|
|
||||||
var actionContext = new ActionContext()
|
|
||||||
{
|
{
|
||||||
HttpContext = new DefaultHttpContext()
|
metadataProvider = _metadataProvider;
|
||||||
{
|
}
|
||||||
RequestServices = serviceProvider,
|
else
|
||||||
},
|
{
|
||||||
};
|
metadata = metadataProvider.GetMetadataForProperty(typeof(string), nameof(string.Length));
|
||||||
|
}
|
||||||
|
|
||||||
return new ClientModelValidationContext(
|
return new ClientModelValidationContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
_metadata,
|
metadata,
|
||||||
_metadataProvider,
|
metadataProvider,
|
||||||
new AttributeDictionary());
|
new AttributeDictionary());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClientModelValidationContext GetValidationContextWithArea(string currentArea)
|
private static ClientModelValidationContext GetValidationContextWithArea(string currentArea)
|
||||||
{
|
{
|
||||||
var serviceCollection = GetServiceCollection();
|
var serviceCollection = GetServiceCollection(localizerFactory: null);
|
||||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
var routeCollection = GetRouteCollectionWithArea(serviceProvider);
|
var routeCollection = GetRouteCollectionWithArea(serviceProvider);
|
||||||
var routeData = new RouteData
|
var routeData = new RouteData
|
||||||
|
|
@ -566,24 +956,18 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
routeData.Values["area"] = currentArea;
|
routeData.Values["area"] = currentArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = GetActionContext(serviceProvider, routeData);
|
var actionContext = GetActionContext(serviceProvider, routeData);
|
||||||
|
|
||||||
var urlHelper = new UrlHelper(context);
|
var urlHelper = new UrlHelper(actionContext);
|
||||||
var factory = new Mock<IUrlHelperFactory>();
|
var factory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
factory
|
factory
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
.Setup(f => f.GetUrlHelper(actionContext))
|
||||||
.Returns(urlHelper);
|
.Returns(urlHelper);
|
||||||
|
|
||||||
|
// Make an IUrlHelperFactory available through the ActionContext.
|
||||||
serviceCollection.AddSingleton<IUrlHelperFactory>(factory.Object);
|
serviceCollection.AddSingleton<IUrlHelperFactory>(factory.Object);
|
||||||
serviceProvider = serviceCollection.BuildServiceProvider();
|
serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
actionContext.HttpContext.RequestServices = serviceProvider;
|
||||||
var actionContext = new ActionContext()
|
|
||||||
{
|
|
||||||
HttpContext = new DefaultHttpContext()
|
|
||||||
{
|
|
||||||
RequestServices = serviceProvider,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return new ClientModelValidationContext(
|
return new ClientModelValidationContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
|
|
@ -615,7 +999,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
private static RouteBuilder GetRouteBuilder(IServiceProvider serviceProvider)
|
private static RouteBuilder GetRouteBuilder(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
var app = new Mock<IApplicationBuilder>();
|
var app = new Mock<IApplicationBuilder>(MockBehavior.Strict);
|
||||||
app
|
app
|
||||||
.SetupGet(a => a.ApplicationServices)
|
.SetupGet(a => a.ApplicationServices)
|
||||||
.Returns(serviceProvider);
|
.Returns(serviceProvider);
|
||||||
|
|
@ -631,9 +1015,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ActionContext GetActionContext(
|
private static ActionContext GetActionContext(IServiceProvider serviceProvider, RouteData routeData)
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
RouteData routeData = null)
|
|
||||||
{
|
{
|
||||||
// Set IServiceProvider properties because TemplateRoute gets services (e.g. an ILoggerFactory instance)
|
// Set IServiceProvider properties because TemplateRoute gets services (e.g. an ILoggerFactory instance)
|
||||||
// through the HttpContext.
|
// through the HttpContext.
|
||||||
|
|
@ -653,23 +1035,22 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
return new ActionContext(httpContext, routeData, new ActionDescriptor());
|
return new ActionContext(httpContext, routeData, new ActionDescriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ServiceCollection GetServiceCollection()
|
private static ServiceCollection GetServiceCollection(IStringLocalizerFactory localizerFactory)
|
||||||
{
|
{
|
||||||
var serviceCollection = new ServiceCollection();
|
var serviceCollection = new ServiceCollection();
|
||||||
serviceCollection.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
|
serviceCollection.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
|
||||||
serviceCollection.AddSingleton<UrlEncoder>(new UrlTestEncoder());
|
serviceCollection.AddSingleton<UrlEncoder>(new UrlTestEncoder());
|
||||||
|
|
||||||
var routeOptions = new RouteOptions();
|
serviceCollection.AddOptions();
|
||||||
var accessor = new Mock<IOptions<RouteOptions>>();
|
|
||||||
accessor
|
|
||||||
.SetupGet(options => options.Value)
|
|
||||||
.Returns(routeOptions);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IOptions<RouteOptions>>(accessor.Object);
|
|
||||||
serviceCollection.AddRouting();
|
serviceCollection.AddRouting();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IInlineConstraintResolver>(
|
serviceCollection.AddSingleton<IInlineConstraintResolver>(
|
||||||
new DefaultInlineConstraintResolver(accessor.Object));
|
provider => new DefaultInlineConstraintResolver(provider.GetRequiredService<IOptions<RouteOptions>>()));
|
||||||
|
|
||||||
|
if (localizerFactory != null)
|
||||||
|
{
|
||||||
|
serviceCollection.AddSingleton<IStringLocalizerFactory>(localizerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
return serviceCollection;
|
return serviceCollection;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -126,4 +126,7 @@
|
||||||
<data name="DisplayAttribute_Name" xml:space="preserve">
|
<data name="DisplayAttribute_Name" xml:space="preserve">
|
||||||
<value>name from resources</value>
|
<value>name from resources</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RemoteAttribute_Error" xml:space="preserve">
|
||||||
|
<value>Error about '{0}' from resources.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
Loading…
Reference in New Issue