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);
Related
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
});
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 created a custom validation with following sample JS code:
(function ($) {
$.validator.unobtrusive.adapters.add('myrule', ['minvalueproperty', 'maxvalueproperty'],
function (options) {
options.rules['myrule'] = options.params;
options.messages['currencyrule'] = options.message;
}
);
$.validator.addMethod('myrule', function (value, element, params) {
return false;
}
} (jQuery));
Server side validator code is
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class MyValidationAttribute : ValidationAttribute, IClientValidatable
{
public CurrencyValidationAttribute(string minPropertyName, string maxPropertyName)
: base (DefaultErrorMessage)
{
this.minPropertyName = minPropertyName;
this.maxPropertyName = maxPropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return new ValidationResult(this.ErrorMessage);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule
{
ValidationType = "myrule",
ErrorMessage = this.ErrorMessage,
};
clientValidationRule.ValidationParameters["minvalueproperty"] = minPropertyName;
clientValidationRule.ValidationParameters["maxvalueproperty"] = maxPropertyName;
yield return clientValidationRule;
}
}
My model class is decorated with my custom validation attribute
[MyValidation("MinValue", "MaxValue",
ErrorMessage = "Wrong value, must be between {0} and {1}")]
public string Money { get; set; }
It appears to work for most part other than error message displaying. In my model property I have used a string with place holders to be replaced, but there is no actual code yet.
When I run the page, I entered some invalid value to the text box and forced client side validation to return false, the funny thing is that the error message I saw was
Wrong value, must be between [object Object] and {1}
Somehow {0} was replaced automatically. Any clue what is the reason the error message got replaced?
i have defined a route culture/Controller/action/id... my controller contains following action..
[OutputCache(Duration=60*10)]
public ActionResult Index()
{*/do magic here/*}
is it possible to Cache contents based on Culture?
The localization complete guide presents an example of how to achieve this using the VaryByCustom parameter. In global.asax you would override the GetVaryByCustomString method:
public override string GetVaryByCustomString(HttpContext context, string value)
{
if (value == "lang")
{
return Thread.CurrentThread.CurrentUICulture.Name;
}
return base.GetVaryByCustomString(context, value);
}
and then:
[OutputCache(Duration = 60 * 10, VaryByParam = "none", VaryByCustom = "lang")]
public ActionResult Index()
{
/* do magic here */
...
}
Or if you want to rely solely on the culture route data parameter you could do this:
public override string GetVaryByCustomString(HttpContext context, string value)
{
if (value == "lang")
{
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
var culture = (string)routeData.Values["culture"];
if (!string.IsNullOrEmpty(culture))
{
return culture;
}
}
return base.GetVaryByCustomString(context, value);
}
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 */
};
};