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; }
}
Related
I have this Dto class for the web api controller in .NET Core 2.2 MVC.
ApplicationDocumentType is an enum
public class DocumentUploadDto
{
[FileValidation]
public IFormFile File { get; set; }
public ApplicationDocumentType DocumentType { get; set; }
public Guid Id { get; set; }
}
and
public enum ApplicationDocumentType
{
BANKSTATEMENT,
NRIC
}
and the below class implements the [FileValidation]
public class FileValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var file = value as IFormFile;
// some code removed for brevity
if (!AllowMimeTypes.Contains(file.ContentType))
{
ErrorMessage = "Invalid file type.";
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Now I need to validate based on DocumentType. How do I pass DocumentType into FileValidationAttribute to do some validation?
Currently all DocumentType is having the same validation. But now I need to customize the validation based on DocumentType.
Thanks Richard for the clue, but I just keep getting the first enum value.
public class FileValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var containerType = validationContext.ObjectType;
var documentType = containerType.GetProperty("DocumentType");
var file = value as IFormFile;
if (file == null)
return new ValidationResult("No file found.");
if (documentType != null)
{
var documentTypeValue = documentType.GetValue(validationContext.ObjectInstance, null);
if (documentTypeValue.ToString() == "NRIC"
&& file.ContentType == "application/pdf")
{
ErrorMessage = "Invalid file type. Pdf file type is not allowed for NRIC.";
return new ValidationResult(ErrorMessage);
}
}
// some code removed for brevity purpose.
}
}
If document type is static, you can simply add an attribute to your Attribute.
public class FileValidationAttribute : ValidationAttribute
{
public FileValidationAttribute(params string[] allowMimeTypes)
{
AllowMimeTypes = allowMimeTypes;
}
public string[] AllowMimeTypes { get; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var file = value as IFormFile;
// some code removed for brevity
if (!AllowMimeTypes.Contains(file.ContentType))
{
ErrorMessage = "Invalid file type.";
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
and then, in Dto, add allowed Mime Types
public class DocumentUploadDto
{
[FileValidation("text/javascript", "text/html")]
public IFormFile File { get; set; }
public ApplicationDocumentType DocumentType { get; set; }
public Guid Id { get; set; }
}
If you need to pass mime type dynamically, then I suggest looking at https://fluentvalidation.net/ which allows you to easily add data to validation context and write more fluent validators.
I am trying to write my own ValidationAttribute for which I want to pass the value of a parameter of my class to the ValidationAttribute. Very simple, if the boolean property is true, the property with the ValidationAttribute on top should not be null or empty.
My class:
public class Test
{
public bool Damage { get; set; }
[CheckForNullOrEmpty(Damage)]
public string DamageText { get; set; }
...
}
My Attribute:
public class CheckForNullOrEmpty: ValidationAttribute
{
private readonly bool _damage;
public RequiredForWanrnleuchte(bool damage)
{
_damage = damage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string damageText = validationContext.ObjectType.GetProperty(validationContext.MemberName).GetValue(validationContext.ObjectInstance).ToString();
if (_damage == true && string.IsNullOrEmpty(damageText))
return new ValidationResult(ErrorMessage);
return ValidationResult.Success;
}
}
However, I cannot simply pass the property inside the class to the ValidationAttribute like that. What would be a solution to pass the value of that property?
Instead of passing the bool value to the CheckForNullOrEmptyAttribute, you should pass the name of the corresponding property; within the attribute, you then can retrieve this bool value from the object instance being validated.
The CheckForNullOrEmptyAttribute below, can be applied on your model as shown here.
public class Test
{
public bool Damage { get; set; }
[CheckForNullOrEmpty(nameof(Damage))] // Pass the name of the property.
public string DamageText { get; set; }
}
public class CheckForNullOrEmptyAttribute : ValidationAttribute
{
public CheckForNullOrEmptyAttribute(string propertyName)
{
PropertyName = propertyName;
}
public string PropertyName { get; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var hasValue = !string.IsNullOrEmpty(value as string);
if (hasValue)
{
return ValidationResult.Success;
}
// Retrieve the boolean value.
var isRequired =
Convert.ToBoolean(
validationContext.ObjectInstance
.GetType()
.GetProperty(PropertyName)
.GetValue(validationContext.ObjectInstance)
);
if (isRequired)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
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; }
}
Within a Class I have a Static List of values which are allowed
private static List<string> allowedClassNames = new List<string> {"Real Estate", "Factored Debt"};
And I also have an attribute of that class, which I want to restrict to being values in that list.
[Required]
public string assetClassName { get; set; }
I want to do this at the model level, so it works in either a REST or view context.
How would I implement forcing the value in the submission to be limited to that list?
Thanks!
Here's Where I wound up - Not fully tested yet, but to give an idea to future posters.
class MustContainAttribute : RequiredAttribute
{
public string Field { get; private set; }
List<string> allowed;
public MustContainAttribute(string validateField)
{
this.Field = validateField;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
switch (Field)
{
case "assetClassName":
allowed = new List<string> { "Real Estate", "Factored Debt" };
break;
default:
return ValidationResult.Success;
}
if (!allowed.Contains(Field))
{
return new ValidationResult("Invalid Value");
}else{
return ValidationResult.Success;
}
}
}
Create a custom validation attribute:
public class ClassNameRequiredAttribute : RequiredAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
MyAssetClass myAssetClass = (MyAssetClass)type.GetProperty("MyAssetClass").GetValue(instance, null);
if (!string.IsNullOrEmpty(myAssetClass.assetClassName))
{
if (myAssetClass.allowedClassNames.Contains(myAssetClass.assetClassName))
{
return ValidationResult.Success;
}
}
return new ValidationResult(ErrorMessage);
}
}
And in your model:
[ClassNameRequired(ErrorMessage="Your error message.")]
public string assetClassName { get; set; }
As mentioned in the comments you can create your own ValidationAttribute. This is useful if you have this validation on multiple models or if you want to implement client side validation as well (JavaScript)
However, A quick and easy way to do one off validations like this is the IValidatableObject. You can use it as follows:
public class AssetModel:IValidatableObject
{
private static List<string> allowedClassNames = new List<string> {"Real Estate", "Factored Debt"};
[Required]
public string assetClassName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!allowedClassNames.Contains(assetClassName)
{
yield new ValidationResult("Not an allowed value", new string[] { "assetClassName" } );
}
}
}
I am working in asp.net web api project. I created a ValidationAttribute class like as
public class MyValidationAttribute: ValidationAttribute
{
private int _Id { get; set; }
public MyValidationAttribute(int Id)
{
_Id = Id;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
object abc = value;
if (_Id > 10)
return ValidationResult.Success;
return base.IsValid(value, validationContext);
}
}
After that I am using this on a function
[MyValidationAttribute(12)]
public object MyFunction(int Iden){
//Some code here
}
The issue is, on debug mode, I can see that control is not parsing the MyValidationAttribute class. Am I doing some thing wrong?
Data attributes are supposed to be used on properties.
Change it to the following instead:
[MyValidationAttribute(12)]
public int MyProperty { get; set; }