I want to set the data annotation (display) of a field based on a variable or function:
public class InputModel
{
[Required]
[StringLength(100, ErrorMessage = VARIABLE or FUNCTION())]
[Display(Name = "Password - must use at least 12 characters")]
[DataType(DataType.Password)]
public string Password { get; set; }
}
How do you set data annotation programmatically?
InputModel.DataAnnotation.Display = "Foo";
How would you set the data annotation in the model to a variable or function?
You may derive a class (e.g. MyStringLenghtAttribute) from StringLengthAttribute and override the IsValid.
You must extend DataAnnotationsModelValidatorProvider and override GetValidators method.
DataAnnotationsModelValidatorProvider loops through all the validation attributes using reflection to validate.
You can get access to all the attributes in the GetValidators methods and make changes to the validation attributes.
However you must register your custom DataAnnotationsModelValidatorProvider class in the application start like below.
protected void Application_Start()
{
ModelValidatorProviders.Providers.Add(new CustomMetadataValidationProvider());
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
see below discussion for more details.
DataAnnotations dynamically attaching attributes
StringLength is an ValidationAttribute which does not accept Variable or Function as constructor parameter.
If you want to custom StringLength error message, you could follow steps below:
Custom ValidationAttribute
public class StringError : StringLengthAttribute
{
private ErrorProvider _error;
private string _key;
public StringError(int maxlength, string key):base(maxlength)
{
_key = key;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
_error = (ErrorProvider)validationContext.GetService(typeof(ErrorProvider));
return base.IsValid(value, validationContext);
}
public override string FormatErrorMessage(string name)
{
if (_error != null)
{
return _error.Error(_key);
}
return base.FormatErrorMessage(name);
}
}
Define ErrorProvider to set and get ErrorMessage
public class ErrorProvider
{
private Dictionary<string, string> _errorDic = new Dictionary<string, string>();
public void AddOrUpdateError(string key, string value)
{
_errorDic[key] = value;
//_errorDic.TryAdd(key,value);
}
public string Error(string key)
{
string value = null;
_errorDic.TryGetValue(key, out value);
return value;
}
}
Resigter ErrorProvider
services.AddSingleton<ErrorProvider>();
Set the ErrorMessage value
[HttpPost("SetKey")]
public IActionResult SetKey([FromForm]string key,[FromForm]string value)
{
_errorProvider.AddOrUpdateError(key, value);
return Ok();
}
[HttpPost("CustomAttribute")]
public IActionResult CustomAttribute(InputModel input)
{
return Ok();
}
InputModel
public class InputModel
{
[StringError(3, "Password")]
public string Password { get; set; }
}
Related
How to create a custom validation attribute that allows only specified integers. adding a custom error message.
Create a class and inherit from ValidationAttribute class.
Follow the example code below
public class IntegersAllowedAttribute : ValidationAttribute
{
private readonly int[] _values;
public IntegersAllowedAttribute(params int[] values)
{
_values = values;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
int valuesToValidate = (int)value;
if (!_values.Contains(valuesToValidate))
{
string errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
return ValidationResult.Success;
}
}
Then place attribute on the property
public class Example
{
[IntValuesAllowed(1, 2, 3, ErrorMessage = "error message goes here")]
public int Values { get; set; }
}
Based on the following link: Multi Language - Data Annotations
Make a series of classes to translate the texts of the Data Annotation.
Everything works fine on the server side, but client side validation does not work.
If i use: [System.ComponentModel.DataAnnotations.Required]
public string Name { get; set; }
Validation on the client side works correctly, but if I use:
[Infrastructure.Required]//My custom class
public string Name { get; set; }
It works only on the server side.
This is the class that I am currently using:
namespace project.Infrastructure
{
public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
{
private string _displayName;
public RequiredAttribute()
{
ErrorMessageResourceName = "Validation_Required";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
_displayName = validationContext.DisplayName;
return base.IsValid(value, validationContext);
}
public override string FormatErrorMessage(string name)
{
var msg = WebsiteTranslations.GetTranslationErrorMessage(Settings.LanguageId, "Required", WebsiteTranslations.GetTranslation(name, 1, Settings.LanguageId));
return string.Format(msg, _displayName);
}
public System.Collections.IEnumerable GetClientValidationRules(System.Web.Mvc.ModelMetadata metadata, ControllerContext context)
{
return new[] { new ModelClientValidationRequiredRule((ErrorMessage)) };
}
}
}
I get the answer from this post: validation-type-names-in-unobtrusive
The GetClientValidationRules Method is like this:
public IEnumerable GetClientValidationRules(System.Web.Mvc.ModelMetadata metadata, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(ErrorMessage),
ValidationType = "required"
};
yield return new[] { clientValidationRule };
}
And in the Application_Start inside Global.asax:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredAttribute), typeof(RequiredAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(StringLengthAttribute), typeof(StringLengthAttributeAdapter));
I am trying to create a custom validation attribute to only require a field depending on the result of another.
The problem I am having is that the IsValid block is never called. The data seems to be getting into the fields and I have been able to check this with a breakpoint.
I tried putting a TryValidateModel(this) in the OnPostAsync and this worked through the breakpoint but I could see that another error occurred.
The requested operation is invalid for DynamicMethod
Here is my code below. Any help would be appreciated.
public class PageOneModel : PageModel
{
[BindProperty]
public bool CompanyHouseToggle { get; set; }
[BindProperty]
[StringLength(60, MinimumLength = 3)]
[RequiredIf("CompanyHouseToggle", desiredvalue: "true")]
public string CompanyNumber { get; set; }
[BindProperty]
[StringLength(60, MinimumLength = 3)]
public string OrganisationName { get; set; }
[BindProperty]
[RegularExpression(pattern: "(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})", ErrorMessage = "This VAT number is not recognised")]
public string VatNumber { get; set; }
public void OnGet()
{
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
RedirectToPage("2");
}
}
public class RequiredIfAttribute : System.ComponentModel.DataAnnotations.ValidationAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
this.PropertyName = propertyName;
this.DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var property = context.ObjectType.GetProperty(PropertyName);
if (property == null)
throw new ArgumentException("Property with this name not found");
// Just for testing purposes.
return new ValidationResult(ErrorMessage);
}
}
I suggest you to inherit from the ReuiredAttribute. It fully works for me.
public class RequiredUnlessDeletingAttribute : RequiredAttribute
{
string DeletingProperty;
/// <summary>
/// Check if the object is going to be deleted skip the validation.
/// </summary>
/// <param name="deletingProperty">The boolean property`s name which shows the object will be deleted.</param>
public RequiredUnlessDeletingAttribute(string deletingProperty = "MustBeDeleted") =>
DeletingProperty = deletingProperty;
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(deletingProperty);
if ((bool)property.GetValue(validationContext.ObjectInstance))
return ValidationResult.Success;
return base.IsValid(value, validationContext);
}
}
Check the full implementation here
I have next (simplified) view model:
public class RegisterModel
{
public string UserName { get; set; }
[MustExistIf("SomeProperty", "some value", "SomeOtherProperty", ErrorMessage = "You have to select something")]
public string LastName { get; set; }
public AddressModel Address { get; set; }
}
public class AddressModel
{
public string Street { get; set; }
public string House { get; set; }
}
and I have custom validator
public class MustExistIfAttribute : ValidationAttribute, IClientValidatable
{
private string _masterName { get; set; }
private object _masterValue { get; set; }
private string _dependantName { get; set; }
public MustExistIfAttribute(string masterName, object masterValue, string dependantName)
{
this._masterName = masterName;
this._masterValue = masterValue;
this._dependantName = dependantName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get value of master property
var masValue = _getValue(validationContext.ObjectInstance, _masterName);
// get value of property whch depends on master property
var depValue = _getValue(validationContext.ObjectInstance, _dependantName);
if (masValue.Equals(_masterValue)) // if value in request is equal to value in specified in data annotation
{
if (depValue == null) // if dependant value does not exist
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public override bool IsValid(object value)
{
return base.IsValid(value);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "mustexistif",
ErrorMessage = FormatErrorMessage(metadata.DisplayName)
};
modelClientValidationRule.ValidationParameters.Add("mastername", this._masterName);
modelClientValidationRule.ValidationParameters.Add("mastervalue", this._masterValue);
modelClientValidationRule.ValidationParameters.Add("dependantname", this._dependantName);
yield return modelClientValidationRule;
}
private static object _getValue(object objectInstance, string propertyName)
{
...
}
}
I have next javascript (please neglect returning false in mustexitif method - it's just for test purposes)
(function () {
jQuery.validator.addMethod('mustexistif', function (value, element, params) {
var masterName = params['mastername'];
var masterValue = params['mastervalue'];
var dependantName = params['dependantname'];
return false;
});
var setValidationValues = function (options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
};
var $Unob = $.validator.unobtrusive;
$Unob.adapters.add("mustexistif", ["mastername", "mastervalue", "dependantname"], function (options) {
var value = {
mastername: options.params.mastername,
mastervalue: options.params.mastervalue,
dependantname: options.params.dependantname
};
setValidationValues(options, "mustexistif", value);
});
})();
It works as expected when I decorate LastName property of RegisterModel class with MustExistIf annotation (like in provided code).
But what I really want is to decorate complex Address property of RegisterModel with MustExistIf annotation. Problem is that when I do that no unobrusive adapter gets registered (javascript doing that IS NOT triggered).
So, there is difference when I decoreate simple and complex properties. My solution does not allow me to decorate properties of Address class (FYI, I tried that and then also validation is working fine). Is there a way to accomplish what I intended? Am I missing something? Woud solution be to validate on model level? But then is it possible to do client side validation?
Maybe you can use Remote Validation.
http://msdn.microsoft.com/en-us/library/gg508808%28v=vs.98%29.aspx
I have a custom ValidationAttribute, however I only want to validate this property if a CheckBox is checked.
I've made my class inherit from IValidationObject and am using the Validate method to perform any custom validation, however can I use a custom ValidationAttribute here instead of duplicating the code? And if so, how?
public class MyClass : IValidatableObject
{
public bool IsReminderChecked { get; set; }
public bool EmailAddress { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsReminderChecked)
{
// How can I validate the EmailAddress field using
// the Custom Validation Attribute found below?
}
}
}
// Custom Validation Attribute - used in more than one place
public class EmailValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var email = value as string;
if (string.IsNullOrEmpty(email))
return false;
try
{
var testEmail = new MailAddress(email).Address;
}
catch (FormatException)
{
return false;
}
return true;
}
}
It's possible to validate a property based on the value of another property, but there are a few hoops to jump through to make sure the validation engine works the way you expect. Simon Ince's RequiredIfAttribute has a good approach and it should be easy to modify it into a ValidateEmailIfAttribute just by adding your e-mail validation logic to the IsValid method.
For example, you could have your base validation attribute, just like you do now:
public class ValidateEmailAttribute : ValidationAttribute
{
...
}
and then define the conditional version, using Ince's approach:
public class ValidateEmailIfAttribute : ValidationAttribute, IClientValidatable
{
private ValidateEmailAttribute _innerAttribute = new ValidateEmailAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public ValidateEmailIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(this.DependentProperty);
if (field != null)
{
// get the value of the dependent property
var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);
// compare the value against the target value
if ((dependentvalue == null && this.TargetValue == null) ||
(dependentvalue != null && dependentvalue.Equals(this.TargetValue)))
{
// match => means we should try validating this field
if (!_innerAttribute.IsValid(value))
// validation failed - return an error
return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
// Client-side validation code omitted for brevity
}
Then you could just have something like:
[ValidateEmailIf("IsReminderChecked", true)]
public bool EmailAddress { get; set; }