0

I have a bit of an odd requirement.

I have a class with 2 properties on it as shown below:

public class MyViewModel
{
    [EnhancedServiceValidInteger("Name")]
    public int NumberOfItems { get; set; }

    public string Name { get; set; }
}

The above class is used as a dynamic field used to build dynamic forms. When NumberOfItems is incorrect (not a valid integer), an error message should be displayed using the value from the Name property as the DisplayName and is the output of any error messages.

I've implemented a custom validation attribute that triggers, but if the user enters a letter I want that to be picked up by the attribute. In this case (IsValid(object value, ...)) the IsValid methods value property is always 0 so the validation passes and no breakpoint in MyCustomValidIntegerValidator gets hit. Why doesn't the value get set to 'aaa' if that is what was entered in the UI?

Can anyone help please?

I have the following custom validation attribute:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false)]
public class MyCustomValidInteger : ValidationAttribute
{
    private readonly string _validationNameFromProperty;

    public EnhancedServiceValidInteger(string validationNameFromProperty) : base("{0} contains invalid character.")
    {
        _validationNameFromProperty = validationNameFromProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // the the other property
        var property = validationContext.ObjectType.GetProperty(_validationNameFromProperty);

        // check it is not null
        if (property == null)
        {
            return new ValidationResult(String.Format("Unknown property: {0}.", _validationNameFromProperty));
        }

        // get the other value
        var nameToUse = property.GetValue(validationContext.ObjectInstance, null).ToString();

        if (value == null || value.ToString().Length == 0)
        {
            return ValidationResult.Success;
        }

        int i;

        var errorMessage = string.Format("The value '{0}' is not valid for {1}", value, nameToUse);

        ValidationResult validationResult = !int.TryParse(value.ToString(), out i) ? new ValidationResult(errorMessage) : ValidationResult.Success;

        return validationResult;
    }
}

Here is the DataAnnotationsModelValidator implementation for the above class:

public class MyCustomValidIntegerValidator : DataAnnotationsModelValidator<MyCustomValidInteger>
{
    public MyCustomValidIntegerValidator(ModelMetadata metadata, ControllerContext context, EnhancedServiceValidInteger attribute) 
        : base(metadata, context, attribute)
    {
        if (!attribute.IsValid(context.HttpContext.Request.Form[metadata.PropertyName]))
        {
            var propertyName = metadata.PropertyName;
            context.Controller.ViewData.ModelState[propertyName].Errors.Clear();
            context.Controller.ViewData.ModelState[propertyName].Errors.Add(attribute.ErrorMessage);
        }
    }
}
2
  • 1
    If you are creating the input field with the usual EditorFor() Html helper, note that it will create a type="number" input with required as well. Some browsers will NOT return a value if you put letters in it. Commented Aug 11, 2014 at 10:44
  • Thanks Tallmaris, I am using @Html.TextboxFor(...). Commented Aug 11, 2014 at 11:51

1 Answer 1

1

As to why, when form values are posted back to a controller, the DefaultModelBinder first binds the value to the property (using the SetValue() method of PropertyDescriptor). As the property is int and the value is string, it cant be set and the default value is assigned. It is not until this binding is complete that the validation process starts.

Not sure how you could achieve want you want, but creating a custom ModelBinder might be an option

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks Stephen, I will have a look at the model binder
You may also need a CustomModelBinderAttribute applied to the property NumberOfItems, which tells the ModelBinder to use only your custom ModelBinder for this property (you probably would not want to apply it to all properties of type int)
Have you got any examples? My Google-Fu is letting me down
Sorry not for this case, but ModelBinder has lots of methods you can override such as OnModelUpdating() where you should be able to get the posted value before its bound and OnPropertyValidating() which occurs before validation that may allow you to alter the behaviour

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.