diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/CompareAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/CompareAttributeAdapter.cs index 0e8d9eca32..09d1cebe1f 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/CompareAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/CompareAttributeAdapter.cs @@ -5,13 +5,14 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public class CompareAttributeAdapter : DataAnnotationsClientModelValidator { - public CompareAttributeAdapter(CompareAttribute attribute) - : base(new CompareAttributeWrapper(attribute)) + public CompareAttributeAdapter(CompareAttribute attribute, IStringLocalizer stringLocalizer) + : base(new CompareAttributeWrapper(attribute), stringLocalizer) { if (attribute == null) { diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsClientModelValidatorOfTAttribute.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsClientModelValidatorOfTAttribute.cs index c456fb765f..e210d41ba7 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsClientModelValidatorOfTAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsClientModelValidatorOfTAttribute.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { @@ -14,13 +15,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation public abstract class DataAnnotationsClientModelValidator : IClientModelValidator where TAttribute : ValidationAttribute { + private readonly IStringLocalizer _stringLocalizer; /// /// Create a new instance of . /// /// The instance to validate. - public DataAnnotationsClientModelValidator(TAttribute attribute) + /// The . + public DataAnnotationsClientModelValidator(TAttribute attribute, IStringLocalizer stringLocalizer) { Attribute = attribute; + _stringLocalizer = stringLocalizer; } /// @@ -48,7 +52,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation throw new ArgumentNullException(nameof(modelMetadata)); } - return Attribute.FormatErrorMessage(modelMetadata.GetDisplayName()); + var displayName = modelMetadata.GetDisplayName(); + if (_stringLocalizer != null && + !string.IsNullOrEmpty(Attribute.ErrorMessage) && + string.IsNullOrEmpty(Attribute.ErrorMessageResourceName) && + Attribute.ErrorMessageResourceType == null) + { + return _stringLocalizer[displayName]; + } + + return Attribute.FormatErrorMessage(displayName); } } } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsClientModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsClientModelValidatorProvider.cs index 27bb911bfc..93c38586dc 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsClientModelValidatorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsClientModelValidatorProvider.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using Microsoft.Framework.Localization; +using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { @@ -18,11 +20,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation public class DataAnnotationsClientModelValidatorProvider : IClientModelValidatorProvider { // A factory for validators based on ValidationAttribute. - internal delegate IClientModelValidator - DataAnnotationsClientModelValidationFactory(ValidationAttribute attribute); + internal delegate IClientModelValidator DataAnnotationsClientModelValidationFactory( + ValidationAttribute attribute, + IStringLocalizer stringLocalizer); private readonly Dictionary _attributeFactories = BuildAttributeFactoriesDictionary(); + private readonly IOptions _options; + private readonly IStringLocalizerFactory _stringLocalizerFactory; + + /// + /// Create a new instance of . + /// + /// The . + /// The . + public DataAnnotationsClientModelValidatorProvider( + IOptions options, + IStringLocalizerFactory stringLocalizerFactory) + { + _options = options; + _stringLocalizerFactory = stringLocalizerFactory; + } internal Dictionary AttributeFactories { @@ -36,6 +54,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { throw new ArgumentNullException(nameof(context)); } + IStringLocalizer stringLocalizer = null; + if (_options.Value.DataAnnotationLocalizerProvider != null && _stringLocalizerFactory != null) + { + // This will pass first non-null type (either containerType or modelType) to delegate. + // Pass the root model type(container type) if it is non null, else pass the model type. + stringLocalizer = _options.Value.DataAnnotationLocalizerProvider( + context.ModelMetadata.ContainerType ?? context.ModelMetadata.ModelType, + _stringLocalizerFactory); + } var hasRequiredAttribute = false; @@ -46,14 +73,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation DataAnnotationsClientModelValidationFactory factory; if (_attributeFactories.TryGetValue(attribute.GetType(), out factory)) { - context.Validators.Add(factory(attribute)); + context.Validators.Add(factory(attribute, stringLocalizer)); } } if (!hasRequiredAttribute && context.ModelMetadata.IsRequired) { // Add a default '[Required]' validator for generating HTML if necessary. - context.Validators.Add(new RequiredAttributeAdapter(new RequiredAttribute())); + context.Validators.Add(new RequiredAttributeAdapter(new RequiredAttribute(), stringLocalizer)); } } @@ -63,47 +90,73 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { { typeof(RegularExpressionAttribute), - (attribute) => new RegularExpressionAttributeAdapter((RegularExpressionAttribute)attribute) + (attribute, stringLocalizer) => new RegularExpressionAttributeAdapter( + (RegularExpressionAttribute)attribute, + stringLocalizer) }, { typeof(MaxLengthAttribute), - (attribute) => new MaxLengthAttributeAdapter((MaxLengthAttribute)attribute) + (attribute, stringLocalizer) => new MaxLengthAttributeAdapter( + (MaxLengthAttribute)attribute, + stringLocalizer) }, { typeof(MinLengthAttribute), - (attribute) => new MinLengthAttributeAdapter((MinLengthAttribute)attribute) + (attribute, stringLocalizer) => new MinLengthAttributeAdapter( + (MinLengthAttribute)attribute, + stringLocalizer) }, { typeof(CompareAttribute), - (attribute) => new CompareAttributeAdapter((CompareAttribute)attribute) + (attribute, stringLocalizer) => new CompareAttributeAdapter( + (CompareAttribute)attribute, + stringLocalizer) }, { typeof(RequiredAttribute), - (attribute) => new RequiredAttributeAdapter((RequiredAttribute)attribute) + (attribute, stringLocalizer) => new RequiredAttributeAdapter( + (RequiredAttribute)attribute, + stringLocalizer) }, { typeof(RangeAttribute), - (attribute) => new RangeAttributeAdapter((RangeAttribute)attribute) + (attribute, stringLocalizer) => new RangeAttributeAdapter( + (RangeAttribute)attribute, + stringLocalizer) }, { typeof(StringLengthAttribute), - (attribute) => new StringLengthAttributeAdapter((StringLengthAttribute)attribute) + (attribute, stringLocalizer) => new StringLengthAttributeAdapter( + (StringLengthAttribute)attribute, + stringLocalizer) }, { typeof(CreditCardAttribute), - (attribute) => new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "creditcard") + (attribute, stringLocalizer) => new DataTypeAttributeAdapter( + (DataTypeAttribute)attribute, + "creditcard", + stringLocalizer) }, { typeof(EmailAddressAttribute), - (attribute) => new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "email") + (attribute, stringLocalizer) => new DataTypeAttributeAdapter( + (DataTypeAttribute)attribute, + "email", + stringLocalizer) }, { typeof(PhoneAttribute), - (attribute) => new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "phone") + (attribute, stringLocalizer) => new DataTypeAttributeAdapter( + (DataTypeAttribute)attribute, + "phone", + stringLocalizer) }, { typeof(UrlAttribute), - (attribute) => new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "url") + (attribute, stringLocalizer) => new DataTypeAttributeAdapter( + (DataTypeAttribute)attribute, + "url", + stringLocalizer) } }; } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidator.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidator.cs index 88ba55df68..6991dab020 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidator.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidator.cs @@ -5,12 +5,15 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public class DataAnnotationsModelValidator : IModelValidator { - public DataAnnotationsModelValidator(ValidationAttribute attribute) + private IStringLocalizer _stringLocalizer; + + public DataAnnotationsModelValidator(ValidationAttribute attribute, IStringLocalizer stringLocalizer) { if (attribute == null) { @@ -18,9 +21,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation } Attribute = attribute; + _stringLocalizer = stringLocalizer; } - public ValidationAttribute Attribute { get; private set; } + public ValidationAttribute Attribute { get; } public bool IsRequired { @@ -59,7 +63,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation errorMemberName = null; } - var validationResult = new ModelValidationResult(errorMemberName, result.ErrorMessage); + string errorMessage = null; + if (_stringLocalizer != null && + !string.IsNullOrEmpty(Attribute.ErrorMessage) && + string.IsNullOrEmpty(Attribute.ErrorMessageResourceName) && + Attribute.ErrorMessageResourceType == null) + { + errorMessage = _stringLocalizer[Attribute.ErrorMessage]; + } + + var validationResult = new ModelValidationResult(errorMemberName, errorMessage ?? result.ErrorMessage); return new ModelValidationResult[] { validationResult }; } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs index 8e426963a6..097b4c2281 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs @@ -2,10 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; +using Microsoft.Framework.Localization; +using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { @@ -16,11 +17,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation /// public class DataAnnotationsModelValidatorProvider : IModelValidatorProvider { + private readonly IOptions _options; + private readonly IStringLocalizerFactory _stringLocalizerFactory; + + /// + /// Create a new instance of . + /// + /// The . + /// The . + public DataAnnotationsModelValidatorProvider( + IOptions options, + IStringLocalizerFactory stringLocalizerFactory) + { + _options = options; + _stringLocalizerFactory = stringLocalizerFactory; + } + public void GetValidators(ModelValidatorProviderContext context) { + IStringLocalizer stringLocalizer = null; + if (_options.Value.DataAnnotationLocalizerProvider != null && _stringLocalizerFactory != null) + { + stringLocalizer = _options.Value.DataAnnotationLocalizerProvider( + context.ModelMetadata.ContainerType ?? context.ModelMetadata.ModelType, + _stringLocalizerFactory); + } + foreach (var attribute in context.ValidatorMetadata.OfType()) { - context.Validators.Add(new DataAnnotationsModelValidator(attribute)); + context.Validators.Add(new DataAnnotationsModelValidator(attribute, stringLocalizer)); } // Produce a validator if the type supports IValidatableObject diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataTypeAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataTypeAttributeAdapter.cs index f815135b29..e82493ac27 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataTypeAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataTypeAttributeAdapter.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Microsoft.AspNet.Mvc.DataAnnotations; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { @@ -14,10 +15,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation /// public class DataTypeAttributeAdapter : DataAnnotationsClientModelValidator { - public DataTypeAttributeAdapter( - DataTypeAttribute attribute, - string ruleName) - : base(attribute) + public DataTypeAttributeAdapter(DataTypeAttribute attribute, string ruleName, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) { if (string.IsNullOrEmpty(ruleName)) { diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.cs new file mode 100644 index 0000000000..6da0c9bc17 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Mvc.DataAnnotations.Internal; +using Microsoft.AspNet.Mvc.ModelBinding.Validation; + +namespace Microsoft.Framework.DependencyInjection +{ + /// + /// Extension methods for configuring MVC data annotations localization. + /// + public static class MvcDataAnnotationsMvcBuilderExtensions + { + /// + /// Adds MVC data annotations localization to the application. + /// + /// The . + /// The . + public static IMvcBuilder AddDataAnnotationsLocalization(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddDataAnnotationsLocalization(builder, setupAction: null); + } + + /// + /// Adds MVC data annotations localization to the application. + /// + /// The . + /// The action to configure . + /// + /// The . + public static IMvcBuilder AddDataAnnotationsLocalization( + this IMvcBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + DataAnnotationsLocalizationServices.AddDataAnnotationsLocalizationServices( + builder.Services, + setupAction); + + return builder; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs index 794c4b3947..143c9e027f 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs @@ -4,13 +4,22 @@ using System; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.DataAnnotations.Internal; +using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.Framework.DependencyInjection.Extensions; using Microsoft.Framework.OptionsModel; namespace Microsoft.Framework.DependencyInjection { + /// + /// Extensions for configuring MVC data annotations using an . + /// public static class MvcDataAnnotationsMvcCoreBuilderExtensions { + /// + /// Registers MVC data annotations. + /// + /// The . + /// The . public static IMvcCoreBuilder AddDataAnnotations(this IMvcCoreBuilder builder) { if (builder == null) @@ -22,11 +31,39 @@ namespace Microsoft.Framework.DependencyInjection return builder; } + /// + /// Registers an action to configure for MVC data + /// annotations localization. + /// + /// The . + /// An . + /// The . + public static IMvcCoreBuilder AddDataAnnotationsLocalization( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddDataAnnotationsLocalizationServices(builder.Services, setupAction); + return builder; + } + // Internal for testing. internal static void AddDataAnnotationsServices(IServiceCollection services) { services.TryAddEnumerable( ServiceDescriptor.Transient, MvcDataAnnotationsMvcOptionsSetup>()); } + + // Internal for testing. + internal static void AddDataAnnotationsLocalizationServices( + IServiceCollection services, + Action setupAction) + { + DataAnnotationsLocalizationServices.AddDataAnnotationsLocalizationServices(services, setupAction); + } } } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs new file mode 100644 index 0000000000..22c839ec6a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Mvc.ModelBinding.Validation; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Extensions; +using Microsoft.Framework.OptionsModel; + +namespace Microsoft.AspNet.Mvc.DataAnnotations.Internal +{ + public static class DataAnnotationsLocalizationServices + { + public static void AddDataAnnotationsLocalizationServices( + IServiceCollection services, + Action setupAction) + { + services.AddLocalization(); + + if (setupAction != null) + { + services.Configure(setupAction); + } + else + { + services.TryAddEnumerable( + ServiceDescriptor.Transient + , + MvcDataAnnotationsLocalizationOptionsSetup>()); + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.cs new file mode 100644 index 0000000000..bea83738bc --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Mvc.ModelBinding.Validation; +using Microsoft.Framework.OptionsModel; + +namespace Microsoft.AspNet.Mvc.DataAnnotations.Internal +{ + /// + /// Sets up default options for . + /// + public class MvcDataAnnotationsLocalizationOptionsSetup : ConfigureOptions + { + public MvcDataAnnotationsLocalizationOptionsSetup() + : base(ConfigureMvc) + { + } + + public static void ConfigureMvc(MvcDataAnnotationsLocalizationOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) => + stringLocalizerFactory.Create(modelType); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs index 473cba0b7b..4c12dd424e 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs @@ -1,8 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.ModelBinding.Validation; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Localization; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.DataAnnotations.Internal @@ -12,15 +15,23 @@ namespace Microsoft.AspNet.Mvc.DataAnnotations.Internal /// public class MvcDataAnnotationsMvcOptionsSetup : ConfigureOptions { - public MvcDataAnnotationsMvcOptionsSetup() - : base(ConfigureMvc) + public MvcDataAnnotationsMvcOptionsSetup(IServiceProvider serviceProvider) + : base(options => ConfigureMvc(options, serviceProvider)) { } - public static void ConfigureMvc(MvcOptions options) + public static void ConfigureMvc(MvcOptions options, IServiceProvider serviceProvider) { + var dataAnnotationLocalizationOptions = + serviceProvider.GetRequiredService>(); + + // This service will be registered only if AddDataAnnotationsLocalization() is added to service collection. + var stringLocalizerFactory = serviceProvider.GetService(); + options.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider()); - options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider()); + options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider( + dataAnnotationLocalizationOptions, + stringLocalizerFactory)); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/MaxLengthAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/MaxLengthAttributeAdapter.cs index f0027a3135..20ab4908d1 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/MaxLengthAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/MaxLengthAttributeAdapter.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public class MaxLengthAttributeAdapter : DataAnnotationsClientModelValidator { - public MaxLengthAttributeAdapter(MaxLengthAttribute attribute) - : base(attribute) + public MaxLengthAttributeAdapter(MaxLengthAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) { } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/MinLengthAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/MinLengthAttributeAdapter.cs index 718b67fa5a..551fc5e855 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/MinLengthAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/MinLengthAttributeAdapter.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public class MinLengthAttributeAdapter : DataAnnotationsClientModelValidator { - public MinLengthAttributeAdapter(MinLengthAttribute attribute) - : base(attribute) + public MinLengthAttributeAdapter(MinLengthAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) { } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs new file mode 100644 index 0000000000..965e4cca43 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Framework.Localization; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + /// + /// Provides programmatic configuration for DataAnnotations localization in the MVC framework. + /// + public class MvcDataAnnotationsLocalizationOptions + { + /// + /// The delegate to invoke for creating . + /// + public Func DataAnnotationLocalizerProvider; + } +} diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/RangeAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/RangeAttributeAdapter.cs index cfe32ce780..01e808f16d 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/RangeAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/RangeAttributeAdapter.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public class RangeAttributeAdapter : DataAnnotationsClientModelValidator { - public RangeAttributeAdapter(RangeAttribute attribute) - : base(attribute) + public RangeAttributeAdapter(RangeAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) { } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/RegularExpressionAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/RegularExpressionAttributeAdapter.cs index 03d130d2dd..60a709ca8c 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/RegularExpressionAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/RegularExpressionAttributeAdapter.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public class RegularExpressionAttributeAdapter : DataAnnotationsClientModelValidator { - public RegularExpressionAttributeAdapter(RegularExpressionAttribute attribute) - : base(attribute) + public RegularExpressionAttributeAdapter(RegularExpressionAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) { } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/RequiredAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/RequiredAttributeAdapter.cs index 00aace75cb..525daeec60 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/RequiredAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/RequiredAttributeAdapter.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public class RequiredAttributeAdapter : DataAnnotationsClientModelValidator { - public RequiredAttributeAdapter(RequiredAttribute attribute) - : base(attribute) + public RequiredAttributeAdapter(RequiredAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) { } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/StringLengthAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/StringLengthAttributeAdapter.cs index 319aac5739..7ac1e00ebf 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/StringLengthAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/StringLengthAttributeAdapter.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.Framework.Localization; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public class StringLengthAttributeAdapter : DataAnnotationsClientModelValidator { - public StringLengthAttributeAdapter(StringLengthAttribute attribute) - : base(attribute) + public StringLengthAttributeAdapter(StringLengthAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) { } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/project.json b/src/Microsoft.AspNet.Mvc.DataAnnotations/project.json index f2d5c486df..20cb49d2bd 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/project.json +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/project.json @@ -11,7 +11,8 @@ "dependencies": { "Microsoft.AspNet.Mvc.Core": "6.0.0-*", "Microsoft.Framework.ClosedGenericMatcher.Sources": { "version": "1.0.0-*", "type": "build" }, - "Microsoft.Framework.CopyOnWriteDictionary.Sources": { "version": "1.0.0-*", "type": "build" } + "Microsoft.Framework.CopyOnWriteDictionary.Sources": { "version": "1.0.0-*", "type": "build" }, + "Microsoft.Framework.Localization": "1.0.0-*" }, "frameworks": { diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Internal/MvcViewOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Internal/MvcViewOptionsSetup.cs index 559017105d..9d99ee6a7e 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Internal/MvcViewOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Internal/MvcViewOptionsSetup.cs @@ -1,7 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Microsoft.AspNet.Mvc.ModelBinding.Validation; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Localization; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal @@ -14,16 +17,24 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal /// /// Initializes a new instance of . /// - public MvcViewOptionsSetup() - : base(ConfigureMvc) + public MvcViewOptionsSetup(IServiceProvider serviceProvider) + : base(options => ConfigureMvc(options, serviceProvider)) { } - public static void ConfigureMvc(MvcViewOptions options) + public static void ConfigureMvc( + MvcViewOptions options, + IServiceProvider serviceProvider) { + var dataAnnotationsLocalizationOptions = + serviceProvider.GetRequiredService>(); + var stringLocalizerFactory = serviceProvider.GetService(); + // Set up client validators options.ClientModelValidatorProviders.Add(new DefaultClientModelValidatorProvider()); - options.ClientModelValidatorProviders.Add(new DataAnnotationsClientModelValidatorProvider()); + options.ClientModelValidatorProviders.Add(new DataAnnotationsClientModelValidatorProvider( + dataAnnotationsLocalizationOptions, + stringLocalizerFactory)); options.ClientModelValidatorProviders.Add(new NumericClientModelValidatorProvider()); } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs index 46e9fc469d..20640d2503 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs @@ -242,7 +242,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { ModelBinder = innerBinder ?? CreateIntBinder(), MetadataProvider = metataProvider, - ValidatorProvider = new DataAnnotationsModelValidatorProvider() + ValidatorProvider = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null) } }; return bindingContext; diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs index 750654198a..e095ffccca 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs @@ -72,7 +72,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new MutableObjectModelBinder() }; - var validator = new DataAnnotationsModelValidatorProvider(); + var validator = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var model = new MyModel(); var modelStateDictionary = new ModelStateDictionary(); var values = new Dictionary @@ -111,7 +113,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new MutableObjectModelBinder() }; - var validator = new DataAnnotationsModelValidatorProvider(); + var validator = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var model = new MyModel { MyProperty = "Old-Value" }; var modelStateDictionary = new ModelStateDictionary(); var values = new Dictionary @@ -185,7 +189,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new MutableObjectModelBinder() }; - var validator = new DataAnnotationsModelValidatorProvider(); + var validator = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var model = new MyModel { MyProperty = "Old-Value", IncludedProperty = "Old-IncludedPropertyValue", @@ -274,7 +280,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new MutableObjectModelBinder() }; - var validator = new DataAnnotationsModelValidatorProvider(); + var validator = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var model = new MyModel { MyProperty = "Old-Value", @@ -326,7 +334,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new MutableObjectModelBinder() }; - var validator = new DataAnnotationsModelValidatorProvider(); + var validator = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var model = new MyModel { MyProperty = "Old-Value", @@ -532,7 +542,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new MutableObjectModelBinder() }; - var validator = new DataAnnotationsModelValidatorProvider(); + var validator = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var model = new MyModel { MyProperty = "Old-Value", @@ -623,7 +635,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new MutableObjectModelBinder() }; - var validator = new DataAnnotationsModelValidatorProvider(); + var validator = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var model = new MyModel { MyProperty = "Old-Value" }; var modelStateDictionary = new ModelStateDictionary(); var values = new Dictionary diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/CompareAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/CompareAttributeAdapterTest.cs index 83c2172c0a..41f8aa720d 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/CompareAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/CompareAttributeAdapterTest.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var metadata = metadataProvider.GetMetadataForProperty(typeof(PropertyDisplayNameModel), "MyProperty"); var attribute = new CompareAttribute("OtherProperty"); - var adapter = new CompareAttributeAdapter(attribute); + var adapter = new CompareAttributeAdapter(attribute, stringLocalizer: null); var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); @@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, metadataProvider, requestServices); - var adapter = new CompareAttributeAdapter(attribute); + var adapter = new CompareAttributeAdapter(attribute, stringLocalizer: null); // Act var rules = adapter.GetClientValidationRules(context); @@ -76,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, metadataProvider, requestServices); - var adapter = new CompareAttributeAdapter(attribute); + var adapter = new CompareAttributeAdapter(attribute, stringLocalizer: null); // Act var rules = adapter.GetClientValidationRules(context); @@ -102,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, metadataProvider, requestServices); - var adapter = new CompareAttributeAdapter(attribute); + var adapter = new CompareAttributeAdapter(attribute, stringLocalizer: null); // Act var rules = adapter.GetClientValidationRules(context); diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsClientModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsClientModelValidatorProviderTest.cs index 10f2ede1f9..e2c30028ca 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsClientModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsClientModelValidatorProviderTest.cs @@ -17,7 +17,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation public void GetValidators_AddsRequiredAttribute_ForIsRequiredTrue() { // Arrange - var provider = new DataAnnotationsClientModelValidatorProvider(); + var provider = new DataAnnotationsClientModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var metadata = _metadataProvider.GetMetadataForProperty( typeof(DummyRequiredAttributeHelperClass), @@ -37,7 +39,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation public void GetValidators_DoesNotAddRequiredAttribute_ForIsRequiredFalse() { // Arrange - var provider = new DataAnnotationsClientModelValidatorProvider(); + var provider = new DataAnnotationsClientModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var metadata = _metadataProvider.GetMetadataForProperty( typeof(DummyRequiredAttributeHelperClass), @@ -56,7 +60,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation public void GetValidators_DoesNotAddExtraRequiredAttribute_IfAttributeIsSpecifiedExplicitly() { // Arrange - var provider = new DataAnnotationsClientModelValidatorProvider(); + var provider = new DataAnnotationsClientModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var metadata = _metadataProvider.GetMetadataForProperty( typeof(DummyRequiredAttributeHelperClass), @@ -122,11 +128,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation Type expectedAdapterType) { // Arrange - var adapters = new DataAnnotationsClientModelValidatorProvider().AttributeFactories; + var adapters = new DataAnnotationsClientModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null) + .AttributeFactories; var adapterFactory = adapters.Single(kvp => kvp.Key == attribute.GetType()).Value; // Act - var adapter = adapterFactory(attribute); + var adapter = adapterFactory(attribute, stringLocalizer: null); // Assert Assert.IsType(expectedAdapterType, adapter); @@ -150,11 +159,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation string expectedRuleName) { // Arrange - var adapters = new DataAnnotationsClientModelValidatorProvider().AttributeFactories; + var adapters = new DataAnnotationsClientModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null) + .AttributeFactories; var adapterFactory = adapters.Single(kvp => kvp.Key == attribute.GetType()).Value; // Act - var adapter = adapterFactory(attribute); + var adapter = adapterFactory(attribute, stringLocalizer: null); // Assert var dataTypeAdapter = Assert.IsType(adapter); @@ -165,7 +177,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation public void UnknownValidationAttribute_IsNotAddedAsValidator() { // Arrange - var provider = new DataAnnotationsClientModelValidatorProvider(); + var provider = new DataAnnotationsClientModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var metadata = _metadataProvider.GetMetadataForType(typeof(DummyClassWithDummyValidationAttribute)); var providerContext = new ClientValidatorProviderContext(metadata); diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs index fb0fc7ba8c..68b3b84c55 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs @@ -21,7 +21,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation public void GetValidators_ReturnsValidatorForIValidatableObject() { // Arrange - var provider = new DataAnnotationsModelValidatorProvider(); + var provider = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var mockValidatable = Mock.Of(); var metadata = _metadataProvider.GetMetadataForType(mockValidatable.GetType()); @@ -40,7 +42,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation public void UnknownValidationAttributeGetsDefaultAdapter() { // Arrange - var provider = new DataAnnotationsModelValidatorProvider(); + var provider = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var metadata = _metadataProvider.GetMetadataForType(typeof(DummyClassWithDummyValidationAttribute)); var providerContext = new ModelValidatorProviderContext(metadata); @@ -69,7 +73,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation public void IValidatableObjectGetsAValidator() { // Arrange - var provider = new DataAnnotationsModelValidatorProvider(); + var provider = new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); var mockValidatable = new Mock(); var metadata = _metadataProvider.GetMetadataForType(mockValidatable.Object.GetType()); diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorTest.cs index 2248930b80..b3aa9c04e1 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorTest.cs @@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; #endif using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Localization; #if DNX451 using Moq; using Moq.Protected; @@ -26,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var attribute = new RequiredAttribute(); // Act - var validator = new DataAnnotationsModelValidator(attribute); + var validator = new DataAnnotationsModelValidator(attribute, stringLocalizer : null); // Assert Assert.Same(attribute, validator.Attribute); @@ -67,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation }) .Returns(ValidationResult.Success) .Verifiable(); - var validator = new DataAnnotationsModelValidator(attribute.Object); + var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = CreateValidationContext(modelExplorer); // Act @@ -89,7 +90,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var attribute = new Mock { CallBase = true }; attribute.Setup(a => a.IsValid(modelExplorer.Model)).Returns(true); - var validator = new DataAnnotationsModelValidator(attribute.Object); + var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = CreateValidationContext(modelExplorer); // Act @@ -110,7 +111,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var attribute = new Mock { CallBase = true }; attribute.Setup(a => a.IsValid(modelExplorer.Model)).Returns(false); - var validator = new DataAnnotationsModelValidator(attribute.Object); + var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = CreateValidationContext(modelExplorer); // Act @@ -134,7 +135,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation attribute.Protected() .Setup("IsValid", ItExpr.IsAny(), ItExpr.IsAny()) .Returns(ValidationResult.Success); - var validator = new DataAnnotationsModelValidator(attribute.Object); + var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = CreateValidationContext(modelExplorer); // Act @@ -158,7 +159,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation attribute.Protected() .Setup("IsValid", ItExpr.IsAny(), ItExpr.IsAny()) .Returns(new ValidationResult(errorMessage, memberNames: null)); - var validator = new DataAnnotationsModelValidator(attribute.Object); + var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = CreateValidationContext(modelExplorer); @@ -184,7 +185,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation .Setup("IsValid", ItExpr.IsAny(), ItExpr.IsAny()) .Returns(new ValidationResult(errorMessage, new[] { "FirstName" })); - var validator = new DataAnnotationsModelValidator(attribute.Object); + var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = CreateValidationContext(modelExplorer); // Act @@ -207,7 +208,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation .Setup("IsValid", ItExpr.IsAny(), ItExpr.IsAny()) .Returns(new ValidationResult("Name error", new[] { "Name" })); - var validator = new DataAnnotationsModelValidator(attribute.Object); + var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer: null); var validationContext = CreateValidationContext(modelExplorer); // Act @@ -217,15 +218,46 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation ModelValidationResult validationResult = Assert.Single(results); Assert.Equal("Name", validationResult.MemberName); } + + [Fact] + public void ValidateWithIsValidFalse_StringLocalizerReturnsLocalizerErrorMessage() + { + // Arrange + var modelExplorer = _metadataProvider + .GetModelExplorerForType(typeof(string), "Hello") + .GetExplorerForProperty("Length"); + + var attribute = new Mock { CallBase = true }; + attribute.Setup(a => a.IsValid(modelExplorer.Model)).Returns(false); + + attribute.Object.ErrorMessage = "Length"; + + var localizedString = new LocalizedString("Length", "Longueur est invalide"); + var stringLocalizer = new Mock(); + stringLocalizer.Setup(s => s["Length"]).Returns(localizedString); + + var validator = new DataAnnotationsModelValidator(attribute.Object, stringLocalizer.Object); + var validationContext = CreateValidationContext(modelExplorer); + + // Act + var result = validator.Validate(validationContext); + + // Assert + var validationResult = result.Single(); + Assert.Equal("", validationResult.MemberName); + Assert.Equal("Longueur est invalide", validationResult.Message); + } #endif [Fact] public void IsRequiredTests() { // Arrange & Act & Assert - Assert.False(new DataAnnotationsModelValidator(new RangeAttribute(10, 20)).IsRequired); - Assert.True(new DataAnnotationsModelValidator(new RequiredAttribute()).IsRequired); - Assert.True(new DataAnnotationsModelValidator(new DerivedRequiredAttribute()).IsRequired); + Assert.False(new DataAnnotationsModelValidator(new RangeAttribute(10, 20), stringLocalizer: null) + .IsRequired); + Assert.True(new DataAnnotationsModelValidator(new RequiredAttribute(), stringLocalizer: null).IsRequired); + Assert.True(new DataAnnotationsModelValidator(new DerivedRequiredAttribute(), stringLocalizer: null) + .IsRequired); } private static ModelValidationContext CreateValidationContext(ModelExplorer modelExplorer) diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/MaxLengthAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/MaxLengthAttributeAdapterTest.cs index 8c41a9120a..85847ae878 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/MaxLengthAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/MaxLengthAttributeAdapterTest.cs @@ -4,6 +4,8 @@ using System.ComponentModel.DataAnnotations; using Microsoft.AspNet.Testing; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Localization; +using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation @@ -18,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new MaxLengthAttribute(10); - var adapter = new MaxLengthAttributeAdapter(attribute); + var adapter = new MaxLengthAttributeAdapter(attribute, stringLocalizer: null); var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, provider, requestServices); @@ -44,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), propertyName); var attribute = new MaxLengthAttribute(5) { ErrorMessage = message }; - var adapter = new MaxLengthAttributeAdapter(attribute); + var adapter = new MaxLengthAttributeAdapter(attribute, stringLocalizer: null); var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, provider, requestServices); @@ -59,5 +61,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation Assert.Equal(5, rule.ValidationParameters["max"]); Assert.Equal("Length must be at most 5", rule.ErrorMessage); } - } + +#if DNX451 + [Fact] + [ReplaceCulture] + public void ClientRulesWithMaxLengthAttribute_StringLocalizer_ReturnsLocalizedErrorString() + { + // Arrange + var provider = TestModelMetadataProvider.CreateDefaultProvider(); + var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); + var errorKey = metadata.GetDisplayName(); + var attribute = new MaxLengthAttribute(10); + attribute.ErrorMessage = errorKey; + + var localizedString = new LocalizedString(errorKey, "Longueur est invalide"); + var stringLocalizer = new Mock(); + stringLocalizer.Setup(s => s[errorKey]).Returns(localizedString); + + var adapter = new MaxLengthAttributeAdapter(attribute, stringLocalizer.Object); + var serviceCollection = new ServiceCollection(); + var requestServices = serviceCollection.BuildServiceProvider(); + var context = new ClientModelValidationContext(metadata, provider, requestServices); + + // Act + var rules = adapter.GetClientValidationRules(context); + + // Assert + var rule = Assert.Single(rules); + Assert.Equal("maxlength", rule.ValidationType); + Assert.Equal(1, rule.ValidationParameters.Count); + Assert.Equal(10, rule.ValidationParameters["max"]); + Assert.Equal("Longueur est invalide", rule.ErrorMessage); + } +#endif + } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/MinLengthAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/MinLengthAttributeAdapterTest.cs index cbc27a3775..6b97bda490 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/MinLengthAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/MinLengthAttributeAdapterTest.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new MinLengthAttribute(6); - var adapter = new MinLengthAttributeAdapter(attribute); + var adapter = new MinLengthAttributeAdapter(attribute, stringLocalizer: null); var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, provider, requestServices); @@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), propertyName); var attribute = new MinLengthAttribute(2) { ErrorMessage = message }; - var adapter = new MinLengthAttributeAdapter(attribute); + var adapter = new MinLengthAttributeAdapter(attribute, stringLocalizer: null); var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, provider, requestServices); diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/RangeAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/RangeAttributeAdapterTest.cs index faaadae16e..6ff2a4a0f2 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/RangeAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/RangeAttributeAdapterTest.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new RangeAttribute(typeof(decimal), "0", "100"); - var adapter = new RangeAttributeAdapter(attribute); + var adapter = new RangeAttributeAdapter(attribute, stringLocalizer: null); var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, provider, requestServices); diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/RequiredAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/RequiredAttributeAdapterTest.cs index 9edc169d45..40897088de 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/RequiredAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/RequiredAttributeAdapterTest.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new RequiredAttribute(); - var adapter = new RequiredAttributeAdapter(attribute); + var adapter = new RequiredAttributeAdapter(attribute, stringLocalizer: null); var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, provider, requestServices); diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/StringLengthAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/StringLengthAttributeAdapterTest.cs index 35f7d9e27d..9a3b706131 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/StringLengthAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/StringLengthAttributeAdapterTest.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new StringLengthAttribute(8); - var adapter = new StringLengthAttributeAdapter(attribute); + var adapter = new StringLengthAttributeAdapter(attribute, stringLocalizer: null); var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, provider, requestServices); @@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new StringLengthAttribute(10) { MinimumLength = 3 }; - var adapter = new StringLengthAttributeAdapter(attribute); + var adapter = new StringLengthAttributeAdapter(attribute, stringLocalizer: null); var serviceCollection = new ServiceCollection(); var requestServices = serviceCollection.BuildServiceProvider(); var context = new ClientModelValidationContext(metadata, provider, requestServices); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/LocalizationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/LocalizationTest.cs index 0d248be37f..06ff90dbc5 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/LocalizationTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/LocalizationTest.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Testing; using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNet.Mvc.FunctionalTests @@ -121,5 +122,32 @@ Salut John ! Vous êtes en 2015 an aujourd'hui est Thursday"; // Assert Assert.Equal(expected, body.Trim()); } + + [Fact] + public async Task Localization_InvalidModel_ValidationAttributes_ReturnsLocalizedErrorMessage() + { + // Arrange + var expected = +@"Nom non valide. Longueur minimale de nom est 4 +Nom du produit est invalide +
+
Nom non valide. Longueur minimale de nom est 4
+ +
+
Nom du produit est invalide
"; + + var cultureCookie = "c=fr|uic=fr"; + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Home/GetInvalidUser"); + request.Headers.Add( + "Cookie", + new CookieHeaderValue("ASPNET_CULTURE", cultureCookie).ToString()); + + // Act + var response = await Client.SendAsync(request); + var body = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true); + } } } diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs index ffef0a475b..eb66fc912a 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs @@ -1,10 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using Microsoft.AspNet.Mvc.DataAnnotations.Internal; using Microsoft.AspNet.Mvc.Formatters.Json.Internal; using Microsoft.AspNet.Mvc.Internal; +using Microsoft.AspNet.Mvc.TestCommon; +using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.IntegrationTests @@ -15,7 +16,10 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests { Value = new MvcOptions(); MvcCoreMvcOptionsSetup.ConfigureMvc(Value); - MvcDataAnnotationsMvcOptionsSetup.ConfigureMvc(Value); + var collection = new ServiceCollection().AddOptions(); + MvcDataAnnotationsMvcOptionsSetup.ConfigureMvc( + Value, + collection.BuildServiceProvider()); MvcJsonMvcOptionsSetup.ConfigureMvc(Value, SerializerSettingsProvider.CreateSerializerSettings()); } diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/TestClientModelValidatorProvider.cs b/test/Microsoft.AspNet.Mvc.TestCommon/TestClientModelValidatorProvider.cs index 856bc4bf30..18b56d683d 100644 --- a/test/Microsoft.AspNet.Mvc.TestCommon/TestClientModelValidatorProvider.cs +++ b/test/Microsoft.AspNet.Mvc.TestCommon/TestClientModelValidatorProvider.cs @@ -13,7 +13,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var providers = new IClientModelValidatorProvider[] { new DefaultClientModelValidatorProvider(), - new DataAnnotationsClientModelValidatorProvider(), + new DataAnnotationsClientModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null), }; return new TestClientModelValidatorProvider(providers); diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/TestModelValidatorProvider.cs b/test/Microsoft.AspNet.Mvc.TestCommon/TestModelValidatorProvider.cs index 27e33d8043..906a5f7f10 100644 --- a/test/Microsoft.AspNet.Mvc.TestCommon/TestModelValidatorProvider.cs +++ b/test/Microsoft.AspNet.Mvc.TestCommon/TestModelValidatorProvider.cs @@ -13,7 +13,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var providers = new IModelValidatorProvider[] { new DefaultModelValidatorProvider(), - new DataAnnotationsModelValidatorProvider(), + new DataAnnotationsModelValidatorProvider( + new TestOptionsManager(), + stringLocalizerFactory: null), }; return new TestModelValidatorProvider(providers); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerTest.cs index 1fbde6b0f0..f5d03a75e6 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerTest.cs @@ -1753,7 +1753,9 @@ namespace Microsoft.AspNet.Mvc.Test { ModelBinder = binder, ValueProvider = provider, - ValidatorProvider = new DataAnnotationsModelValidatorProvider() + ValidatorProvider = new DataAnnotationsModelValidatorProvider( + options: null, + stringLocalizerFactory: null) }; return new TestableController() diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs index 781267d4c9..ade8be814e 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs @@ -219,7 +219,13 @@ namespace Microsoft.AspNet.Mvc.Rendering { options.HtmlHelperOptions.IdAttributeDotReplacement = idAttributeDotReplacement; } - options.ClientModelValidatorProviders.Add(new DataAnnotationsClientModelValidatorProvider()); + var localizationOptionsAccesor = new Mock>(); + + localizationOptionsAccesor.SetupGet(o => o.Value).Returns(new MvcDataAnnotationsLocalizationOptions()); + + options.ClientModelValidatorProviders.Add(new DataAnnotationsClientModelValidatorProvider( + localizationOptionsAccesor.Object, + stringLocalizerFactory: null)); var optionsAccessor = new Mock>(); optionsAccessor .SetupGet(o => o.Value) @@ -235,6 +241,9 @@ namespace Microsoft.AspNet.Mvc.Rendering serviceProvider .Setup(s => s.GetService(typeof(IViewComponentHelper))) .Returns(new Mock().Object); + serviceProvider + .Setup(s => s.GetService(typeof(IViewComponentHelper))) + .Returns(new Mock().Object); httpContext.RequestServices = serviceProvider.Object; if (htmlGenerator == null) diff --git a/test/WebSites/LocalizationWebSite/Controllers/HomeController.cs b/test/WebSites/LocalizationWebSite/Controllers/HomeController.cs index fdc25a2171..bcb8724459 100644 --- a/test/WebSites/LocalizationWebSite/Controllers/HomeController.cs +++ b/test/WebSites/LocalizationWebSite/Controllers/HomeController.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using LocalizationWebSite.Models; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Localization; @@ -25,5 +26,17 @@ namespace LocalizationWebSite.Controllers ViewData["Message"] = _localizer["Learn More"]; return View(); } + + public IActionResult GetInvalidUser() + { + var user = new User + { + Name = "A", + Product = new Product() + }; + + TryValidateModel(user); + return View(user); + } } } diff --git a/test/WebSites/LocalizationWebSite/Models/User.cs b/test/WebSites/LocalizationWebSite/Models/User.cs new file mode 100644 index 0000000000..cf4daa9b55 --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Models/User.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.ComponentModel.DataAnnotations; + +namespace LocalizationWebSite.Models +{ + public class User + { + [MinLength(4, ErrorMessage = "Name")] + public string Name { get; set; } + + public Product Product { get; set; } + } + + public class Product + { + [Required(ErrorMessage = "ProductName")] + public string ProductName { get; set; } + } +} diff --git a/test/WebSites/LocalizationWebSite/Resources/Product.fr.resx b/test/WebSites/LocalizationWebSite/Resources/Product.fr.resx new file mode 100644 index 0000000000..0046bc487a --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Resources/Product.fr.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nom du produit est invalide + + \ No newline at end of file diff --git a/test/WebSites/LocalizationWebSite/Resources/User.fr.resx b/test/WebSites/LocalizationWebSite/Resources/User.fr.resx new file mode 100644 index 0000000000..01c1eacfd4 --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Resources/User.fr.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nom non valide. Longueur minimale de nom est 4 + + \ No newline at end of file diff --git a/test/WebSites/LocalizationWebSite/Startup.cs b/test/WebSites/LocalizationWebSite/Startup.cs index 0ae5506883..70b84461d3 100644 --- a/test/WebSites/LocalizationWebSite/Startup.cs +++ b/test/WebSites/LocalizationWebSite/Startup.cs @@ -10,7 +10,10 @@ namespace LocalizationWebSite { public void ConfigureServices(IServiceCollection services) { - services.AddMvc().AddViewLocalization(options => options.ResourcesPath = "Resources"); + services + .AddMvc() + .AddViewLocalization(options => options.ResourcesPath = "Resources") + .AddDataAnnotationsLocalization(); } public void Configure(IApplicationBuilder app) @@ -18,7 +21,7 @@ namespace LocalizationWebSite app.UseCultureReplacer(); app.UseRequestLocalization(); - + app.UseMvcWithDefaultRoute(); } } diff --git a/test/WebSites/LocalizationWebSite/Views/Home/GetInvalidUser.cshtml b/test/WebSites/LocalizationWebSite/Views/Home/GetInvalidUser.cshtml new file mode 100644 index 0000000000..08fb71fc8a --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Views/Home/GetInvalidUser.cshtml @@ -0,0 +1,8 @@ +@model LocalizationWebSite.Models.User + +@Html.ValidationMessage("Name") +@Html.ValidationMessage("Product.ProductName") +@Html.EditorForModel() +@Html.EditorFor(model => model.Product) + +