From 3cbcf66b194bf861a295e9efeab125214bfb4135 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 16 Sep 2011 15:23:35 -0700 Subject: [PATCH] #17754: Localizing validation attributes Work Items: 17754 --HG-- branch : 1.x --- .../CommentValidationAttributes.cs | 39 ------------ .../Orchard.Comments/Orchard.Comments.csproj | 1 - .../ViewModels/CommentsCreateViewModel.cs | 13 ++-- src/Orchard/Environment/OrchardStarter.cs | 5 ++ .../LocalizedModelValidatorProvider.cs | 63 +++++++++++++++++++ .../LocalizedRangeAttribute.cs | 24 +++++++ .../LocalizedRequiredAttribute.cs | 24 +++++++ .../LocalizedReularExpressionAttribute.cs | 23 +++++++ .../LocalizedStringMaxLengthAttribute.cs | 28 +++++++++ src/Orchard/Orchard.Framework.csproj | 5 ++ 10 files changed, 179 insertions(+), 46 deletions(-) delete mode 100644 src/Orchard.Web/Modules/Orchard.Comments/Annotations/CommentValidationAttributes.cs create mode 100644 src/Orchard/Mvc/DataAnnotations/LocalizedModelValidatorProvider.cs create mode 100644 src/Orchard/Mvc/DataAnnotations/LocalizedRangeAttribute.cs create mode 100644 src/Orchard/Mvc/DataAnnotations/LocalizedRequiredAttribute.cs create mode 100644 src/Orchard/Mvc/DataAnnotations/LocalizedReularExpressionAttribute.cs create mode 100644 src/Orchard/Mvc/DataAnnotations/LocalizedStringMaxLengthAttribute.cs diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Annotations/CommentValidationAttributes.cs b/src/Orchard.Web/Modules/Orchard.Comments/Annotations/CommentValidationAttributes.cs deleted file mode 100644 index 551afb931..000000000 --- a/src/Orchard.Web/Modules/Orchard.Comments/Annotations/CommentValidationAttributes.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Orchard.Localization; - -namespace Orchard.Comments.Annotations { - public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute { - public RequiredAttribute() { - T = NullLocalizer.Instance; - } - - public Localizer T { get; set; } - - public override string FormatErrorMessage(string name) { - return T("You must provide a {0} in order to comment.", name).Text; - } - } - - public class CommentRequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute { - public CommentRequiredAttribute() { - T = NullLocalizer.Instance; - } - - public Localizer T { get; set; } - - public override string FormatErrorMessage(string name) { - return T("You must provide a Comment.", name).Text; - } - } - - public class RegularExpressionAttribute : System.ComponentModel.DataAnnotations.RegularExpressionAttribute { - public RegularExpressionAttribute(string pattern) : base(pattern) { - T = NullLocalizer.Instance; - } - - public Localizer T { get; set; } - - public override string FormatErrorMessage(string name) { - return T("The {0} is not valid.", name).Text; - } - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj b/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj index e7c1147d7..4df3b95c1 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj +++ b/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj @@ -67,7 +67,6 @@ - diff --git a/src/Orchard.Web/Modules/Orchard.Comments/ViewModels/CommentsCreateViewModel.cs b/src/Orchard.Web/Modules/Orchard.Comments/ViewModels/CommentsCreateViewModel.cs index 558d85e4c..94408a78a 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/ViewModels/CommentsCreateViewModel.cs +++ b/src/Orchard.Web/Modules/Orchard.Comments/ViewModels/CommentsCreateViewModel.cs @@ -1,21 +1,22 @@ -using System.ComponentModel.DataAnnotations; -using Orchard.Comments.Annotations; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace Orchard.Comments.ViewModels { public class CommentsCreateViewModel { - [Annotations.Required] + [Required] [StringLength(255)] public string Name { get; set; } - [Annotations.RegularExpression(@"^[^@\s]+@[^@\s]+$")] + [RegularExpression(@"^[^@\s]+@[^@\s]+$")] [StringLength(255)] public string Email { get; set; } [StringLength(245)] - [Annotations.RegularExpression(@"^(http(s)?://)?([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}[\S]+$")] + [DisplayName("Site")] + [RegularExpression(@"^(http(s)?://)?([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}[\S]+$")] public string SiteName { get; set; } - [CommentRequired] + [Required, DisplayName("Comment")] public string CommentText { get; set; } public int CommentedOn { get; set; } diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 66654d908..c781ab137 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -23,6 +23,7 @@ using Orchard.FileSystems.VirtualPath; using Orchard.FileSystems.WebSite; using Orchard.Logging; using Orchard.Mvc; +using Orchard.Mvc.DataAnnotations; using Orchard.Mvc.ViewEngines.Razor; using Orchard.Mvc.ViewEngines.ThemeAwareness; using Orchard.Services; @@ -143,6 +144,10 @@ namespace Orchard.Environment { //MvcServiceLocator.SetCurrent(hostContainer); OrchardHostContainerRegistry.RegisterHostContainer(hostContainer); + // Register localized data annotations + ModelValidatorProviders.Providers.Clear(); + ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider()); + return container; } diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedModelValidatorProvider.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedModelValidatorProvider.cs new file mode 100644 index 000000000..5975da82e --- /dev/null +++ b/src/Orchard/Mvc/DataAnnotations/LocalizedModelValidatorProvider.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; +using Orchard.Localization; + +namespace Orchard.Mvc.DataAnnotations { + public class LocalizedModelValidatorProvider : DataAnnotationsModelValidatorProvider { + private static readonly Dictionary> _validationAttributes; + + static LocalizedModelValidatorProvider() { + _validationAttributes = new Dictionary> { + { typeof(RequiredAttribute), (attribute, t) => new LocalizedRequiredAttribute((RequiredAttribute)attribute, t)}, + { typeof(RangeAttribute), (attribute, t) => new LocalizedRangeAttribute((RangeAttribute)attribute, t)}, + { typeof(StringLengthAttribute), (attribute, t) => new LocalizedStringLengthAttribute((StringLengthAttribute)attribute, t)}, + { typeof(RegularExpressionAttribute), (attribute, t) => new LocalizedRegularExpressionAttribute((RegularExpressionAttribute)attribute, t)} + }; + } + + protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes) { + var localizedAttributes = new List(); + + foreach ( var attribute in attributes ) { + Func localizedAttribute; + + // overriden messages have their localization in the scope of the class they are applied to + var tContainer = LocalizationUtilities.Resolve(context, metadata.ContainerType.FullName); + + // default translations use the attribute's scope, e.g., System.ComponentModel.DataAnnotations.RequiredAttribute + var tProvider = LocalizationUtilities.Resolve(context, attribute.GetType().FullName); + + var validationAttribute = attribute as ValidationAttribute; + + // substitute the attribute to its localized version if available + if ( _validationAttributes.TryGetValue(attribute.GetType(), out localizedAttribute) ) { + localizedAttributes.Add(localizedAttribute((ValidationAttribute)attribute, tProvider)); + } + else { + + // try to inject the localizer if it's an unkown validation attribute + if ( validationAttribute != null ) { + + var propertyInfo = validationAttribute.GetType().GetProperty("T", typeof(Localizer)); + if ( propertyInfo != null ) { + propertyInfo.SetValue(attribute, tProvider, null); + } + } + + if ( attribute is DisplayNameAttribute ) { + metadata.DisplayName = tContainer(metadata.DisplayName).Text; + } + + localizedAttributes.Add(attribute); + } + } + + var result = base.GetValidators(metadata, context, localizedAttributes); + + return result; + } + } +} \ No newline at end of file diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedRangeAttribute.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedRangeAttribute.cs new file mode 100644 index 000000000..573b58bfe --- /dev/null +++ b/src/Orchard/Mvc/DataAnnotations/LocalizedRangeAttribute.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; +using Orchard.Localization; + +namespace Orchard.Mvc.DataAnnotations { + public class LocalizedRangeAttribute : RangeAttribute { + public LocalizedRangeAttribute(RangeAttribute attribute, Localizer t) + : base(attribute.OperandType, new FormatterConverter().ToString(attribute.Minimum), new FormatterConverter().ToString(attribute.Maximum)) { + if ( !String.IsNullOrEmpty(attribute.ErrorMessage) ) + ErrorMessage = attribute.ErrorMessage; + + T = t; + } + + public Localizer T { get; set; } + + public override string FormatErrorMessage(string name) { + return String.IsNullOrEmpty(ErrorMessage) + ? T("The field {0} must be between {1} and {2}.", name, Minimum, Maximum).Text + : T(ErrorMessage, name, Minimum, Maximum).Text; + } + } +} diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedRequiredAttribute.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedRequiredAttribute.cs new file mode 100644 index 000000000..72d3258b3 --- /dev/null +++ b/src/Orchard/Mvc/DataAnnotations/LocalizedRequiredAttribute.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Orchard.Localization; + +namespace Orchard.Mvc.DataAnnotations { + public class LocalizedRequiredAttribute : RequiredAttribute { + public LocalizedRequiredAttribute(RequiredAttribute attribute, Localizer t) { + AllowEmptyStrings = attribute.AllowEmptyStrings; + + if ( !String.IsNullOrEmpty(attribute.ErrorMessage) ) + ErrorMessage = attribute.ErrorMessage; + + T = t; + } + + public Localizer T { get; set; } + + public override string FormatErrorMessage(string name) { + return String.IsNullOrEmpty(ErrorMessage) + ? T("The field {0} is required.", name).Text + : T(ErrorMessage, name).Text; + } + } +} diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedReularExpressionAttribute.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedReularExpressionAttribute.cs new file mode 100644 index 000000000..4ff861822 --- /dev/null +++ b/src/Orchard/Mvc/DataAnnotations/LocalizedReularExpressionAttribute.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Orchard.Localization; + +namespace Orchard.Mvc.DataAnnotations { + public class LocalizedRegularExpressionAttribute : RegularExpressionAttribute { + public LocalizedRegularExpressionAttribute(RegularExpressionAttribute attribute, Localizer t) + : base(attribute.Pattern) { + if ( !String.IsNullOrEmpty(attribute.ErrorMessage) ) + ErrorMessage = attribute.ErrorMessage; + + T = t; + } + + public Localizer T { get; set; } + + public override string FormatErrorMessage(string name) { + return String.IsNullOrEmpty(ErrorMessage) + ? T("The field {0} must match the regular expression '{1}'.", name, Pattern).Text + : T(ErrorMessage, name, Pattern).Text; + } + } +} \ No newline at end of file diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedStringMaxLengthAttribute.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedStringMaxLengthAttribute.cs new file mode 100644 index 000000000..6a5bfbbe1 --- /dev/null +++ b/src/Orchard/Mvc/DataAnnotations/LocalizedStringMaxLengthAttribute.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Orchard.Localization; + +namespace Orchard.Mvc.DataAnnotations { + public class LocalizedStringLengthAttribute : StringLengthAttribute { + public LocalizedStringLengthAttribute(StringLengthAttribute attribute, Localizer t) + : base(attribute.MaximumLength) { + if ( !String.IsNullOrEmpty(attribute.ErrorMessage) ) + ErrorMessage = attribute.ErrorMessage; + + MinimumLength = attribute.MinimumLength; + + T = t; + } + + public Localizer T { get; set; } + + public override string FormatErrorMessage(string name) { + if ( !String.IsNullOrEmpty(ErrorMessage) ) + return T(ErrorMessage, name, MaximumLength, MinimumLength).Text; + + return MinimumLength > 0 + ? T("The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.", name, MaximumLength, MinimumLength).Text + : T("The field {0} must be a string with a maximum length of {1}.", name, MaximumLength, MinimumLength).Text; + } + } +} diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index f33f28740..e96f77bda 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -230,6 +230,11 @@ + + + + +