How do I access the value of a property inside an attribute class. I'm writing a custom validation attribute that needs to check the value of the property against a regular expression. The
For Instance:
public class MyAttribute
{
public MyAttribute (){}
//now this is where i want to use the value of the property using the attribute. The attribute can be use in different classed
public string DoSomething()
{
//do something with the property value
}
}
Public class MyClass
{
[MyAttribute]
public string Name {get; set;}
}
If you just want to use a regular expression validation attribute, then you can inherit from RegularExpressionAttribute, see https://stackoverflow.com/a/8431253/486434 for an example of how to do that.
However, if you want to do something more complex and access the value you can inherit from ValidationAttribute and override the 2 virtual methods IsValid. E.g.:
public class MyAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// Do your own custom validation logic here
return base.IsValid(value);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return base.IsValid(value, validationContext);
}
}
Related
In my code below I want to check with AttributeValidation if a field is given dependent on a property of its parent element. The comment in the class
RequiredIfParentState1
describes my question best.
public class ChildModel()
{
[RequiredIfParentState1]
public string ImRequired { get; set; }
}
public class ParentViewModel()
{
public int state { get; set; }
public ChildModel child = new ChildModel();
}
public class RequiredIfParentState1: ValidationAttribute, IClientModelValidator
{
RequiredIfParentState1()
{
}
void AddValidation(ClientModelValidationContext context)
{
}
protected override ValidationResult IsValid(object i_value, ValidationContext i_context)
{
var element = i_context.ObjectInstance;
if(i_value == null && //what do i have to put here to check if the state is 1?)
{
return new ValidationResult($"Field is Required in state 1.");
}
return ValidationResult.Success;
}
}
I feel this is the wrong approach.
An object being in a valid state is one thing (required fields and type checking), but handling business logic is a separate concern.
You could write a validation service, that examines the model in detail, checking business logic concerns, and build up a list of errors.
Where errors are found you can return these in your response.
Given a model with these data annotations:
public class Example
{
[Required]
[Display(Name = "Activity response")]
public string ActivityResponse { get; set; }
}
I would expect the model state error message to be "The Activity response field is required." Instead it is "The ActivityResponse field is required."
Hooray! The codeplex issue reports that this bug will be fixed in Web API v5.1 Preview.
Had the same problem and I made a workaround for it.
I know it is not perfect.
For every dataannotation attribute create a new class
public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
validationContext.DisplayName = ModelMetadataProviders
.Current
.GetMetadataForProperty(null, validationContext.ObjectType, validationContext.DisplayName)
.DisplayName;
return base.IsValid(value, validationContext);
}
}
public class StringLengthAttribute : System.ComponentModel.DataAnnotations.StringLengthAttribute
{
public StringLengthAttribute(int maximumLength)
: base(maximumLength)
{ }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
validationContext.DisplayName = ModelMetadataProviders
.Current
.GetMetadataForProperty(null, validationContext.ObjectType, validationContext.DisplayName)
.DisplayName;
return base.IsValid(value, validationContext);
}
}
etc....
I want to model an 'Expense' object that has a 'Sum' (decimal) field.
In the view, I want to validate that the user enters a positive value.
OTOH I want to make sure I save the object with a negative value in the DB.
Right now, the model looks like this:
//------The model-------
public class Operation {
[Range(typeof(decimal), "0.0001", "79228162514264337593543950335")]
public virtual decimal Sum { get; set; }
[...]
}
public class Expense : Operation
{
public override decimal Sum
{
get
{
return base.Sum;
}
set
{
base.Sum = - Math.Abs(value);
}
}
}
//------In the controller-------
[HttpPost]
public ActionResult CreateExpense(Expense operation, int[] SelectedTags)
{
return CreatePost(operation, SelectedTags);
}
private ActionResult CreatePost(Operation operation, int[] SelectedTags)
{
if (ModelState.IsValid) // <-- this fails
[...]
}
The problem is, the MVC validation works with the object's properties (not the POST'ed form values), sees the negative value and fails to validate.
What should I do to fix this?
It looks to me like I'm not separating concerns (validate user input vs maintain database integrity).
Should I use a view model to hold the user input and then populate the actual model from the view model? Doesn't sound like KISS...
I found out that specifying a separate validation attribute on the property of the inherited class works a treat.
Can't think of something more straight-forward.
Here's how the model looks like now:
public class Operation {
public virtual decimal Sum { get; set; }
}
public class Income : Operation
{
[Range(typeof(decimal), "0.0001", "79228162514264337593543950335")]
public override decimal Sum
{
get { return base.Sum; }
set { base.Sum = Math.Abs(value); }
}
}
public class Expense : Operation
{
[Range(typeof(decimal), "-79228162514264337593543950335", "-0.0001")]
public override decimal Sum
{
get { return base.Sum; }
set { base.Sum = - Math.Abs(value); }
}
}
A validation attribute to check if the value is less than zero is another simple solution.
public class PositiveNumberAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object number, ValidationContext validationContext)
{
return int.Parse(number.ToString()) >= 0
? ValidationResult.Success : new ValidationResult("Positive value required.");
}
}
Then apply to property
[PositiveNumber]
public virtual decimal Sum { get; set; }
Lets say i have a route like the following
/{controller}/{action}/{id}
Is it possible to bind the id to a property in my model
public ActionResult Update(Model model)
{
model.Details.Id <-- Should contain the value from the route...
}
Where my model class is the following?
public class Model
{
public Details Details {get;set;}
}
public class Details
{
public int Id {get;set;}
}
You'll want to create your own custom model binder.
public class SomeModelBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
ValueProviderResult value = bindingContext.ValueProvider.GetValue("id");
SomeModel model = new SomeModel() { Details = new Details() };
model.Details.Id = int.Parse(value.AttemptedValue);
//Or you can load the information from the database based on the Id, whatever you want.
return model;
}
}
To register your binder you add this to your Application_Start()
ModelBinders.Binders.Add(typeof(SomeModel), new SomeModelBinder());
Your controller then looks exactly as you have it above. This is a very simplistic example but the easiest way to do it. I'll be happy to provide any additional help.
I'm having some trouble understanding validation logic behing DataAnnotation validation :
With the following model :
[AlwaysInvalid]
public class TestModel
{
[Required]
public string Test { get; set; }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class AlwaysInvalidAttribute : ValidationAttribute
{
private readonly object typeId = new object();
public AlwaysInvalidAttribute() : base("Fail !") {}
public override object TypeId { get { return this.typeId; } }
public override bool IsValid(object value)
{
return false;
}
}
The AlwaysInvalidAttribute error message gets displayed only if the Required attribute is valid : I can't get both messages at the same time. Anyone got an idea why ? I think it's an issue with DefaultModelBinder, but still haven't found where, or why.
Class-level validators only run if all the property-level validators were successful. This behavior is coded up in the ModelValidator class.