Edit
I found that the problem is that View Components are unable to have an #section (see ViewComponent and #Section #2910 ) so adding custom client-side validation using the unobtrusive library seems imposible (or very complex). Moreover, the inability of including the required javascript into a View Component makes me regret of following this approach to modularize my app in the first place...
I am learning to make custom validation attributes with client-side support. I was able to implement a custom validator for a string property and it works pretty well, but when I tried to make one for input file it doesn't work (i.e. when I select a file in my computer, the application doesn't display the validation messages. The server-side validation works. Here is some code that shows my implementation.
The class of the model
public class UploadPanelModel
{
public int? ID { get; set; }
public string Title { get; set; }
public string Description { get; set; } //Raw HTML with the panel description
[FileType(type: "application/pdf")]
[FileSize(maxSize: 5000000)]
public IFormFile File { get; set; }
public byte[] FileBytes { get; set; }
public ModalModel Modal { get; set; } //Only used if the Upload panel uses a modal.
The validator
public class FileSizeAttribute : ValidationAttribute, IClientModelValidator
{
private long _MaxSize { get; set; }
public FileSizeAttribute (long maxSize)
{
_MaxSize = maxSize;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
UploadPanelModel panel = (UploadPanelModel)validationContext.ObjectInstance;
return (panel.File==null || panel.File.Length <= _MaxSize) ? ValidationResult.Success : new ValidationResult(GetFileSizeErrorMessage(_MaxSize));
}
private string GetFileSizeErrorMessage(long maxSize)
{
double megabytes = maxSize / 1000000.0;
return $"El archivo debe pesar menos de {megabytes}MB";
}
public void AddValidation(ClientModelValidationContext context)
{
if(context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-filesize", GetFileSizeErrorMessage(_MaxSize));
var maxSize = _MaxSize.ToString();
MergeAttribute(context.Attributes, "data-val-filesize-maxsize", maxSize);
}
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
The javascript in the Razor View
#section Scripts{
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
$.validator.addMethod('filesize',
function (value, element, params) {
var size = $((params[0]).val()).size(),
maxSize = params[1];
if (size < maxSize) {
return false;
}
else {
return false;
}
}
);
$.validator.unobtrusive.adapters.add('filesize',
['maxSize'],
function (options) {
var element = $(options.form).find('input#File')[0];
options.rules['filesize'] = [element, options.params['maxSize']];
options.messages['filesize'] = options.message;
}
);
</script>
I always return false in the javascript method to force the application to show the validation error regardless the chosen file, but it still doesn't work.
Your addMethod() function will be throwing an error because params[0] is not a jQuery object and has no .val() (you also have the $ in the wrong place). You would need to use
var size = params[0].files[0].size;
However I suggest you write you scripts as
$.validator.unobtrusive.adapters.add('filesize', ['maxsize'], function (options) {
options.rules['filesize'] = { maxsize: options.params.maxsize };
if (options.message) {
options.messages['filesize'] = options.message;
}
});
$.validator.addMethod("filesize", function (value, element, param) {
if (value === "") {
return true;
}
var maxsize = parseInt(param.maxsize);
if (element.files != undefined && element.files[0] != undefined && element.files[0].size != undefined) {
var filesize = parseInt(element.files[0].size);
return filesize <= maxsize ;
}
return true; // in case browser does not support HTML5 file API
});
Related
I try to add a custom attribute to validate required field and trim value for white space.
So here is my custom attribute :
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class CustomRequired : ValidationAttribute, IClientModelValidator
{
public CustomRequired()
{
ErrorMessage = new ResourceManager(typeof(ErrorResource)).GetString("All_Required");
}
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-customrequired", ErrorMessage);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return value.ToString().Trim().Length > 0 ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
And here how I add it (or try) :
$(document).ready(function () {
$.validator.addMethod("customrequired", function (value, element, parameters) {
return $.trim(value).length > 0;
});
$.validator.unobtrusive.adapters.addBool('customrequired');
});
And set it on property in a viewmodel :
[CustomRequired]
public string Code { get; set; }
My problem is it doesn't had any client side validation whereas the function is in the jQuery validator... The ModelState is invalid so the controller reject it but I want a client side validation.
console:
Edit :
I forgot to say I'm using kendo... See my own answer below.
I forgot to say that I'm using kendo...
My code is functional with a classic validation but not with kendo edit pop-up. :/
So here is the solution for those who have the same problem, write this in your javascript instead of add it in the $.validator :
(function ($, kendo) {
$.extend(true, kendo.ui.validator, {
rules: {
customrequired: function (input) {
if (input.is("[data-val-customrequired]")) {
return $.trim(input.val()).length > 0;
}
return true;
}
},
messages: {
customrequired: function (input) {
return input.attr("data-val-customrequired");
}
}
});
})(jQuery, kendo);
Hi All!
I'm a bit of a noob at model validation and i've been trying to validate an Articles object and an uploaded file using the IValidatableObject interface with no success.
This following class validates the Articles object just fine but I can't see how the HttpPostedFileBase is injected to allow me to validate against it. Is this even possible to achieve using this method?
The form i'm using to submit the data includes the enctype = multipart/form-data attribute so it knows its posting files.
This is the full class im trying to validate. This ones really got me stuck and any help will be very gratefully appreciated.
public class ArticlesModel : IValidatableObject
{
public Article Article { get; set; }
public IEnumerable<Category> Categories { get; set; }
public HttpPostedFileBase PostedFile { get; set; }
public ArticlesModel(){}
public ArticlesModel(Article article, IEnumerable<Category> categories)
{
this.Article = article;
this.Categories = categories;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Article.CategoryID == 0)
{
yield return new ValidationResult("Please select a category.", new[] { "Article.Category"});
}
if (Article.Title == null)
{
yield return new ValidationResult("Please enter a title.", new[] { "Article.Title" });
}
if (Article.Content == null)
{
yield return new ValidationResult("Please enter some content.", new[] { "Article.Content" });
}
if (PostedFile == null)
{
yield return new ValidationResult("Please upload a file.", new[] { "Article.ImageFile" });
}
else
{
if (PostedFile.ContentLength > 1 * 1024 * 1024)
{
yield return new ValidationResult("Please upload a file 1Mb or less.", new[] { "Article.ImageFile" });
}
//Other file checking logic here please!!
}
}
}
I had two fields some thing like phone number and mobile number. Some thing like..
[Required]
public string Phone { get; set; }
[Required]
public string Mobile{ get; set; }
But user can enter data in either one of it. One is mandatory. How to handle them i.e how to disable the required field validator for one field when user enter data in another field and viceversa. In which event i have to handle it in javascript and what are the scripts i need to add for this. Can anyone please help to find the solution...
One possibility is to write a custom validation attribute:
public class RequiredIfOtherFieldIsNullAttribute : ValidationAttribute, IClientValidatable
{
private readonly string _otherProperty;
public RequiredIfOtherFieldIsNullAttribute(string otherProperty)
{
_otherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_otherProperty);
if (property == null)
{
return new ValidationResult(string.Format(
CultureInfo.CurrentCulture,
"Unknown property {0}",
new[] { _otherProperty }
));
}
var otherPropertyValue = property.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue == null || otherPropertyValue as string == string.Empty)
{
if (value == null || value as string == string.Empty)
{
return new ValidationResult(string.Format(
CultureInfo.CurrentCulture,
FormatErrorMessage(validationContext.DisplayName),
new[] { _otherProperty }
));
}
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
rule.ValidationParameters.Add("other", _otherProperty);
yield return rule;
}
}
which you would apply to one of the properties of your view model:
public class MyViewModel
{
[RequiredIfOtherFieldIsNull("Mobile")]
public string Phone { get; set; }
public string Mobile { get; set; }
}
then you could have a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and finally a view in which you will register an adapter to wire the client side validation for this custom rule:
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
jQuery.validator.unobtrusive.adapters.add(
'requiredif', ['other'], function (options) {
var getModelPrefix = function (fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf('.') + 1);
}
var appendModelPrefix = function (value, prefix) {
if (value.indexOf('*.') === 0) {
value = value.replace('*.', prefix);
}
return value;
}
var prefix = getModelPrefix(options.element.name),
other = options.params.other,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(':input[name="' + fullOtherName + '"]')[0];
options.rules['requiredif'] = element;
if (options.message) {
options.messages['requiredif'] = options.message;
}
}
);
jQuery.validator.addMethod('requiredif', function (value, element, params) {
var otherValue = $(params).val();
if (otherValue != null && otherValue != '') {
return true;
}
return value != null && value != '';
}, '');
</script>
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.Phone)
#Html.EditorFor(x => x.Phone)
#Html.ValidationMessageFor(x => x.Phone)
</div>
<div>
#Html.LabelFor(x => x.Mobile)
#Html.EditorFor(x => x.Mobile)
#Html.ValidationMessageFor(x => x.Mobile)
</div>
<button type="submit">OK</button>
}
Pretty sick stuff for something so extremely easy as validation rule that we encounter in our everyday lives. I don't know what the designers of ASP.NET MVC have been thinking when they decided to pick a declarative approach for validation instead of imperative.
Anyway, that's why I use FluentValidation.NET instead of data annotations to perform validations on my models. Implementing such simple validation scenarios is implemented in a way that it should be - simple.
I know this question is not so hot, because it was asked relatively long time ago, nevertheless I'm going to share with a slightly different idea of solving such an issue. I decided to implement mechanism which provides conditional attributes to calculate validation results based on other properties values and relations between them, which are defined in logical expressions.
Your problem can be defined and automatically solved by the usage of following annotations:
[RequiredIf("Mobile == null",
ErrorMessage = "At least email or phone should be provided.")]
public string Phone{ get; set; }
[RequiredIf("Phone == null",
ErrorMessage = "At least email or phone should be provided.")]
public string Mobile { get; set; }
If you feel it would be useful for your purposes, more information about ExpressiveAnnotations library can be found here. Client side validation is also supported out of the box.
Since nobody else suggested it, I'm going to tell you a different way to do this that we use.
If you create a notmapped field of a custom data type (in my example, a pair of gps points), you can put the validator on that and you don't even need to use reflection to get all the values.
[NotMapped]
[DCGps]
public GPS EntryPoint
{
get
{
return new GPS(EntryPointLat, EntryPointLon);
}
}
and the class, a standard getter/setter
public class GPS
{
public decimal? lat { get; set; }
public decimal? lon { get; set; }
public GPS(decimal? lat, decimal? lon)
{
this.lat = lat;
this.lon = lon;
}
}
and now the validator:
public class DCGps : DCValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (!(value is GPS)) {
return new ValidationResult("DCGps: This annotation only works with fields with the data type GPS.");
}
//value stored in the field.
//these come through as zero or emptry string. Normalize to ""
string lonValue = ((GPS)value).lonstring == "0" ? "" : ((GPS)value).lonstring;
string latValue = ((GPS)value).latstring == "0" ? "" : ((GPS)value).latstring;
//place validation code here. You have access to both values.
//If you have a ton of values to validate, you can do them all at once this way.
}
}
I am trying to create a t4 template to help speed up my Create Form template.
Is it possible to add extra html depending if a model's property is required?
e.g.
[Required]
[Display(Name = "Contact Email Address:")]
public string ContactEmailAddress { get; set; }
Now in my tt file do something like
foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
if (!property.IsPrimaryKey && !property.IsReadOnly) {
#>
<div>
#Html.LabelFor(model => model.<#= property.Name #>)
#Html.EditorFor(model => model.<#= property.Name #>)
#Html.ValidationMessageFor(model => model.<#= property.Name #>)
if(this.Required==true){<span class="required-field"></span>}
</div>
<#
}
Or is this not possible?
1,Open this file:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates\MvcViewWithContextScaffolder\ModelPropertyFunctions.include.t4
2, Add some property to ModelProperty
class ModelProperty {
public string Name { get; set; }
public string AssociationName { get; set; }
public string ValueExpression { get; set; }
public string ModelValueExpression { get; set; }
public string ItemValueExpression { get; set; }
public EnvDTE.CodeTypeRef Type { get; set; }
public bool IsPrimaryKey { get; set; }
public bool IsForeignKey { get; set; }//
public bool IsReadOnly { get; set; }//
public bool IsRequired{ get; set;}//Here is your customer Property
public bool Scaffold { get; set; }
}
3, Add an method out under this class
bool IsRequired(EnvDTE.CodeProperty propertyType)
{
foreach (EnvDTE.CodeAttribute attribute in propertyType.Attributes)
{
if (String.Equals(attribute.FullName, "System.ComponentModel.DataAnnotations.RequiredAttribute", StringComparison.Ordinal))
{
return true;
}
}
return false;
}
4,Go to the bottom of this file to modify the method GetEligibleProperties:
List<ModelProperty> GetEligibleProperties(EnvDTE.CodeType typeInfo) {
List<ModelProperty> results = new List<ModelProperty>();
if (typeInfo != null) {
foreach (var prop in typeInfo.GetPublicMembers().OfType<EnvDTE.CodeProperty>()) {
if (prop.HasPublicGetter() && !prop.IsIndexerProperty() && IsBindableType(prop.Type)) {
string valueExpression = GetValueExpressionSuffix(prop);
results.Add(new ModelProperty {
Name = prop.Name,
AssociationName = GetAssociationName(prop),
ValueExpression = valueExpression,
ModelValueExpression = "Model." + valueExpression,
ItemValueExpression = "item." + valueExpression,
Type = prop.Type,
IsPrimaryKey = IsPrimaryKey(prop),
IsForeignKey = IsForeignKey(prop),
IsRequired=IsRequired(prop),//Here is your customer property.
IsReadOnly = !prop.HasPublicSetter(),
Scaffold = Scaffold(prop)
});
}
}
}
return results;
}
5, go to the file
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates\MvcViewWithContextScaffolder\Edit.cs.t4
Add the following check before your ValidationMessageFor
#Html.ValidationMessageFor(model => model.<#= property.Name #>):
Codes like this:
<#
if (property.IsRequired) {
#>
*<!--your html code-->
<#
}
#>
This would be possible but would take more work than what you have here. You could add a Required property to ModelProperty and then set it by looking for the required attribute on the property when getting the model properties. You can take a look at the code that determines whether or not a property is a primary key in the .tt templates for an example.
If someone still searching for a solution...
I'm using the MetadataType attibute to define the property attributes like Required or DisplayName in a seperate class.
Sample:
[MetadataType(typeof(personMetaData))]
public partial class Person
{
}
public class personMetaData
{
[DisplayName("Surname")]
[Required]
public object Name { get; set; }
}
If you want to access these attributes in the t4-template you have to extend the ModelProperty class and creator in the template-file.
Put the following code at the bottom of your template (e.g. List.tt). You have to replace the existing code.
<#+
// Describes the information about a property on the model
public class ModelProperty
{
public string Name { get; set; }
public string ValueExpression { get; set; }
public Type UnderlyingType { get; set; }
public bool IsPrimaryKey { get; set; }
public bool IsReadOnly { get; set; }
public string DisplayName { get; set; }
}
// Change this list to include any non-primitive types you think should be eligible for display/edit
private static Type[] bindableNonPrimitiveTypes = new[]
{
typeof (string),
typeof (decimal),
typeof (Guid),
typeof (DateTime),
typeof (DateTimeOffset),
typeof (TimeSpan),
};
// Call this to get the list of properties in the model. Change this to modify or add your
// own default formatting for display values.
public List<ModelProperty> GetModelProperties(Type type)
{
List<ModelProperty> results = GetEligibleProperties(type);
foreach (ModelProperty prop in results)
{
if (prop.UnderlyingType == typeof (double) || prop.UnderlyingType == typeof (decimal))
{
prop.ValueExpression = "String.Format(\"{0:F}\", " + prop.ValueExpression + ")";
}
else if (prop.UnderlyingType == typeof (DateTime))
{
prop.ValueExpression = "String.Format(\"{0:g}\", " + prop.ValueExpression + ")";
}
}
return results;
}
// Call this to determine if the property represents a primary key. Change the
// code to change the definition of primary key.
private bool IsPrimaryKey(PropertyInfo property)
{
if (string.Equals(property.Name, "id", StringComparison.OrdinalIgnoreCase))
{
// EF Code First convention
return true;
}
if (string.Equals(property.Name, property.DeclaringType.Name + "id", StringComparison.OrdinalIgnoreCase))
{
// EF Code First convention
return true;
}
foreach (object attribute in property.GetCustomAttributes(true))
{
if (attribute is KeyAttribute)
{
// WCF RIA Services and EF Code First explicit
return true;
}
var edmScalar = attribute as EdmScalarPropertyAttribute;
if (edmScalar != null && edmScalar.EntityKeyProperty)
{
// EF traditional
return true;
}
/* var column = attribute as ColumnAttribute;
if (column != null && column.IsPrimaryKey)
{
// LINQ to SQL
return true;
}*/
}
return false;
}
// This will return the primary key property name, if and only if there is exactly
// one primary key. Returns null if there is no PK, or the PK is composite.
private string GetPrimaryKeyName(Type type)
{
IEnumerable<string> pkNames = GetPrimaryKeyNames(type);
return pkNames.Count() == 1 ? pkNames.First() : null;
}
// This will return all the primary key names. Will return an empty list if there are none.
private IEnumerable<string> GetPrimaryKeyNames(Type type)
{
return GetEligibleProperties(type).Where(mp => mp.IsPrimaryKey).Select(mp => mp.Name);
}
// Helper
private List<ModelProperty> GetEligibleProperties(Type type)
{
List<ModelProperty> results = new List<ModelProperty>();
foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
if (prop.GetGetMethod() != null && prop.GetIndexParameters().Length == 0 &&
IsBindableType(underlyingType))
{
var displayName = prop.Name;
// Search in Metadata
var metadata = type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray().FirstOrDefault();
if (metadata != null)
{
var metaPropery = metadata.MetadataClassType.GetProperty(prop.Name);
if (metaPropery != null)
{
displayName = ((DisplayNameAttribute)metaPropery.GetCustomAttributes(typeof (DisplayNameAttribute), true).First()).DisplayName;
}
}
results.Add(new ModelProperty
{
Name = prop.Name,
ValueExpression = "Model." + prop.Name,
UnderlyingType = underlyingType,
IsPrimaryKey = IsPrimaryKey(prop),
IsReadOnly = prop.GetSetMethod() == null,
DisplayName = displayName
});
}
}
return results;
}
// Helper
private bool IsBindableType(Type type)
{
return type.IsPrimitive || bindableNonPrimitiveTypes.Contains(type);
}
#>
Now you can access these attributes like this:
<#
List<ModelProperty> properties = GetModelProperties(mvcHost.ViewDataType);
foreach (ModelProperty property in properties) {
if (!property.IsPrimaryKey) {
#>
<th>
<#= property.DisplayName #><#= property.AllowEmptyStrings ? "*" : "" #>
</th>
<#
}
}
#>
T4 templates have changed with MVC5. To accomplish this in MVC5, I've written a tutorial here: https://johniekarr.wordpress.com/2015/05/16/mvc-5-t4-templates-and-view-model-property-attributes/
I am writing a custom validation attribute
It does conditional validation between two fields
When I create my rule, one of the things that I could not solve is how to pass javascript code through ValidationParameters
Usually, I just do
ValidationParameters["Param1"] = "{ required :function(element) { return $("#age").val() < 13;) }"
However, the MicrosoftMvcJQueryValidation.js routines trnasforms this to
Param1 = "{ required :function(element) { return $("#age").val() < 13;) }"
I could use Param1.eval() in Javascript. This will evaluates and executes the code but I just want to evalute the code and execute it later
JSON parser does not parse string contening Javascript code
So I am asking here for any idea
Not sure how you would inject javascript as you describe, but you may want to consider using the custom validation pattern for ASP.NET MVC 2.
Important pieces are the ValidationAttribute, DataAnnotationsModelValidator, registering the validator in Application_Start with DataAnnotationsModelValidatorProvider.RegisterAdapter, and the client side Sys.Mvc.ValidatorRegistry.validators function collection to register your client side validation code.
Here's the example code from my post.
[RegularExpression("[\\S]{6,}", ErrorMessage = "Must be at least 6 characters.")]
public string Password { get; set; }
[StringLength(128, ErrorMessage = "Must be under 128 characters.")]
[MinStringLength(3, ErrorMessage = "Must be at least 3 characters.")]
public string PasswordAnswer { get; set; }
public class MinStringLengthAttribute : ValidationAttribute
{
public int MinLength { get; set; }
public MinStringLengthAttribute(int minLength)
{
MinLength = minLength;
}
public override bool IsValid(object value)
{
if (null == value) return true; //not a required validator
var len = value.ToString().Length;
if (len < MinLength)
return false;
else
return true;
}
}
public class MinStringLengthValidator : DataAnnotationsModelValidator<MinStringLengthAttribute>
{
int minLength;
string message;
public MinStringLengthValidator(ModelMetadata metadata, ControllerContext context, MinStringLengthAttribute attribute)
: base(metadata, context, attribute)
{
minLength = attribute.MinLength;
message = attribute.ErrorMessage;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ErrorMessage = message,
ValidationType = "minlen"
};
rule.ValidationParameters.Add("min", minLength);
return new[] { rule };
}
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MinStringLengthAttribute), typeof(MinStringLengthValidator));
}
Sys.Mvc.ValidatorRegistry.validators["minlen"] = function(rule) {
//initialization
var minLen = rule.ValidationParameters["min"];
//return validator function
return function(value, context) {
if (value.length < minLen)
return rule.ErrorMessage;
else
return true; /* success */
};
};