From 6f946fa5c184c3ddfd8281e302a7aa9bb0c95865 Mon Sep 17 00:00:00 2001 From: Sergey Date: Thu, 12 Mar 2020 10:12:17 +0300 Subject: [PATCH 1/3] Add TimeSpan comparison support --- .../Attributes/CompareToAttribute.cs | 53 +++++++++++++++++++ .../Extensions/ObjectExtensions.cs | 5 ++ 2 files changed, 58 insertions(+) diff --git a/src/AspNetCore.CustomValidation/Attributes/CompareToAttribute.cs b/src/AspNetCore.CustomValidation/Attributes/CompareToAttribute.cs index 0ae79d7..8d70efb 100644 --- a/src/AspNetCore.CustomValidation/Attributes/CompareToAttribute.cs +++ b/src/AspNetCore.CustomValidation/Attributes/CompareToAttribute.cs @@ -88,6 +88,11 @@ protected override ValidationResult IsValid(object value, ValidationContext vali var validationResult = TriggerValueComparison(); return validationResult; } + else if (value is TimeSpan && comparePropertyValue is TimeSpan) + { + var validationResult = TriggerValueComparison(); + return validationResult; + } else { throw new ArgumentException($"The type of {validationContext.MemberName} is not comparable to type of {ComparePropertyName}"); @@ -120,6 +125,14 @@ ValidationResult TriggerValueComparison() } } + if (value is TimeSpan timeSpanValue) + { + if (timeSpanValue != (TimeSpan)comparePropertyValue) + { + return new ValidationResult(errorMessage); + } + } + if (value is string) { if (value.ToString() != comparePropertyValue.ToString()) @@ -150,6 +163,14 @@ ValidationResult TriggerValueComparison() } } + if (value is TimeSpan timeSpanValue) + { + if (timeSpanValue == (TimeSpan)comparePropertyValue) + { + return new ValidationResult(errorMessage); + } + } + if (value is string) { if (value.ToString() == comparePropertyValue.ToString()) @@ -180,6 +201,14 @@ ValidationResult TriggerValueComparison() } } + if (value.IsTimeSpan()) + { + if ((TimeSpan)value <= (TimeSpan)comparePropertyValue) + { + return new ValidationResult(errorMessage); + } + } + if (value is string) { if (value.ToString().Length <= comparePropertyValue.ToString().Length) @@ -209,6 +238,14 @@ ValidationResult TriggerValueComparison() } } + if (value.IsTimeSpan()) + { + if ((TimeSpan)value < (TimeSpan)comparePropertyValue) + { + return new ValidationResult(errorMessage); + } + } + if (value is string) { if (value.ToString().Length < comparePropertyValue.ToString().Length) @@ -238,6 +275,14 @@ ValidationResult TriggerValueComparison() } } + if (value.IsTimeSpan()) + { + if ((TimeSpan)value >= (TimeSpan)comparePropertyValue) + { + return new ValidationResult(errorMessage); + } + } + if (value is string) { if (value.ToString().Length >= comparePropertyValue.ToString().Length) @@ -267,6 +312,14 @@ ValidationResult TriggerValueComparison() } } + if (value.IsTimeSpan()) + { + if ((TimeSpan)value > (TimeSpan)comparePropertyValue) + { + return new ValidationResult(errorMessage); + } + } + if (value is string) { if (value.ToString().Length > comparePropertyValue.ToString().Length) diff --git a/src/AspNetCore.CustomValidation/Extensions/ObjectExtensions.cs b/src/AspNetCore.CustomValidation/Extensions/ObjectExtensions.cs index 2614c74..8484bb2 100644 --- a/src/AspNetCore.CustomValidation/Extensions/ObjectExtensions.cs +++ b/src/AspNetCore.CustomValidation/Extensions/ObjectExtensions.cs @@ -23,5 +23,10 @@ internal static bool IsDateTime(this object value) { return value is DateTime; } + + internal static bool IsTimeSpan(this object value) + { + return value is TimeSpan; + } } } From 2031232ba17712ed9568f6a7a96adb149c30daf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9?= Date: Mon, 31 Jan 2022 12:46:25 +0300 Subject: [PATCH 2/3] Fix arrays and complex objects client side validation --- demo/AspNetCore/AspNetCore.csproj | 6 +++- .../Controllers/EmployeeController.cs | 2 +- demo/AspNetCore/Models/Employee.cs | 3 ++ .../Models/PreviousJobExperience.cs | 19 +++++++++++ demo/AspNetCore/Views/Employee/Create.cshtml | 20 ++++++++++++ ...anvirarjel.customvalidation.unobtrusive.js | 32 +++++++++++++------ .../Adapters/CompareToAttributeAdapter.cs | 3 ++ .../Adapters/RequiredIfAttributeAdapter.cs | 2 +- 8 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 demo/AspNetCore/Models/PreviousJobExperience.cs diff --git a/demo/AspNetCore/AspNetCore.csproj b/demo/AspNetCore/AspNetCore.csproj index 7b6a27f..5c89025 100644 --- a/demo/AspNetCore/AspNetCore.csproj +++ b/demo/AspNetCore/AspNetCore.csproj @@ -34,7 +34,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + diff --git a/demo/AspNetCore/Controllers/EmployeeController.cs b/demo/AspNetCore/Controllers/EmployeeController.cs index 02f2fd8..b87cfde 100644 --- a/demo/AspNetCore/Controllers/EmployeeController.cs +++ b/demo/AspNetCore/Controllers/EmployeeController.cs @@ -14,7 +14,7 @@ public ActionResult Index() // GET: Employee/Create public ActionResult Create() { - return View(); + return View(new Employee()); } // POST: Employee/Create diff --git a/demo/AspNetCore/Models/Employee.cs b/demo/AspNetCore/Models/Employee.cs index 6372491..e625ae9 100644 --- a/demo/AspNetCore/Models/Employee.cs +++ b/demo/AspNetCore/Models/Employee.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using AspNetCore.Models; using Microsoft.AspNetCore.Http; using TanvirArjel.CustomValidation.AspNetCore.Attributes; using TanvirArjel.CustomValidation.Attributes; @@ -70,6 +71,8 @@ public class Employee : IValidatableObject [RequiredIf(nameof(IsPhoneRequired), ComparisonType.Equal, true)] public string Phone { get; set; } + public PreviousJobExperience[] PreviousJobs { get; set; } = new PreviousJobExperience[2]; + public IEnumerable Validate(ValidationContext validationContext) { List validationResults = new List(); diff --git a/demo/AspNetCore/Models/PreviousJobExperience.cs b/demo/AspNetCore/Models/PreviousJobExperience.cs new file mode 100644 index 0000000..c11ef8b --- /dev/null +++ b/demo/AspNetCore/Models/PreviousJobExperience.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel.DataAnnotations; +using TanvirArjel.CustomValidation.Attributes; + +namespace AspNetCore.Models +{ + public class PreviousJobExperience + { + [DataType(DataType.Date)] + public DateTime? StartDate { get; set; } + + [DataType(DataType.Date)] + [CompareTo(nameof(StartDate), ComparisonType.GreaterThan)] + public DateTime? EndDate { get; set; } + + [RequiredIf(nameof(StartDate), ComparisonType.NotEqual, null)] + public string JobName { get; set; } + } +} diff --git a/demo/AspNetCore/Views/Employee/Create.cshtml b/demo/AspNetCore/Views/Employee/Create.cshtml index e019f41..6812f15 100644 --- a/demo/AspNetCore/Views/Employee/Create.cshtml +++ b/demo/AspNetCore/Views/Employee/Create.cshtml @@ -99,6 +99,26 @@ +
+ @for (var i = 0; i < Model.PreviousJobs.Length; i++) + { +
+ + + +
+
+ + + +
+
+ + + +
+
+ }
diff --git a/demo/AspNetCore/wwwroot/lib/tanvirarjel-custom-validation-unobtrusive/tanvirarjel.customvalidation.unobtrusive.js b/demo/AspNetCore/wwwroot/lib/tanvirarjel-custom-validation-unobtrusive/tanvirarjel.customvalidation.unobtrusive.js index afeb572..8ee8365 100644 --- a/demo/AspNetCore/wwwroot/lib/tanvirarjel-custom-validation-unobtrusive/tanvirarjel.customvalidation.unobtrusive.js +++ b/demo/AspNetCore/wwwroot/lib/tanvirarjel-custom-validation-unobtrusive/tanvirarjel.customvalidation.unobtrusive.js @@ -56,6 +56,17 @@ } } + function getInputName(otherPropertyName, elementName) { + let modelPrefix = elementName.substr(0, elementName.lastIndexOf(".") + 1); + + if (otherPropertyName.indexOf("*.") === 0) { + otherPropertyName = otherPropertyName.replace("*.", modelPrefix); + } + + // As mentioned on http://api.jquery.com/category/selectors/ + return otherPropertyName.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); + } + // valid date validation $.validator.addMethod("valid-date-format", function (value, element, params) { if (value) { @@ -211,7 +222,7 @@ $.validator.addMethod("input-type-compare", function (value, element, params) { var inputPropertyType = $(element).prop('type'); var comparePropertyName = params["property"]; - var compareProperty = $(element).closest('form').find('[name="' + comparePropertyName + '"]'); + var compareProperty = $(element).closest('form').find('[name="' + getInputName(comparePropertyName, element.name) + '"]'); var comparePropertyType = compareProperty.prop('type'); return inputPropertyType === comparePropertyType; @@ -230,7 +241,7 @@ const inputPropertyType = $(element).prop('type'); const comparePropertyName = params.property; - const compareProperty = $(element).closest('form').find('[name="' + comparePropertyName + '"]'); + const compareProperty = $(element).closest('form').find('[name="' + getInputName(comparePropertyName, element.name) + '"]'); const comparePropertyType = compareProperty.prop('type'); const comparePropertyValue = compareProperty.val(); @@ -278,7 +289,7 @@ const inputPropertyType = $(element).prop('type'); const comparePropertyName = params.property; - const compareProperty = $(element).closest('form').find('[name="' + comparePropertyName + '"]'); + const compareProperty = $(element).closest('form').find('[name="' + getInputName(comparePropertyName, element.name) + '"]'); const comparePropertyType = compareProperty.prop('type'); const comparePropertyValue = compareProperty.val(); @@ -324,7 +335,7 @@ let inputPropertyType = $(element).prop('type'); const comparePropertyName = params.property; - const compareProperty = $(element).closest('form').find('[name="' + comparePropertyName + '"]'); + const compareProperty = $(element).closest('form').find('[name="' + getInputName(comparePropertyName, element.name) + '"]'); const comparePropertyType = compareProperty.prop('type'); const comparePropertyValue = compareProperty.val(); @@ -372,7 +383,7 @@ let inputPropertyType = $(element).prop('type'); const comparePropertyName = params.property; - const compareProperty = $(element).closest('form').find('[name="' + comparePropertyName + '"]'); + const compareProperty = $(element).closest('form').find('[name="' + getInputName(comparePropertyName, element.name) + '"]'); const comparePropertyType = compareProperty.prop('type'); const comparePropertyValue = compareProperty.val(); @@ -418,7 +429,7 @@ const inputPropertyType = $(element).prop('type'); const comparePropertyName = params.property; - const compareProperty = $(element).closest('form').find('[name="' + comparePropertyName + '"]'); + const compareProperty = $(element).closest('form').find('[name="' + getInputName(comparePropertyName, element.name) + '"]'); const comparePropertyType = compareProperty.prop('type'); const comparePropertyValue = compareProperty.val(); @@ -464,7 +475,7 @@ const inputPropertyType = $(element).prop('type'); const comparePropertyName = params.property; - const compareProperty = $(element).closest('form').find('[name="' + comparePropertyName + '"]'); + const compareProperty = $(element).closest('form').find('[name="' + getInputName(comparePropertyName, element.name) + '"]'); const comparePropertyType = compareProperty.prop('type'); const comparePropertyValue = compareProperty.val(); @@ -565,16 +576,17 @@ const otherPropertyName = params['other-property']; const comparisonType = params['comparison-type']; const otherPropertyType = params['other-property-type']; - let otherPropertyValue = params['other-property-value']; + let otherPropertyValue = params['other-property-value']; + let inputName = getInputName(otherPropertyName, element.name); - const otherPropertyElement = $(element).closest('form').find('[name="' + otherPropertyName + '"]'); + const otherPropertyElement = $(element).closest('form').find('[name="' + inputName + '"]'); const otherPropertyInputType = otherPropertyElement.attr('type'); let otherPropertyCurrentValue = null; if (otherPropertyInputType == "checkbox" || otherPropertyInputType == "radio") { - var control = $("[name$='" + otherPropertyName + "']:checked"); + var control = $("[name$='" + inputName + "']:checked"); otherPropertyCurrentValue = control.val(); } else { otherPropertyCurrentValue = otherPropertyElement.val(); diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/CompareToAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/CompareToAttributeAdapter.cs index e7cff1c..550bc4d 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/CompareToAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/CompareToAttributeAdapter.cs @@ -33,6 +33,9 @@ public override void AddValidation(ClientModelValidationContext context) AddAttribute(context.Attributes, "data-val", "true"); AddAttribute(context.Attributes, "data-val-input-type-compare", $"{propertyDisplayName} is not comparable to {comparePropertyName}"); + + comparePropertyName = "*." + comparePropertyName; + AddAttribute(context.Attributes, "data-val-input-type-compare-property", comparePropertyName); if (comparisonType == ComparisonType.Equal) diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/RequiredIfAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/RequiredIfAttributeAdapter.cs index 60b1a2d..2b0752e 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/RequiredIfAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/RequiredIfAttributeAdapter.cs @@ -57,7 +57,7 @@ public override void AddValidation(ClientModelValidationContext context) } AddAttribute(context.Attributes, "data-val", "true"); - AddAttribute(context.Attributes, "data-val-requiredif-other-property", Attribute.OtherPropertyName); + AddAttribute(context.Attributes, "data-val-requiredif-other-property", "*." + Attribute.OtherPropertyName); AddAttribute(context.Attributes, "data-val-requiredif-comparison-type", Attribute.ComparisonType.ToString()); AddAttribute(context.Attributes, "data-val-requiredif-other-property-value", Attribute.OtherPropertyValue?.ToString() ?? string.Empty); From 9bddd7344f3420445ad8901b06eaf4e173db1b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9?= Date: Fri, 25 Mar 2022 11:09:26 +0300 Subject: [PATCH 3/3] Fixed InvalidOperationException when using error messages from resources --- demo/AspNetCore/AspNetCore.csproj | 15 ++ .../Controllers/EmployeeController.cs | 19 ++ demo/AspNetCore/Models/EmployeeWithResx.cs | 96 ++++++++++ demo/AspNetCore/Resources/Common.Designer.cs | 126 +++++++++++++ demo/AspNetCore/Resources/Common.resx | 141 +++++++++++++++ .../Resources/Views/Employee/Create.fr.resx | 3 + .../Resources/Views/Employee/Create.resx | 17 ++ .../Views/Employee/CreateWithResx.cshtml | 166 ++++++++++++++++++ demo/AspNetCore/Views/Shared/_Layout.cshtml | 5 + ...anvirarjel.customvalidation.unobtrusive.js | 15 +- .../TanvirArjelAttributeAdapterProvider.cs | 6 + .../Adapters/CompareToAttributeAdapter.cs | 31 +++- .../Adapters/FileMaxSizeAttributeAdapter.cs | 5 +- .../Adapters/FileMinSizeAttributeAdapter.cs | 5 +- .../Adapters/FileTypeAttributeAdapter.cs | 5 +- .../Adapters/MaxAgeAttributeAdapter.cs | 26 +-- .../Adapters/MaxDateAttributeAdapter.cs | 2 +- .../Adapters/MinAgeAttributeAdapter.cs | 18 +- .../Adapters/MinDateAttributeAdapter.cs | 2 +- .../Adapters/TextEditorAttributeAdapter.cs | 76 ++++++++ .../TextEditorMaxLengthAttributeAdapter.cs | 54 ++++++ .../TextEditorMinLengthAttributeAdapter.cs | 54 ++++++ .../TextEditorRequiredAttributeAdapter.cs | 27 +-- .../Attributes/FileMaxSizeAttribute.cs | 12 +- .../Attributes/FileMinSizeAttribute.cs | 12 +- .../Attributes/FileTypeAttribute.cs | 36 ++-- .../Attributes/CompareToAttribute.cs | 32 ++-- .../Attributes/FixedLengthAttribute.cs | 15 +- .../Attributes/MaxAgeAttribute.cs | 44 ++--- .../Attributes/MaxDateAttribute.cs | 19 +- .../Attributes/MinAgeAttribute.cs | 44 ++--- .../Attributes/MinDateAttribute.cs | 19 +- .../Attributes/RequiredIfAttribute.cs | 4 +- .../Attributes/TextEditorAttribute.cs | 106 +++++++++++ .../TextEditorMaxLengthAttribute.cs | 80 +++++++++ .../TextEditorMinLengthAttribute.cs | 82 +++++++++ .../Attributes/TextEditorRequiredAttribute.cs | 65 ++----- 37 files changed, 1233 insertions(+), 251 deletions(-) create mode 100644 demo/AspNetCore/Models/EmployeeWithResx.cs create mode 100644 demo/AspNetCore/Resources/Common.Designer.cs create mode 100644 demo/AspNetCore/Resources/Common.resx create mode 100644 demo/AspNetCore/Resources/Views/Employee/Create.resx create mode 100644 demo/AspNetCore/Views/Employee/CreateWithResx.cshtml create mode 100644 src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorAttributeAdapter.cs create mode 100644 src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorMaxLengthAttributeAdapter.cs create mode 100644 src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorMinLengthAttributeAdapter.cs create mode 100644 src/TanvirArjel.CustomValidation/Attributes/TextEditorAttribute.cs create mode 100644 src/TanvirArjel.CustomValidation/Attributes/TextEditorMaxLengthAttribute.cs create mode 100644 src/TanvirArjel.CustomValidation/Attributes/TextEditorMinLengthAttribute.cs diff --git a/demo/AspNetCore/AspNetCore.csproj b/demo/AspNetCore/AspNetCore.csproj index 5c89025..15c3bb0 100644 --- a/demo/AspNetCore/AspNetCore.csproj +++ b/demo/AspNetCore/AspNetCore.csproj @@ -41,4 +41,19 @@ + + + True + True + Common.resx + + + + + + PublicResXFileCodeGenerator + Common.Designer.cs + + + diff --git a/demo/AspNetCore/Controllers/EmployeeController.cs b/demo/AspNetCore/Controllers/EmployeeController.cs index b87cfde..46a19e6 100644 --- a/demo/AspNetCore/Controllers/EmployeeController.cs +++ b/demo/AspNetCore/Controllers/EmployeeController.cs @@ -29,5 +29,24 @@ public ActionResult Create(Employee employee) return View(employee); } + + // GET: Employee/Create + public ActionResult CreateWithResx() + { + return View(new EmployeeWithResx()); + } + + // POST: Employee/Create + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult CreateWithResx(EmployeeWithResx employee) + { + if (ModelState.IsValid) + { + return View(); + } + + return View(employee); + } } } \ No newline at end of file diff --git a/demo/AspNetCore/Models/EmployeeWithResx.cs b/demo/AspNetCore/Models/EmployeeWithResx.cs new file mode 100644 index 0000000..19bcd0c --- /dev/null +++ b/demo/AspNetCore/Models/EmployeeWithResx.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using AspNetCore.Models; +using AspNetCore.Resources; +using Microsoft.AspNetCore.Http; +using TanvirArjel.CustomValidation.AspNetCore.Attributes; +using TanvirArjel.CustomValidation.Attributes; + +namespace AspNetCore.CustomValidation.Demo.Models; + +public class EmployeeWithResx : IValidatableObject +{ + ////[DisplayName("Name")] + ////[FixedLength(5, ErrorMessage = "{0} should be exactly {1} characters long.")] + ////[TextEditorRequired] + [Required] + [MinLength(5)] + [AspNetCore.MyCustomAttributes.FooAtrribute] + public string FirstName { get; set; } + + [RequiredIf(nameof(FirstName), ComparisonType.Equal, "Tanvir", ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "RequiredError")] + public string LastName { get; set; } + + [Required] + [Range(1000, 9999)] + [Display(Name = "Starting Year")] + public int? StartingYear { get; set; } + + [Required] + [Range(1000, 9999)] + [CompareTo(nameof(StartingYear), ComparisonType.GreaterThanOrEqual, ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "GreaterThanOrEqualError")] + [Display(Name = "Ending Year")] + public int? EndingYear { get; set; } + + ////[DataType(DataType.Date)] + ////[DisplayName("Date Of Birth")] + [Required] + [MinAge(15, 11, 3, ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "MinAgeError")] + [MaxAge(20, 11, 3, ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "MaxAgeError")] + public DateTime DateOfBirth { get; set; } + + ////[MinDate(2019, 1, 1, ErrorMessage = "{0} should be minimun 2019 January 1.")] // 2019 January 1 + ////[MaxDate(2019, 10, 1, ErrorMessage = "{0} cannot be greater than {1}.")] // 2019 October 1 + ////[CompareTo(nameof(DateOfBirth), ComparisonType.GreaterThan)] + [DisplayName("Joining Date")] + ////[RequiredIf(nameof(DateOfBirth), ComparisonType.Equal, "01-May-2020")] + public DateTime? JoiningDate { get; set; } + + [Display(Name = "First Number")] + [RequiredIf(nameof(SecondNumber), ComparisonType.Equal, null, ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "RequiredError")] + public int? FirstNumber { get; set; } + + ////[RequiredIf(nameof(FirstNumber), ComparisonType.Equal, null)] + [Display(Name = "Second Number")] + [CompareTo(nameof(FirstNumber), ComparisonType.GreaterThan, ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "GreaterThanError")] + public int? SecondNumber { get; set; } + + ////[FileType(new FileType[] { FileType.Mp4, FileType.Mp3 }, ErrorMessage = "{0} should be in {1} formats.")] + ////[FileMinSize(10000, ErrorMessage = "{0} should be at least {1}.")] + [FileType(FileType.Jpeg, ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "FileTypeError")] + [FileMinSize(1024, ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "FileMinSizeError")] + public IFormFile Photo { get; set; } + + public TimeSpan? EntryTime { get; set; } + + [RequiredIf(nameof(EntryTime), ComparisonType.GreaterThan, "10:00", ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "RequiredError")] + public TimeSpan? OutTime { get; set; } + + public bool IsPhoneRequired { get; set; } + + [RequiredIf(nameof(IsPhoneRequired), ComparisonType.Equal, true, ErrorMessageResourceType = typeof(Common), ErrorMessageResourceName = "RequiredError")] + public string Phone { get; set; } + + public PreviousJobExperience[] PreviousJobs { get; set; } = new PreviousJobExperience[2]; + + public IEnumerable Validate(ValidationContext validationContext) + { + List validationResults = new List(); + + // FileOptions fileOptions = new FileOptions() + // { + // FileTypes = new FileType[] {FileType.Jpeg,FileType.Jpg}, + // MinSize = 124, + // MaxSize = Convert.ToInt32(AppSettings.GetValue("DemoSettings:MaxFileSize")) + // }; + + // ValidationResult fileValidationResult = validationContext.ValidateFile(nameof(Photo), fileOptions); + // validationResults.Add(fileValidationResult); + + // ValidationResult minAgeValidationResult = validationContext.ValidateMinAge(nameof(DateOfBirth), 10, 0, 0); + // validationResults.Add(minAgeValidationResult); + return validationResults; + } +} diff --git a/demo/AspNetCore/Resources/Common.Designer.cs b/demo/AspNetCore/Resources/Common.Designer.cs new file mode 100644 index 0000000..dab3bc7 --- /dev/null +++ b/demo/AspNetCore/Resources/Common.Designer.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AspNetCore.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Common { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Common() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AspNetCore.Resources.Common", typeof(Common).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to {0} should be at least {1}. + /// + public static string FileMinSizeError { + get { + return ResourceManager.GetString("FileMinSizeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} should be in {1} formats. + /// + public static string FileTypeError { + get { + return ResourceManager.GetString("FileTypeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} should be greater than {1}. + /// + public static string GreaterThanError { + get { + return ResourceManager.GetString("GreaterThanError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} should be greater than or equal {1}. + /// + public static string GreaterThanOrEqualError { + get { + return ResourceManager.GetString("GreaterThanOrEqualError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} cannot be smaller than {1}. + /// + public static string MaxAgeError { + get { + return ResourceManager.GetString("MaxAgeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} cannot be larger than {1}. + /// + public static string MinAgeError { + get { + return ResourceManager.GetString("MinAgeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} field is required. + /// + public static string RequiredError { + get { + return ResourceManager.GetString("RequiredError", resourceCulture); + } + } + } +} diff --git a/demo/AspNetCore/Resources/Common.resx b/demo/AspNetCore/Resources/Common.resx new file mode 100644 index 0000000..89fa71a --- /dev/null +++ b/demo/AspNetCore/Resources/Common.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + The {0} should be greater than {1} + + + The {0} should be greater than or equal {1} + + + The {0} cannot be larger than {1} + + + The {0} cannot be smaller than {1} + + + The {0} field is required + + + The {0} should be in {1} formats + + + {0} should be at least {1} + + \ No newline at end of file diff --git a/demo/AspNetCore/Resources/Views/Employee/Create.fr.resx b/demo/AspNetCore/Resources/Views/Employee/Create.fr.resx index 5bcf8f3..fc7f776 100644 --- a/demo/AspNetCore/Resources/Views/Employee/Create.fr.resx +++ b/demo/AspNetCore/Resources/Views/Employee/Create.fr.resx @@ -126,4 +126,7 @@ Employée + + + \ No newline at end of file diff --git a/demo/AspNetCore/Resources/Views/Employee/Create.resx b/demo/AspNetCore/Resources/Views/Employee/Create.resx new file mode 100644 index 0000000..8663cce --- /dev/null +++ b/demo/AspNetCore/Resources/Views/Employee/Create.resx @@ -0,0 +1,17 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + \ No newline at end of file diff --git a/demo/AspNetCore/Views/Employee/CreateWithResx.cshtml b/demo/AspNetCore/Views/Employee/CreateWithResx.cshtml new file mode 100644 index 0000000..df87ef5 --- /dev/null +++ b/demo/AspNetCore/Views/Employee/CreateWithResx.cshtml @@ -0,0 +1,166 @@ +@using Microsoft.AspNetCore.Mvc.Localization +@model AspNetCore.CustomValidation.Demo.Models.EmployeeWithResx +@inject IViewLocalizer Localizer +@{ + ViewData["Title"] = Localizer["Create"]; +} + + +

@Localizer["Create"]

+ +

@Localizer["EmployeeWithResx"]

+
+
+
+
+
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+ + @*
+ +
+ +
+
+ +
+
+
+ + +
*@ +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ @for (var i = 0; i < Model.PreviousJobs.Length; i++) + { +
+ + + +
+
+ + + +
+
+ + + +
+
+ } +
+ +
+
+
+
+ + + +@section Scripts { + + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} + + + + +} diff --git a/demo/AspNetCore/Views/Shared/_Layout.cshtml b/demo/AspNetCore/Views/Shared/_Layout.cshtml index 9b73b5a..1c138b5 100644 --- a/demo/AspNetCore/Views/Shared/_Layout.cshtml +++ b/demo/AspNetCore/Views/Shared/_Layout.cshtml @@ -34,6 +34,11 @@ @Localizer["Employee"] + diff --git a/demo/AspNetCore/wwwroot/lib/tanvirarjel-custom-validation-unobtrusive/tanvirarjel.customvalidation.unobtrusive.js b/demo/AspNetCore/wwwroot/lib/tanvirarjel-custom-validation-unobtrusive/tanvirarjel.customvalidation.unobtrusive.js index 8ee8365..2dcf111 100644 --- a/demo/AspNetCore/wwwroot/lib/tanvirarjel-custom-validation-unobtrusive/tanvirarjel.customvalidation.unobtrusive.js +++ b/demo/AspNetCore/wwwroot/lib/tanvirarjel-custom-validation-unobtrusive/tanvirarjel.customvalidation.unobtrusive.js @@ -132,11 +132,7 @@ // max age validation $.validator.addMethod("maxage", function (value, element, params) { if (value) { - let maxAgeDateTime = new Date(); - - maxAgeDateTime.setFullYear(maxAgeDateTime.getFullYear() - params.years); - maxAgeDateTime.setMonth(maxAgeDateTime.getMonth() - params.months); - maxAgeDateTime.setDate(maxAgeDateTime.getDate() - params.days); + let maxAgeDateTime = new Date(params.maxagedatetime); const inputDate = getDateValue(value); return inputDate >= maxAgeDateTime; @@ -145,7 +141,7 @@ return true; }); - $.validator.unobtrusive.adapters.add("maxage", ['years', 'months', 'days'], function (options) { + $.validator.unobtrusive.adapters.add("maxage", ['maxagedatetime'], function (options) { options.rules.maxage = options.params; options.messages["maxage"] = options.message; }); @@ -153,10 +149,7 @@ // min age validation $.validator.addMethod("minage", function (value, element, params) { if (value) { - let minAgeDateTime = new Date(); - minAgeDateTime.setFullYear(minAgeDateTime.getFullYear() - params.years); - minAgeDateTime.setMonth(minAgeDateTime.getMonth() - params.months); - minAgeDateTime.setDate(minAgeDateTime.getDate() - params.days); + let minAgeDateTime = new Date(params.minagedatetime); const inputDate = getDateValue(value); return minAgeDateTime >= inputDate; @@ -165,7 +158,7 @@ return true; }); - $.validator.unobtrusive.adapters.add("minage", ['years', 'months', 'days'], function (options) { + $.validator.unobtrusive.adapters.add("minage", ['minagedatetime'], function (options) { options.rules.minage = options.params; options.messages["minage"] = options.message; }); diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/AdapterProviders/TanvirArjelAttributeAdapterProvider.cs b/src/TanvirArjel.CustomValidation.AspNetCore/AdapterProviders/TanvirArjelAttributeAdapterProvider.cs index 801c2b8..6d3b30d 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/AdapterProviders/TanvirArjelAttributeAdapterProvider.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/AdapterProviders/TanvirArjelAttributeAdapterProvider.cs @@ -50,8 +50,14 @@ public virtual IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribu return new MinDateAttributeAdapter(minDateAttribute, stringLocalizer); case RequiredIfAttribute requiredIfAttribute: return new RequiredIfAttributeAdapter(requiredIfAttribute, stringLocalizer); + case TextEditorAttribute textEditorAttribute: + return new TextEditorAttributeAdapter(textEditorAttribute, stringLocalizer); case TextEditorRequiredAttribute textEditorRequiredAttribute: return new TextEditorRequiredAttributeAdapter(textEditorRequiredAttribute, stringLocalizer); + case TextEditorMaxLengthAttribute textEditorMaxLengthAttribute: + return new TextEditorMaxLengthAttributeAdapter(textEditorMaxLengthAttribute, stringLocalizer); + case TextEditorMinLengthAttribute textEditorMinLengthAttribute: + return new TextEditorMinLengthAttributeAdapter(textEditorMinLengthAttribute, stringLocalizer); default: return _baseProvider.GetAttributeAdapter(attribute, stringLocalizer); } diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/CompareToAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/CompareToAttributeAdapter.cs index 550bc4d..acc35f6 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/CompareToAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/CompareToAttributeAdapter.cs @@ -16,7 +16,7 @@ namespace TanvirArjel.CustomValidation.AspNetCore.Adapters internal class CompareToAttributeAdapter : AttributeAdapterBase { public CompareToAttributeAdapter(CompareToAttribute attribute, IStringLocalizer stringLocalizer) - : base(attribute, stringLocalizer) + : base(new CompareToAttributeWrapper(attribute), stringLocalizer) { } @@ -86,6 +86,8 @@ public override string GetErrorMessage(ModelValidationContextBase validationCont string comparePropertyDisplayName = validationContext.ModelMetadata.ContainerMetadata.Properties .Single(p => p.PropertyName == Attribute.ComparePropertyName).GetDisplayName(); + ((CompareToAttributeWrapper)Attribute).ComparePropertyDisplayName = comparePropertyDisplayName; + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, comparePropertyDisplayName); } @@ -97,9 +99,32 @@ private static void AddAttribute(IDictionary attributes, string } } - private static string GetFormattedErrorMessage(string errorMessage, string propertyName, string comparePropertyName) + // The attribute wrapper is needed to override the FormatErrorMessage method + // See https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.DataAnnotations/src/CompareAttributeAdapter.cs + private sealed class CompareToAttributeWrapper : CompareToAttribute { - return string.Format(CultureInfo.InvariantCulture, errorMessage, propertyName, comparePropertyName); + public CompareToAttributeWrapper(CompareToAttribute attribute) + : base(attribute.ComparePropertyName, attribute.ComparisonType) + { + if (!string.IsNullOrEmpty(attribute.ErrorMessage) || + !string.IsNullOrEmpty(attribute.ErrorMessageResourceName) || + attribute.ErrorMessageResourceType != null) + { + ErrorMessage = attribute.ErrorMessage; + ErrorMessageResourceName = attribute.ErrorMessageResourceName; + ErrorMessageResourceType = attribute.ErrorMessageResourceType; + } + } + + /// + /// Display name of the property which against the comparison will be done. + /// + public string ComparePropertyDisplayName { get; set; } + + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, ComparePropertyDisplayName ?? ComparePropertyName); + } } } } diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileMaxSizeAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileMaxSizeAttributeAdapter.cs index 5305c2c..343842c 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileMaxSizeAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileMaxSizeAttributeAdapter.cs @@ -41,9 +41,8 @@ public override string GetErrorMessage(ModelValidationContextBase validationCont } string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); - int maxSize = Attribute.MaxSize; - string maxSizeAndUnit = maxSize >= 1024 ? Math.Round(maxSize / 1024M, 2) + " MB" : maxSize + " KB"; - return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, maxSizeAndUnit); + + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.MaxSizeAndUnit); } private static void AddAttribute(IDictionary attributes, string key, string value) diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileMinSizeAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileMinSizeAttributeAdapter.cs index 080f084..fbe658b 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileMinSizeAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileMinSizeAttributeAdapter.cs @@ -41,9 +41,8 @@ public override string GetErrorMessage(ModelValidationContextBase validationCont } string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); - int minSize = Attribute.MinSize; - string minSizeAndUnit = minSize >= 1024 ? Math.Round(minSize / 1024M, 2) + " MB" : minSize + " KB"; - return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, minSizeAndUnit); + + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.MinSizeAndUnit); } private static void AddAttribute(IDictionary attributes, string key, string value) diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileTypeAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileTypeAttributeAdapter.cs index 3ce313f..296d863 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileTypeAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/FileTypeAttributeAdapter.cs @@ -42,9 +42,8 @@ public override string GetErrorMessage(ModelValidationContextBase validationCont } string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); - string[] validFileTypeNames = Attribute.FileTypes.Select(ft => ft.ToString("G")).ToArray(); - string validFileTypeNamesString = string.Join(",", validFileTypeNames); - return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, validFileTypeNamesString); + + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.ValidFileTypeNamesString); } private static void AddAttribute(IDictionary attributes, string key, string value) diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MaxAgeAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MaxAgeAttributeAdapter.cs index 633858a..d52a6dc 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MaxAgeAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MaxAgeAttributeAdapter.cs @@ -31,22 +31,28 @@ public override void AddValidation(ClientModelValidationContext context) AddAttribute(context.Attributes, "data-val", "true"); - AddAttribute(context.Attributes, "data-val-valid-date-format", "The input date/datetime format is not valid! Please prefer: '01-Jan-2019' format."); - AddAttribute(context.Attributes, "data-val-currenttime", $"{propertyDisplayName} can not be greater than today's date."); - AddAttribute(context.Attributes, "data-val-maxage", errorMessage); + AddAttribute(context.Attributes, "data-val-valid-date-format", + "The input date/datetime format is not valid! Please prefer: '01-Jan-2019' format."); + + AddAttribute(context.Attributes, "data-val-currenttime", + $"{propertyDisplayName} can not be greater than today's date."); - string years = Attribute.Years.ToString(CultureInfo.InvariantCulture); - string months = Attribute.Months.ToString(CultureInfo.InvariantCulture); - string days = Attribute.Days.ToString(CultureInfo.InvariantCulture); + AddAttribute(context.Attributes, "data-val-maxage", errorMessage); - AddAttribute(context.Attributes, "data-val-maxage-years", years); - AddAttribute(context.Attributes, "data-val-maxage-months", months); - AddAttribute(context.Attributes, "data-val-maxage-days", days); + string maxAgeDateTime = Attribute.MaxAgeDateTime.ToString("dd-MMM-yyyy", CultureInfo.InvariantCulture); + AddAttribute(context.Attributes, "data-val-maxage-maxagedatetime", maxAgeDateTime); } public override string GetErrorMessage(ModelValidationContextBase validationContext) { - return GetErrorMessage(validationContext.ModelMetadata, Attribute.Years, Attribute.Months, Attribute.Days); + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); + + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.MaxAgeDateTime.ToString(Attribute.ErrorMessageMaxAgeDateTimeFormat, CultureInfo.CurrentCulture)); } private static void AddAttribute(IDictionary attributes, string key, string value) diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MaxDateAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MaxDateAttributeAdapter.cs index fc9a00f..faa6cd6 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MaxDateAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MaxDateAttributeAdapter.cs @@ -43,7 +43,7 @@ public override string GetErrorMessage(ModelValidationContextBase validationCont } string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); - return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.MaxDate); + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.MaxDate.ToString(Attribute.ErrorMessageMaxDateFormat, CultureInfo.CurrentCulture)); } private static void AddAttribute(IDictionary attributes, string key, string value) diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MinAgeAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MinAgeAttributeAdapter.cs index 8208dcd..92cd452 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MinAgeAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MinAgeAttributeAdapter.cs @@ -35,18 +35,20 @@ public override void AddValidation(ClientModelValidationContext context) AddAttribute(context.Attributes, "data-val-minage", errorMessage); - string years = Attribute.Years.ToString(CultureInfo.InvariantCulture); - string months = Attribute.Months.ToString(CultureInfo.InvariantCulture); - string days = Attribute.Days.ToString(CultureInfo.InvariantCulture); - - AddAttribute(context.Attributes, "data-val-minage-years", years); - AddAttribute(context.Attributes, "data-val-minage-months", months); - AddAttribute(context.Attributes, "data-val-minage-days", days); + string minAgeDateTime = Attribute.MinAgeDateTime.ToString("dd-MMM-yyyy", CultureInfo.InvariantCulture); + AddAttribute(context.Attributes, "data-val-minage-minagedatetime", minAgeDateTime); } public override string GetErrorMessage(ModelValidationContextBase validationContext) { - return GetErrorMessage(validationContext.ModelMetadata, Attribute.Years, Attribute.Months, Attribute.Years); + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); + + return GetErrorMessage(validationContext.ModelMetadata,propertyDisplayName, Attribute.MinAgeDateTime.ToString(Attribute.ErrorMessageMinAgeDateTimeFormat, CultureInfo.CurrentCulture)); } private static void AddAttribute(IDictionary attributes, string key, string value) diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MinDateAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MinDateAttributeAdapter.cs index c27716c..a36ad06 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MinDateAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/MinDateAttributeAdapter.cs @@ -44,7 +44,7 @@ public override string GetErrorMessage(ModelValidationContextBase validationCont } string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); - return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.MinDate); + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.MinDate.ToString(Attribute.ErrorMessageMinDateFormat, CultureInfo.CurrentCulture)); } private static void AddAttribute(IDictionary attributes, string key, string value) diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorAttributeAdapter.cs new file mode 100644 index 0000000..7f09e04 --- /dev/null +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorAttributeAdapter.cs @@ -0,0 +1,76 @@ +// +// Copyright (c) TanvirArjel. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; +using TanvirArjel.CustomValidation.Attributes; + +namespace TanvirArjel.CustomValidation.AspNetCore.Adapters +{ + internal class TextEditorAttributeAdapter : AttributeAdapterBase + { + private IStringLocalizer _stringLocalizer; + + public TextEditorAttributeAdapter(TextEditorAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + _stringLocalizer = stringLocalizer; + } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + AddAttribute(context.Attributes, "data-val", "true"); + AddAttribute(context.Attributes, "data-val-texteditor-required", GetErrorMessage(context)); + + string propertyDisplayName = context.ModelMetadata.GetDisplayName(); + + if (Attribute.MinLength > 0) + { + string minLengthErrorMessage = _stringLocalizer[Attribute.MinLengthErrorMessage]; + string formattedMinLengthErrorMessage = string.Format(CultureInfo.InvariantCulture, minLengthErrorMessage, propertyDisplayName, Attribute.MinLength); + + AddAttribute(context.Attributes, "data-val-texteditor-minlength", formattedMinLengthErrorMessage); + AddAttribute(context.Attributes, "data-val-texteditor-minlength-value", Attribute.MinLength.ToString(CultureInfo.InvariantCulture)); + } + + if (Attribute.MaxLength > 0) + { + string maxLengthErrorMessage = _stringLocalizer[Attribute.MaxLengthErrorMessage]; + string formattedMaxLengthErrorMessage = string.Format(CultureInfo.InvariantCulture, maxLengthErrorMessage, propertyDisplayName, Attribute.MaxLength); + + AddAttribute(context.Attributes, "data-val-texteditor-maxlength", formattedMaxLengthErrorMessage); + AddAttribute(context.Attributes, "data-val-texteditor-maxlength-value", Attribute.MaxLength.ToString(CultureInfo.InvariantCulture)); + } + } + + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); + + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName); + } + + private static void AddAttribute(IDictionary attributes, string key, string value) + { + if (!attributes.ContainsKey(key)) + { + attributes.Add(key, value); + } + } + } +} diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorMaxLengthAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorMaxLengthAttributeAdapter.cs new file mode 100644 index 0000000..2e33bb5 --- /dev/null +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorMaxLengthAttributeAdapter.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; +using TanvirArjel.CustomValidation.Attributes; + +namespace TanvirArjel.CustomValidation.AspNetCore.Adapters +{ + internal class TextEditorMaxLengthAttributeAdapter : AttributeAdapterBase + { + public TextEditorMaxLengthAttributeAdapter(TextEditorMaxLengthAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + AddAttribute(context.Attributes, "data-val", "true"); + + if (Attribute.MaxLength > 0) + { + AddAttribute(context.Attributes, "data-val-texteditor-maxlength", GetErrorMessage(context)); + AddAttribute(context.Attributes, "data-val-texteditor-maxlength-value", Attribute.MaxLength.ToString(CultureInfo.InvariantCulture)); + } + } + + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); + + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.MaxLength); + } + + private static void AddAttribute(IDictionary attributes, string key, string value) + { + if (!attributes.ContainsKey(key)) + { + attributes.Add(key, value); + } + } + } +} diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorMinLengthAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorMinLengthAttributeAdapter.cs new file mode 100644 index 0000000..cc3a0be --- /dev/null +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorMinLengthAttributeAdapter.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; +using TanvirArjel.CustomValidation.Attributes; + +namespace TanvirArjel.CustomValidation.AspNetCore.Adapters +{ + internal class TextEditorMinLengthAttributeAdapter : AttributeAdapterBase + { + public TextEditorMinLengthAttributeAdapter(TextEditorMinLengthAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + AddAttribute(context.Attributes, "data-val", "true"); + + if (Attribute.MinLength > 0) + { + AddAttribute(context.Attributes, "data-val-texteditor-minlength", GetErrorMessage(context)); + AddAttribute(context.Attributes, "data-val-texteditor-minlength-value", Attribute.MinLength.ToString(CultureInfo.InvariantCulture)); + } + } + + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + string propertyDisplayName = validationContext.ModelMetadata.GetDisplayName(); + + return GetErrorMessage(validationContext.ModelMetadata, propertyDisplayName, Attribute.MinLength); + } + + private static void AddAttribute(IDictionary attributes, string key, string value) + { + if (!attributes.ContainsKey(key)) + { + attributes.Add(key, value); + } + } + } +} diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorRequiredAttributeAdapter.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorRequiredAttributeAdapter.cs index d503f28..11a7da1 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorRequiredAttributeAdapter.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Adapters/TextEditorRequiredAttributeAdapter.cs @@ -1,10 +1,5 @@ -// -// Copyright (c) TanvirArjel. All rights reserved. -// - -using System; +using System; using System.Collections.Generic; -using System.Globalization; using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Localization; @@ -31,26 +26,6 @@ public override void AddValidation(ClientModelValidationContext context) AddAttribute(context.Attributes, "data-val", "true"); AddAttribute(context.Attributes, "data-val-texteditor-required", GetErrorMessage(context)); - - string propertyDisplayName = context.ModelMetadata.GetDisplayName(); - - if (Attribute.MinLength > 0) - { - string minLengthErrorMessage = _stringLocalizer[Attribute.MinLengthErrorMessage]; - string formattedMinLengthErrorMessage = string.Format(CultureInfo.InvariantCulture, minLengthErrorMessage, propertyDisplayName, Attribute.MinLength); - - AddAttribute(context.Attributes, "data-val-texteditor-minlength", formattedMinLengthErrorMessage); - AddAttribute(context.Attributes, "data-val-texteditor-minlength-value", Attribute.MinLength.ToString(CultureInfo.InvariantCulture)); - } - - if (Attribute.MaxLength > 0) - { - string maxLengthErrorMessage = _stringLocalizer[Attribute.MaxLengthErrorMessage]; - string formattedMaxLengthErrorMessage = string.Format(CultureInfo.InvariantCulture, maxLengthErrorMessage, propertyDisplayName, Attribute.MaxLength); - - AddAttribute(context.Attributes, "data-val-texteditor-maxlength", formattedMaxLengthErrorMessage); - AddAttribute(context.Attributes, "data-val-texteditor-maxlength-value", Attribute.MaxLength.ToString(CultureInfo.InvariantCulture)); - } } public override string GetErrorMessage(ModelValidationContextBase validationContext) diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileMaxSizeAttribute.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileMaxSizeAttribute.cs index 9d8c7fb..57adf33 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileMaxSizeAttribute.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileMaxSizeAttribute.cs @@ -20,9 +20,9 @@ public sealed class FileMaxSizeAttribute : ValidationAttribute /// /// Allowed of the file in KB. public FileMaxSizeAttribute(int maxSize) + : base("{0} should be not more than {1}.") { MaxSize = maxSize; - ErrorMessage = ErrorMessage ?? "{0} should be not more than {1}."; } /// @@ -33,7 +33,12 @@ public FileMaxSizeAttribute(int maxSize) /// /// Get allowed of the file with appropriate unit. /// - private string MaxSizeAndUnit => MaxSize >= 1024 ? Math.Round(MaxSize / 1024M, 2) + " MB" : MaxSize + " KB"; + internal string MaxSizeAndUnit => MaxSize >= 1024 ? Math.Round(MaxSize / 1024M, 2) + " MB" : MaxSize + " KB"; + + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MaxSizeAndUnit); + } /// /// To check whether the input is larger than the specified size. @@ -72,8 +77,7 @@ protected override ValidationResult IsValid(object value, ValidationContext vali if (MaxSize > 0 && fileLengthInKByte > MaxSize) { - string formattedErrorMessage = string.Format(CultureInfo.InvariantCulture, ErrorMessage, validationContext.DisplayName, MaxSizeAndUnit); - return new ValidationResult(formattedErrorMessage); + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } else diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileMinSizeAttribute.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileMinSizeAttribute.cs index 9575b97..bc64f1a 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileMinSizeAttribute.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileMinSizeAttribute.cs @@ -20,9 +20,9 @@ public sealed class FileMinSizeAttribute : ValidationAttribute /// /// Allowed of the file in KB. public FileMinSizeAttribute(int minSize) + : base("{0} should be at least {1}.") { MinSize = minSize; - ErrorMessage = ErrorMessage ?? "{0} should be at least {1}."; } /// @@ -33,7 +33,12 @@ public FileMinSizeAttribute(int minSize) /// /// Get allowed of the file with appropriate unit. /// - private string MinSizeAndUnit => MinSize >= 1024 ? Math.Round(MinSize / 1024M, 2) + " MB" : MinSize + " KB"; + internal string MinSizeAndUnit => MinSize >= 1024 ? Math.Round(MinSize / 1024M, 2) + " MB" : MinSize + " KB"; + + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MinSizeAndUnit); + } /// /// To check whether the input is smaller than the specified size. @@ -72,8 +77,7 @@ protected override ValidationResult IsValid(object value, ValidationContext vali if (MinSize > 0 && fileLengthInKByte < MinSize) { - string formattedErrorMessage = string.Format(CultureInfo.InvariantCulture, ErrorMessage, validationContext.DisplayName, MinSizeAndUnit); - return new ValidationResult(formattedErrorMessage); + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } else diff --git a/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileTypeAttribute.cs b/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileTypeAttribute.cs index 9fc1874..6666601 100644 --- a/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileTypeAttribute.cs +++ b/src/TanvirArjel.CustomValidation.AspNetCore/Attributes/FileTypeAttribute.cs @@ -17,24 +17,16 @@ namespace TanvirArjel.CustomValidation.AspNetCore.Attributes /// public sealed class FileTypeAttribute : ValidationAttribute { - /// - /// Initializes a new instance of the class. - /// - /// A single value. - public FileTypeAttribute(FileType fileType) - { - FileTypes = new FileType[] { fileType }; - ErrorMessage = ErrorMessage ?? "The {0} should be in {1} format."; - } - /// /// This is used to validate file type of object. /// /// An of . - public FileTypeAttribute(FileType[] fileTypes) + public FileTypeAttribute(params FileType[] fileTypes) + : base("The {0} should be in {1} formats.") { FileTypes = fileTypes; - ErrorMessage = ErrorMessage ?? "The {0} should be in {1} formats."; + string[] validFileTypeNames = FileTypes.Select(ft => ft.ToString("G")).ToArray(); + ValidFileTypeNamesString = string.Join(",", validFileTypeNames); } /// @@ -42,6 +34,16 @@ public FileTypeAttribute(FileType[] fileTypes) /// public FileType[] FileTypes { get; } + /// + /// Valid file types string + /// + internal string ValidFileTypeNamesString { get; } + + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, ValidFileTypeNamesString); + } + /// /// To check whether the input is type of the specified type. /// @@ -81,10 +83,7 @@ protected override ValidationResult IsValid(object value, ValidationContext vali validFileTypes = validFileTypes.SelectMany(vft => vft.Split(',')).ToArray(); if (!validFileTypes.Contains(inputFile.ContentType.ToUpperInvariant())) { - string[] validFileTypeNames = FileTypes.Select(ft => ft.ToString("G")).ToArray(); - string validFileTypeNamesString = string.Join(",", validFileTypeNames); - string fileTypeErrorMessage = GetFileTypeErrorMessage(ErrorMessage, validationContext.DisplayName, validFileTypeNamesString); - return new ValidationResult(fileTypeErrorMessage); + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } } @@ -96,10 +95,5 @@ protected override ValidationResult IsValid(object value, ValidationContext vali return ValidationResult.Success; } - - private static string GetFileTypeErrorMessage(string errorMessageString, string propertyName, string fileTypeNamesString) - { - return string.Format(CultureInfo.InvariantCulture, errorMessageString, propertyName, fileTypeNamesString); - } } } diff --git a/src/TanvirArjel.CustomValidation/Attributes/CompareToAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/CompareToAttribute.cs index eaed079..2d6b58d 100644 --- a/src/TanvirArjel.CustomValidation/Attributes/CompareToAttribute.cs +++ b/src/TanvirArjel.CustomValidation/Attributes/CompareToAttribute.cs @@ -51,7 +51,7 @@ public enum ComparisonType /// This is used to compare the decorated property value against the another property value of the same object. /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] - public sealed class CompareToAttribute : ValidationAttribute + public class CompareToAttribute : ValidationAttribute { /// /// Initializes a new instance of the class. @@ -59,14 +59,10 @@ public sealed class CompareToAttribute : ValidationAttribute /// Name of the property which against the comparison will be done. /// The . public CompareToAttribute(string comparePropertyName, ComparisonType comparisonType) + : base(GetErrorMessage(comparisonType)) { ComparePropertyName = comparePropertyName; ComparisonType = comparisonType; - - if (ErrorMessage == null) - { - SetErrorMessage(comparisonType); - } } /// @@ -152,7 +148,7 @@ ValidationResult TriggerValueComparison() DisplayAttribute comparePropertyDisplayAttribute = comparePropertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true).FirstOrDefault() as DisplayAttribute; string comparePropertyDisplayName = comparePropertyDisplayAttribute?.GetName() ?? ComparePropertyName; - string errorMessage = string.Format(CultureInfo.InvariantCulture, ErrorMessage, propertyDisplayName, comparePropertyDisplayName); + string errorMessage = string.Format(CultureInfo.CurrentCulture, ErrorMessageString, propertyDisplayName, comparePropertyDisplayName); // Cast value to the appropriate dynamic type. dynamic propertyValueDynamic; @@ -180,7 +176,7 @@ ValidationResult TriggerValueComparison() } else { - throw new Exception($"The type is not supported in {nameof(RequiredIfAttribute)}."); + throw new Exception($"The type is not supported in {nameof(CompareToAttribute)}."); } // Do comaprison and do the required validation. @@ -231,28 +227,22 @@ ValidationResult TriggerValueComparison() } } - private void SetErrorMessage(ComparisonType comparisonType) + private static string GetErrorMessage(ComparisonType comparisonType) { switch (comparisonType) { case ComparisonType.Equal: - ErrorMessage = "The {0} is not equal to {1}."; - break; + return "The {0} is not equal to {1}."; case ComparisonType.NotEqual: - ErrorMessage = "The {0} can not be equal to {1}."; - break; + return "The {0} can not be equal to {1}."; case ComparisonType.GreaterThan: - ErrorMessage = "The {0} should be greater than {1}."; - break; + return "The {0} should be greater than {1}."; case ComparisonType.GreaterThanOrEqual: - ErrorMessage = "The {0} should be greater than or equal {1}."; - break; + return "The {0} should be greater than or equal {1}."; case ComparisonType.SmallerThan: - ErrorMessage = "The {0} should be smaller than {1}."; - break; + return "The {0} should be smaller than {1}."; case ComparisonType.SmallerThanOrEqual: - ErrorMessage = "The {0} should be smaller than or equal {1}."; - break; + return "The {0} should be smaller than or equal {1}."; default: throw new ArgumentNullException(nameof(comparisonType)); } diff --git a/src/TanvirArjel.CustomValidation/Attributes/FixedLengthAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/FixedLengthAttribute.cs index fc9f400..dbf102a 100644 --- a/src/TanvirArjel.CustomValidation/Attributes/FixedLengthAttribute.cs +++ b/src/TanvirArjel.CustomValidation/Attributes/FixedLengthAttribute.cs @@ -21,9 +21,9 @@ public sealed class FixedLengthAttribute : ValidationAttribute /// /// A positive value. public FixedLengthAttribute(int fixedLength) + : base("The {0} should be exactly {1} characters long.") { FixedLength = fixedLength; - ErrorMessage = ErrorMessage ?? "The {0} should be exactly {1} characters long."; } /// @@ -31,6 +31,11 @@ public FixedLengthAttribute(int fixedLength) /// public int FixedLength { get; } + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, FixedLength); + } + /// /// To check whether the input value has exactly same as specified. /// @@ -64,17 +69,11 @@ protected override ValidationResult IsValid(object value, ValidationContext vali if (inputValue.Length != FixedLength) { - string errorMessage = GetFormattedErrorMessage(ErrorMessage, validationContext.DisplayName, FixedLength); - return new ValidationResult(errorMessage); + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } return ValidationResult.Success; } - - private static string GetFormattedErrorMessage(string errorMessage, string propertyName, int fixedLength) - { - return string.Format(CultureInfo.InvariantCulture, errorMessage, propertyName, fixedLength); - } } } diff --git a/src/TanvirArjel.CustomValidation/Attributes/MaxAgeAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/MaxAgeAttribute.cs index dba3051..8a968fc 100644 --- a/src/TanvirArjel.CustomValidation/Attributes/MaxAgeAttribute.cs +++ b/src/TanvirArjel.CustomValidation/Attributes/MaxAgeAttribute.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Globalization; using System.Reflection; namespace TanvirArjel.CustomValidation.Attributes @@ -22,33 +23,25 @@ public sealed class MaxAgeAttribute : ValidationAttribute /// A value in between 0 and 11. /// A value in between 0 and 31. public MaxAgeAttribute(int years, int months, int days) + : base("The {0} cannot be smaller than {1}.") { - Years = years < 0 ? 0 : years; - Months = years < 0 ? 0 : months; - Days = days < 0 ? 0 : days; - - ErrorMessage = ErrorMessage ?? $"The Maximum age can be {(Years > 0 ? years + " years" : string.Empty)} {(Months > 0 ? months + " months" : string.Empty)} {(Days > 0 ? days + " days." : string.Empty)}"; + MaxAgeDateTime = DateTime.Today.AddYears(years < 0 ? 0 : -years).AddMonths(months < 0 ? 0 : -months).AddDays(days < 0 ? 0 : -days); } /// - /// Get the year value of the max allowed age. - /// - public int Years { get; } - - /// - /// Get the month value of the max allowed age. + /// Get the allowed min date value. /// - public int Months { get; } + public DateTime MaxAgeDateTime { get; } /// - /// Get the day value of the max allowed age. + /// Gets the format of the that will be used in /// - public int Days { get; } + public string ErrorMessageMaxAgeDateTimeFormat { get; set; } = "dd-MM-yyyy"; - ////public override string FormatErrorMessage(string displayName) - ////{ - //// return string.Format(CultureInfo.InvariantCulture, this.ErrorMessage, this.Years, this.Months, this.Days); - ////} + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MaxAgeDateTime.ToString(ErrorMessageMaxAgeDateTimeFormat, CultureInfo.CurrentCulture)); + } /// /// To check whether the input date violates the specified max age constraint. @@ -86,18 +79,13 @@ protected override ValidationResult IsValid(object value, ValidationContext vali return new ValidationResult($"{validationContext.DisplayName} can not be greater than today's date."); } - DateTime dateNow = DateTime.Now; - TimeSpan timeSpan = dateNow.Subtract(dateOfBirth); - DateTime ageDateTime = DateTime.MinValue.Add(timeSpan); - - DateTime maxAgeDateTime = DateTime.MinValue.AddYears(Years).AddMonths(Months).AddDays(Days); + //DateTime dateNow = DateTime.Now; + //TimeSpan timeSpan = dateNow.Subtract(dateOfBirth); + //DateTime ageDateTime = DateTime.MinValue.Add(timeSpan); - if (Years > 0 || Months > 0 || Days > 0) + if (dateOfBirth > MaxAgeDateTime) { - if (ageDateTime > maxAgeDateTime) - { - return new ValidationResult(ErrorMessage); - } + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } diff --git a/src/TanvirArjel.CustomValidation/Attributes/MaxDateAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/MaxDateAttribute.cs index e089ac2..4667fe6 100644 --- a/src/TanvirArjel.CustomValidation/Attributes/MaxDateAttribute.cs +++ b/src/TanvirArjel.CustomValidation/Attributes/MaxDateAttribute.cs @@ -23,9 +23,9 @@ public sealed class MaxDateAttribute : ValidationAttribute /// A calendar month number. The value should be in 1 to 12. /// A calendar date. The value should be in 1 to 31. public MaxDateAttribute(int year, int month, int day) + : base("The {0} cannot be larger than {1}.") { MaxDate = new DateTime(year, month, day); - ErrorMessage = ErrorMessage ?? "The {0} cannot be larger than {1}."; } /// @@ -34,6 +34,7 @@ public MaxDateAttribute(int year, int month, int day) /// The representation of the minDate value. /// Format of the supplied string minDate value. public MaxDateAttribute(string maxDate, string format) + : base("The {0} cannot be larger than {1}.") { MaxDate = DateTime.ParseExact(maxDate, format, CultureInfo.InvariantCulture); } @@ -43,10 +44,15 @@ public MaxDateAttribute(string maxDate, string format) /// public DateTime MaxDate { get; } - ////public override string FormatErrorMessage(string displayName) - ////{ - //// return string.Format(CultureInfo.InvariantCulture, ErrorMessage, displayName, MaxDate.ToString("dd-MMM-yyyy", CultureInfo.InvariantCulture)); - ////} + /// + /// Gets the format of the that will be used in + /// + public string ErrorMessageMaxDateFormat { get; set; } = "dd-MM-yyyy"; + + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MaxDate.ToString(ErrorMessageMaxDateFormat, CultureInfo.CurrentCulture)); + } /// /// To check whether the input date violates the specified max date constraint. @@ -83,8 +89,7 @@ protected override ValidationResult IsValid(object value, ValidationContext vali if (inputDate > MaxDate) { - string errorMessage = FormatErrorMessage(validationContext.DisplayName); - return new ValidationResult(errorMessage); + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } diff --git a/src/TanvirArjel.CustomValidation/Attributes/MinAgeAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/MinAgeAttribute.cs index ff1618a..e0d6673 100644 --- a/src/TanvirArjel.CustomValidation/Attributes/MinAgeAttribute.cs +++ b/src/TanvirArjel.CustomValidation/Attributes/MinAgeAttribute.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Globalization; namespace TanvirArjel.CustomValidation.Attributes { @@ -21,33 +22,25 @@ public sealed class MinAgeAttribute : ValidationAttribute /// A positive value ranging from 0 to 11. /// A positive value ranging from 0 to 31. public MinAgeAttribute(int years, int months, int days) + : base("The {0} cannot be larger than {1}.") { - Years = years < 0 ? 0 : years; - Months = months < 0 ? 0 : months; - Days = days < 0 ? 0 : days; - - ErrorMessage = ErrorMessage ?? $"The Minimum age should be {(Years > 0 ? years + " years" : string.Empty)} {(Months > 0 ? months + " months" : string.Empty)} {(Days > 0 ? days + " days." : string.Empty)}"; + MinAgeDateTime = DateTime.Today.AddYears(years < 0 ? 0 : -years).AddMonths(months < 0 ? 0 : -months).AddDays(days < 0 ? 0 : -days); } /// - /// Get the year value of the allowed min age. - /// - public int Years { get; } - - /// - /// Get the month value of the allowed min age. + /// Get the allowed max date value. /// - public int Months { get; } + public DateTime MinAgeDateTime { get; } /// - /// Get the day value of the allowed min age. + /// Gets the format of the that will be used in /// - public int Days { get; } + public string ErrorMessageMinAgeDateTimeFormat { get; set; } = "dd-MM-yyyy"; - ////public override string FormatErrorMessage(string displayName) - ////{ - //// return string.Format(CultureInfo.InvariantCulture, ErrorMessage, Years, Months, Days); - ////} + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MinAgeDateTime.ToString(ErrorMessageMinAgeDateTimeFormat, CultureInfo.CurrentCulture)); + } /// /// To check whether the input date violates the specified min age constraint. @@ -83,18 +76,13 @@ protected override ValidationResult IsValid(object value, ValidationContext vali return new ValidationResult($"{validationContext.DisplayName} can not be greater than today's date."); } - DateTime dateNow = DateTime.Now; - TimeSpan timeSpan = dateNow.Subtract(dateOfBirth); - DateTime ageDateTime = DateTime.MinValue.Add(timeSpan); - - DateTime minAgeDateTime = DateTime.MinValue.AddYears(Years).AddMonths(Months).AddDays(Days); + //DateTime dateNow = DateTime.Now; + //TimeSpan timeSpan = dateNow.Subtract(dateOfBirth); + //DateTime ageDateTime = DateTime.MinValue.Add(timeSpan); - if (Years > 0 || Months > 0 || Days > 0) + if (MinAgeDateTime > dateOfBirth) { - if (minAgeDateTime > ageDateTime) - { - return new ValidationResult(ErrorMessage); - } + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } diff --git a/src/TanvirArjel.CustomValidation/Attributes/MinDateAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/MinDateAttribute.cs index b657eaa..c5a7a3d 100644 --- a/src/TanvirArjel.CustomValidation/Attributes/MinDateAttribute.cs +++ b/src/TanvirArjel.CustomValidation/Attributes/MinDateAttribute.cs @@ -23,9 +23,9 @@ public sealed class MinDateAttribute : ValidationAttribute /// A calendar month number. The value should be in 1 to 12. /// A calendar date. The value should be in 1 to 31. public MinDateAttribute(int year, int month, int day) + : base("The {0} cannot be smaller than {1}.") { MinDate = new DateTime(year, month, day); - ErrorMessage = ErrorMessage ?? "The {0} cannot be smaller than {1}."; } /// @@ -34,6 +34,7 @@ public MinDateAttribute(int year, int month, int day) /// The representation of the minDate value. /// Format of the supplied string minDate value. public MinDateAttribute(string minDate, string format) + : base("The {0} cannot be smaller than {1}.") { MinDate = DateTime.ParseExact(minDate, format, CultureInfo.InvariantCulture); } @@ -43,10 +44,15 @@ public MinDateAttribute(string minDate, string format) /// public DateTime MinDate { get; } - ////public override string FormatErrorMessage(string displayName) - ////{ - //// return string.Format(CultureInfo.InvariantCulture, ErrorMessage, displayName, MinDate.ToString("dd-MMM-yyyy", CultureInfo.InvariantCulture)); - ////} + /// + /// Gets the format of the that will be used in + /// + public string ErrorMessageMinDateFormat { get; set; } = "dd-MM-yyyy"; + + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MinDate.ToString(ErrorMessageMinDateFormat, CultureInfo.CurrentCulture)); + } /// /// To check whether the input date violates the specified min date constraint. @@ -83,8 +89,7 @@ protected override ValidationResult IsValid(object value, ValidationContext vali if (inputDate < MinDate) { - string errorMessage = FormatErrorMessage(validationContext.DisplayName); - return new ValidationResult(errorMessage); + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } diff --git a/src/TanvirArjel.CustomValidation/Attributes/RequiredIfAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/RequiredIfAttribute.cs index 399a9fa..a4aef44 100644 --- a/src/TanvirArjel.CustomValidation/Attributes/RequiredIfAttribute.cs +++ b/src/TanvirArjel.CustomValidation/Attributes/RequiredIfAttribute.cs @@ -23,11 +23,11 @@ public sealed class RequiredIfAttribute : ValidationAttribute /// The type of the other property supplied value. /// The value against comparison will be done. public RequiredIfAttribute(string otherPropertyName, ComparisonType comparisonType, object otherPropertyValue) + : base("The {0} field is required.") { OtherPropertyName = otherPropertyName; ComparisonType = comparisonType; OtherPropertyValue = otherPropertyValue; - ErrorMessage = ErrorMessage ?? "The {0} field is required."; } /// @@ -191,7 +191,7 @@ private ValidationResult IsRequired(object value, ValidationContext validationCo { if (value == null || string.IsNullOrWhiteSpace(value.ToString())) { - string formattedErrorMessage = string.Format(CultureInfo.InvariantCulture, ErrorMessage, validationContext.DisplayName); + string formattedErrorMessage = string.Format(CultureInfo.CurrentCulture, ErrorMessageString, validationContext.DisplayName); return new ValidationResult(formattedErrorMessage); } diff --git a/src/TanvirArjel.CustomValidation/Attributes/TextEditorAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/TextEditorAttribute.cs new file mode 100644 index 0000000..1ca1fd2 --- /dev/null +++ b/src/TanvirArjel.CustomValidation/Attributes/TextEditorAttribute.cs @@ -0,0 +1,106 @@ +// +// Copyright (c) TanvirArjel. All rights reserved. +// + +using System; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace TanvirArjel.CustomValidation.Attributes +{ + /// + /// This is used to make online text editor field, like TinyMCE, required along with an option for setting minimum length. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] + public sealed class TextEditorAttribute : ValidationAttribute + { + /// + /// Initializes a new instance of the class. + /// + public TextEditorAttribute() + { + ErrorMessage = ErrorMessage ?? "{0} field is required."; + MinLengthErrorMessage = MinLengthErrorMessage ?? "{0} should be at least {1} characters long."; + MaxLengthErrorMessage = MaxLengthErrorMessage ?? "{0} cannot be more than {1} characters long."; + } + + /// + /// Get and set the minimum length of the text editor field. The value should be a positive number. + /// + public int MinLength { get; set; } + + /// + /// Get and set the maximum length of the text editor field. The value should be a positive number. + /// + public int MaxLength { get; set; } + + /// + /// Get and set the error message if the MinLength validation fails. + /// + public string MinLengthErrorMessage { get; set; } + + /// + /// Get and set the error message if the MaxLength validaton fails. + /// + public string MaxLengthErrorMessage { get; set; } + + /// + /// To check whether the input date violates the required constraint. + /// + /// Type of . + /// The request validation context. + /// Returns . + /// Thrown if is null. + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + PropertyInfo propertyInfo = validationContext.ObjectType.GetProperty(validationContext.MemberName); + + if (propertyInfo == null) + { + throw new ArgumentException($"The object does not contain any property with name '{validationContext.MemberName}'"); + } + + if (propertyInfo.PropertyType != typeof(string)) + { + throw new ArgumentException($"The {nameof(TextEditorAttribute)} is not valid on property type {propertyInfo.PropertyType}" + + $"This Attribute is only valid on {typeof(string)}"); + } + + string requiredErrorMessage = string.Format(CultureInfo.InvariantCulture, ErrorMessage, validationContext.DisplayName); + + if (value == null) + { + return new ValidationResult(requiredErrorMessage); + } + + string inputValue = value.ToString(); + string inputValueWithoutHtml = Regex.Replace(inputValue, "<.*?>| ", string.Empty); + + if (string.IsNullOrWhiteSpace(inputValueWithoutHtml)) + { + return new ValidationResult(requiredErrorMessage); + } + + if (MinLength > 0 && inputValueWithoutHtml.Length < MinLength) + { + string minLengthErrorMessage = string.Format(CultureInfo.InvariantCulture, MinLengthErrorMessage, validationContext.DisplayName, MinLength); + return new ValidationResult(minLengthErrorMessage); + } + + if (MaxLength > 0 && inputValueWithoutHtml.Length > MaxLength) + { + string maxLengthErrorMessage = string.Format(CultureInfo.InvariantCulture, MaxLengthErrorMessage, validationContext.DisplayName, MaxLength); + return new ValidationResult(maxLengthErrorMessage); + } + + return ValidationResult.Success; + } + } +} diff --git a/src/TanvirArjel.CustomValidation/Attributes/TextEditorMaxLengthAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/TextEditorMaxLengthAttribute.cs new file mode 100644 index 0000000..05e0b51 --- /dev/null +++ b/src/TanvirArjel.CustomValidation/Attributes/TextEditorMaxLengthAttribute.cs @@ -0,0 +1,80 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace TanvirArjel.CustomValidation.Attributes +{ + /// + /// This is used to validate the WYSIWYG editor text max length + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] + public sealed class TextEditorMaxLengthAttribute : ValidationAttribute + { + /// + /// Initializes a new instance of the class. + /// + public TextEditorMaxLengthAttribute() + : base("{0} cannot be more than {1} characters long.") + { } + + /// + /// Get and set the maximum length of the text editor field. The value should be a positive number. + /// + public int MaxLength { get; } + + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MaxLength); + } + + /// + /// To check whether the input text violates the required constraint. + /// + /// Type of . + /// The request validation context. + /// Returns . + /// Thrown if is null. + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + PropertyInfo propertyInfo = validationContext.ObjectType.GetProperty(validationContext.MemberName); + + if (propertyInfo == null) + { + throw new ArgumentException($"The object does not contain any property with name '{validationContext.MemberName}'"); + } + + if (propertyInfo.PropertyType != typeof(string)) + { + throw new ArgumentException($"The {nameof(TextEditorMaxLengthAttribute)} is not valid on property type {propertyInfo.PropertyType}" + + $"This Attribute is only valid on {typeof(string)}"); + } + + if (value == null) + { + return ValidationResult.Success; + } + + string inputValue = value.ToString(); + string inputValueWithoutHtml = Regex.Replace(inputValue, "<.*?>| ", string.Empty); + + if (string.IsNullOrWhiteSpace(inputValueWithoutHtml)) + { + return ValidationResult.Success; + } + + if (MaxLength > 0 && inputValueWithoutHtml.Length > MaxLength) + { + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); + } + + return ValidationResult.Success; + } + } +} diff --git a/src/TanvirArjel.CustomValidation/Attributes/TextEditorMinLengthAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/TextEditorMinLengthAttribute.cs new file mode 100644 index 0000000..1d7d3bf --- /dev/null +++ b/src/TanvirArjel.CustomValidation/Attributes/TextEditorMinLengthAttribute.cs @@ -0,0 +1,82 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace TanvirArjel.CustomValidation.Attributes +{ + /// + /// This is used to validate the WYSIWYG editor text min length + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] + public sealed class TextEditorMinLengthAttribute : ValidationAttribute + { + /// + /// Initializes a new instance of the class. + /// + public TextEditorMinLengthAttribute() + : base("{0} should be at least {1} characters long.") + { } + + /// + /// Get and set the minimum length of the text editor field. The value should be a positive number. + /// + public int MinLength { get; } + + public override string FormatErrorMessage(string name) + { + return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MinLength); + } + + /// + /// To check whether the input text violates the required constraint. + /// + /// Type of . + /// The request validation context. + /// Returns . + /// Thrown if is null. + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + PropertyInfo propertyInfo = validationContext.ObjectType.GetProperty(validationContext.MemberName); + + if (propertyInfo == null) + { + throw new ArgumentException($"The object does not contain any property with name '{validationContext.MemberName}'"); + } + + if (propertyInfo.PropertyType != typeof(string)) + { + throw new ArgumentException($"The {nameof(TextEditorMinLengthAttribute)} is not valid on property type {propertyInfo.PropertyType}" + + $"This Attribute is only valid on {typeof(string)}"); + } + + string requiredErrorMessage = string.Format(CultureInfo.CurrentCulture, ErrorMessageString, validationContext.DisplayName); + + if (value == null) + { + return MinLength > 0 ? new ValidationResult(FormatErrorMessage(validationContext.DisplayName)) : ValidationResult.Success; + } + + string inputValue = value.ToString(); + string inputValueWithoutHtml = Regex.Replace(inputValue, "<.*?>| ", string.Empty); + + if (string.IsNullOrWhiteSpace(inputValueWithoutHtml)) + { + return new ValidationResult(requiredErrorMessage); + } + + if (MinLength > 0 && inputValueWithoutHtml.Length < MinLength) + { + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); + } + + return ValidationResult.Success; + } + } +} diff --git a/src/TanvirArjel.CustomValidation/Attributes/TextEditorRequiredAttribute.cs b/src/TanvirArjel.CustomValidation/Attributes/TextEditorRequiredAttribute.cs index 1d6f5cb..e5118ec 100644 --- a/src/TanvirArjel.CustomValidation/Attributes/TextEditorRequiredAttribute.cs +++ b/src/TanvirArjel.CustomValidation/Attributes/TextEditorRequiredAttribute.cs @@ -1,50 +1,23 @@ -// -// Copyright (c) TanvirArjel. All rights reserved. -// - -using System; +using System; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Reflection; using System.Text.RegularExpressions; namespace TanvirArjel.CustomValidation.Attributes { /// - /// This is used to make online text editor field, like TinyMCE, required along with an option for setting minimum length. + /// This is used to make WYSIWYG editor text value required /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] + [AttributeUsage( + AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, + AllowMultiple = false)] public sealed class TextEditorRequiredAttribute : ValidationAttribute { /// /// Initializes a new instance of the class. /// public TextEditorRequiredAttribute() - { - ErrorMessage = ErrorMessage ?? "{0} field is required."; - MinLengthErrorMessage = MinLengthErrorMessage ?? "{0} should be at least {1} characters long."; - MaxLengthErrorMessage = MaxLengthErrorMessage ?? "{0} cannot be more than {1} characters long."; - } - - /// - /// Get and set the minimum length of the text editor field. The value should be a positive number. - /// - public int MinLength { get; set; } - - /// - /// Get and set the maximum length of the text editor field. The value should be a positive number. - /// - public int MaxLength { get; set; } - - /// - /// Get and set the error message if the MinLength validation fails. - /// - public string MinLengthErrorMessage { get; set; } - - /// - /// Get and set the error message if the MaxLength validaton fails. - /// - public string MaxLengthErrorMessage { get; set; } + : base("{0} field is required.") { } /// /// To check whether the input date violates the required constraint. @@ -64,20 +37,20 @@ protected override ValidationResult IsValid(object value, ValidationContext vali if (propertyInfo == null) { - throw new ArgumentException($"The object does not contain any property with name '{validationContext.MemberName}'"); + throw new ArgumentException( + $"The object does not contain any property with name '{validationContext.MemberName}'"); } if (propertyInfo.PropertyType != typeof(string)) { - throw new ArgumentException($"The {nameof(TextEditorRequiredAttribute)} is not valid on property type {propertyInfo.PropertyType}" + - $"This Attribute is only valid on {typeof(string)}"); + throw new ArgumentException( + $"The {nameof(TextEditorRequiredAttribute)} is not valid on property type {propertyInfo.PropertyType}" + + $"This Attribute is only valid on {typeof(string)}"); } - string requiredErrorMessage = string.Format(CultureInfo.InvariantCulture, ErrorMessage, validationContext.DisplayName); - if (value == null) { - return new ValidationResult(requiredErrorMessage); + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } string inputValue = value.ToString(); @@ -85,19 +58,7 @@ protected override ValidationResult IsValid(object value, ValidationContext vali if (string.IsNullOrWhiteSpace(inputValueWithoutHtml)) { - return new ValidationResult(requiredErrorMessage); - } - - if (MinLength > 0 && inputValueWithoutHtml.Length < MinLength) - { - string minLengthErrorMessage = string.Format(CultureInfo.InvariantCulture, MinLengthErrorMessage, validationContext.DisplayName, MinLength); - return new ValidationResult(minLengthErrorMessage); - } - - if (MaxLength > 0 && inputValueWithoutHtml.Length > MaxLength) - { - string maxLengthErrorMessage = string.Format(CultureInfo.InvariantCulture, MaxLengthErrorMessage, validationContext.DisplayName, MaxLength); - return new ValidationResult(maxLengthErrorMessage); + return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } return ValidationResult.Success;