How to prevent execution of next ValidationAttribute, if the first validation is failed?
For example, if (category "id" <= 0), then do not try to check if it exists.
Because now, when I do "PUT /api/categories/-1", I get this:
{
"id": [
"id must be greater or equal to 0",
"Entity with such Id was not found!"
]
}
Method where I want to prevent further validation:
[HttpPut("{id}")]
public ActionResult UpdateCategory([Min(0)][CategoryExists] int id, [FromBody] Category category)
{
return new OkResult();
//if (category.Id == 0) {
// return new BadRequestObjectResult("Id property is required!");
//}
//_context.Category.Update(category);
//_context.SaveChanges();
}
Min attribute
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
public class MinAttribute : ValidationAttribute
{
private int _minVal;
public MinAttribute(int minVal)
{
_minVal = minVal;
}
public override bool IsValid(object value)
{
if ((int)value >= _minVal)
{
return true;
}
else
{
return false;
}
}
public override string FormatErrorMessage(string name)
{
return $"{name} must be greater or equal to {_minVal}";
}
}
CategoryExists attribute
public class CategoryExistsAttribute : ValidationAttribute
{
public CategoryExistsAttribute()
{
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var context = (TechDbContext)validationContext.GetService(typeof(TechDbContext));
var result = from i in context.Category
where i.Id == (int)value
select i;
Category category = result.SingleOrDefault();
if (category == null)
{
return new ValidationResult("Entity with such Id was not found!");
}
return ValidationResult.Success;
}
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(name);
}
}
Write an extension method for the Category object, which validates all your needs.
Eg
internal static bool Validate(this Category category){
if (category.Id <= 0) {
return false;
}
// Add more validations or throw exceptions if needed.
return true;
}
So from your controller you can do this.
if(category.Validate()){
//proceed to other business logic
}
For more detailed information about data validation read this
Related
how can i to put conditional Required Attribute into class? i tried the following code and it doesn't work.
public partial class Zone
{
[RequireCondition ]
public int LastCount { get; set; }
}
public class RequireCondition : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var Zone = (Zone)validationContext.ObjectInstance;
if (Zone.LastCount < 1)
{
return new ValidationResult("Last Count value must be greater than one.");
}
else
{
return ValidationResult.Success;
}
}
}
Try this?
public partial class Zone
{
[RequireCondition(1)]
public int LastCount { get; set; }
}
public class RequireConditionAttribute : ValidationAttribute
{
private int _comparisonValue;
public RequireCondition(int comparisonValue)
{
_comparisonValue = comparisonValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value is int && (int)value < comparisonValue)
{
return new ValidationResult($"{validationContext.DisplayName} value must be greater than one.");
}
return ValidationResult.Success;
}
}
I have a problem with a custom DataAnnotation.
public class RequiredInt32 : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
if (Convert.ToInt32(value) == 0)
{
return new ValidationResult("custom-message");
}
}
return ValidationResult.Success;
}
}
I have that code. If the condition is met, no returns "custom-message" , returns me "The field is invalid". For me return the message I want, I need to put it explicitly.
[RequiredInt32 (ErrorMessage = # "custom-message")]
What I have wrong and how can I do to have a default message. Thank you!
If you meant to customize formatting error message, you can define an attribute like this:
public class RequiredInt32 : ValidationAttribute
{
private const string _customFormat = "{0} is not valid";
private string _fieldName;
public RequiredInt32(string fieldName)
: base(_customFormat)
{
_fieldName = fieldName;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
if (Convert.ToInt32(value) == 0)
{
return new ValidationResult(FormatErrorMessage(_fieldName));
}
}
return ValidationResult.Success;
}
}
Usage:
[RequiredInt32("MyField")]
public int NumberProperty {get;set;}
I have a problem with custom validation. I have a ViewModel:
public class CityViewModel
{
[ForeignKey(ErrorMessageResourceName = "County")]
public int CountyId { get; set; }
public string PostCode { get; set; }
}
I created a custom validation class named ForeignKey that contains this code:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class ForeignKeyAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo propretyInfo = validationContext.ObjectType.GetProperty(validationContext.MemberName);
ResourceManager manager = Resource.ResourceManager;
if (propretyInfo.PropertyType.IsGenericType && propretyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
if (value != null && (int)value == 0) {
return new ValidationResult(manager.GetString(ErrorMessageResourceName));
}
else {
return ValidationResult.Success;
}
}
else if (value == null || (int)value == 0) {
return new ValidationResult(manager.GetString(ErrorMessageResourceName));
}
return ValidationResult.Success;
}
}
This method works perfectly and return the error correctly. The problem is in the action in my controller:
[HttpPost]
public ActionResult Create(DataSourceRequest request, CityViewModel model)
{
try {
if (ModelState.IsValid) {
// Some code
return Json(new[] { model }.ToDataSourceResult(request, ModelState));
}
}
catch (Exception e) {
// Some code
}
return Json(ModelState.ToDataSourceResult());
}
If CountyId is null (really is 0 but during validation before entering in the method Create can be null) my ModelState contains, for CountyId field, this error "The CountyId field is required." instead of my error passed to ForeignKey custom attribute.
If I use this code:
TryValidateModel(model);
Then ModelState contains both errors, so before calling TryValidateModel I should use:
ModelState["CountyId"].Errors.Clear();
How can I say to MVC not to overwrite my error during first validation? I prefer to use simply ModelState.IsValid. Anyone can help me?
Try Excluding the Id in the Create method parameters
[HttpPost]
public ActionResultCreate([Bind(Exclude = "Id")], CityViewModel model)
I have created a custom RequiredIf validator like this:
public class RequiredIfValidator : ValidationAttribute, IClientValidatable
{
RequiredAttribute _innerAttribute = new RequiredAttribute();
public string _dependentProperty { get; set; }
public object _targetValue { get; set; }
public RequiredIfValidator(string dependentProperty, object targetValue)
{
this._dependentProperty = dependentProperty;
this._targetValue = targetValue;
}
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, _dependentProperty);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var field = validationContext.ObjectInstance.GetType().GetProperty(_dependentProperty);
if (field != null)
{
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if ((dependentValue == null && _targetValue == null) ||(dependentValue.Equals(_targetValue)))
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "requiredif";
rule.ValidationParameters["dependentproperty"] = _dependentProperty;
rule.ValidationParameters["targetvalue"] = _targetValue;
yield return rule;
}
}
I have an enum with various test types like this:
public enum TestTypes
{
Hair = 1,
Urine = 2
}
My ViewModel has some properties like this:
public class TestViewModel
{
public TestTypes TestTypeId {get; set;}
[RequiredIfValidator("TestTypeId", TestTypes.Hair)]
public string HairSpecimenId {get; set;}
}
My custom RequiredIfValidator is not working in this scinario. Is it because of the enum data type? Any way to achieve this with enums
You logic in the IsValid() does not appear to be correct. It should be
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(_dependentProperty);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue != null && otherPropertyValue.Equals(_targetValue ))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
I am coding a value object for my domain object identifier, similar to what is done here. Basically I am just wrapping a guid.
By following this article's advice I was able to create a custom model binder which can convert a string to the value object (for input to the controller), but I don't seem to be able to get the correct value as output from the controller.
My hope is to get something similar to this back from my controller action:
{
id: "string representation of the guid goes here",
someOtherProperty: "foo"
}
Has anyone done something similar, and if so, what am I missing?
TenantId.cs
public class TenantId : IEquatable<TenantId>
{
public TenantId(Guid id)
{
if (id == Guid.Empty)
{
throw new InvalidOperationException("TenantId must not be an empty GUID.");
}
_id = id;
}
private readonly Guid _id;
public bool Equals(TenantId other)
{
if (null == other)
{
return false;
}
return _id.Equals(other._id);
}
public override int GetHashCode()
{
return _id.GetHashCode();
}
public override bool Equals(object other)
{
return Equals(other as TenantId);
}
public override string ToString()
{
return _id.ToString("B");
}
}
}
TenantIdModelBinder.cs
public class TenantIdModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var key = bindingContext.ModelName;
var val = bindingContext.ValueProvider.GetValue(key);
if (val == null)
{
return false;
}
var s = val.AttemptedValue;
if (s == null)
{
return false;
}
Guid g;
if (!Guid.TryParse(s, out g))
{
return false;
}
bindingContext.Model = new TenantId(g);
return true;
}
}
TenantController.cs
public class TenantController : ApiController
{
public IHttpActionResult Get([ModelBinder(typeof(TenantIdModelBinder))]TenantId id)
{
return Ok(new
{
id
});
}
}
I can step through and see model is bound properly to the correct guid I pass in, however, this code simply returns: {"id":{}}