Search code examples
c#webformsattributesblazorrequired

attribute Required If (Ranged If) setup not giving me the forms text box as error, but does throw error


I have a custom Required if that works fine. I then needed a Ranged If - Required if another item was a certain value, and it needs to be within a range. this does work - but does not throw the error under the box. I assume due to "return new ValidationResult". is there a way to just throw error in custom attributes that I'm missing that will link back to the text box?

Ranged if reads like this: Value I want to look at, the value I want to make it require, min and max values for the range.

public string IsMonetized { get; set; } 

// [RequiredIf("IsMonetized", "Yes", "Please Enter a Value")]
// [Range(1, double.MaxValue, ErrorMessage = "Please Enter an Amount greater than 0")]
[RangedIf("IsMonetized", "Yes", 1, 200)]
public double MaxAmount { get; set; }

//[Range(1, 200, ErrorMessage = "Please Enter an Amount greater than 0")]
[RangedIf("IsMonetized", "Yes", 1, 200)]
public double? AnnualAmount { get; set; }
public class RangedIfAttribute : RequiredAttribute
{
    private String PropertyName { get; set; }
    private Object DesiredValue { get; set; }
    private double max { get; set; }
    private double min { get; set; }

    public RangedIfAttribute(String propertyName, Object desiredvalue, double Min, double Max)
    {
        PropertyName = propertyName;
        DesiredValue = desiredvalue;
        min = Min;
        max = Max;
    }

    public RangedIfAttribute(String propertyName, Object desiredvalue, double Min, double Max, String Errormessage)
    {
        PropertyName = propertyName;
        DesiredValue = desiredvalue;
        ErrorMessage = Errormessage;
        min = Min;
        max = Max;
    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        Object instance = context.ObjectInstance;
        Type type = instance.GetType();
        Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);

        try
        {
            if (proprtyvalue == null)
            {
                if (DesiredValue == null)
                {
                    if (min <= (double)value && (double)value <= max)
                    {
                        return base.IsValid(value, context); // Null was intended , and value in range - Valid
                    }
                }
            }
            else if (proprtyvalue.ToString().Equals(DesiredValue))
            {
                if (min <= (double)value && (double)value <= max)
                {
                    return base.IsValid(value, context); // desired = property value, and value in range - Valid
                }
            }

            // This should submit Not Valid
            return new ValidationResult(this.FormatErrorMessage(context.DisplayName));
        }
        catch
        {
            // this should submit not valid - the required item is null
            return new ValidationResult($"Value must be within the range of {min} and {max}");
        }
    }
}

I have tried looking through all of the custom attributes information I can find and it seems nothing is coming up useful. the Validate.Success can send a successful validation, but it seems there is no Auto Failure (Validate.Failure). seems like an over site to the attribute system.

The boxes on both do nothing right away - and are not flagged - but a validation summary at the bottom will read:

The MaxAmount field is required. - Max Amount Field falls to the bottom with not matching the correct if logic.

Value must be within the range of 1 and 200 - annual amount will hit the try/catch since it was null and throws an error.

Again - it stops the form from submitting, but there is no indication what box (normally highlights red when validation fails) fails to validate.


Solution

  • the "fix" was this line for a false return:

                    return new ValidationResult($"Value must be within the range of {min} and {max}", new[] { context.MemberName });

    Basically this allows the system to link back which entry box needed to be flagged, while you are able to send a custom message for each failed area. my bottom one could read "no null values" but having them say between A and B sounds better.

    Here is the finished product:

            [Required(ErrorMessage = "Please Select an Option")]
            public string IsMonetized { get; set; }
    
            [RangedIf("IsMonetized", "Yes", 1, 200)]
            public double? AnnualAmount { get; set; }

    public class RangedIfAttribute : RequiredAttribute
    {
        private String PropertyName { get; set; }
        private Object DesiredValue { get; set; }
        private double max { get; set; }
        private double min { get; set; }
    
        public RangedIfAttribute(String propertyName, Object desiredvalue, double Min, double Max)
        {
    
            PropertyName = propertyName;
            DesiredValue = desiredvalue;
            min = Min;
            max = Max;
        }
    
        public RangedIfAttribute(String propertyName, Object desiredvalue, double Min, double Max, String Errormessage)
        {
            PropertyName = propertyName;
            DesiredValue = desiredvalue;
            ErrorMessage = Errormessage;
            min = Min;
            max = Max;
        }
    
        protected override ValidationResult IsValid(object value, ValidationContext context)
        {
            Object instance = context.ObjectInstance;
            Type type = instance.GetType();
            Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
            try
            {
                if (proprtyvalue == null)
                {
                    if (DesiredValue == null)
                    {
                        if (min <= (double)value && (double)value <= max)
                        {
                            return base.IsValid(value, context); // Null was intended , and value in range
                        }
                        else
                        {
                            return new ValidationResult($"Value must be within the range of {min} and {max}", new[] { context.MemberName }); // null was intended but value not in range
                        }
                    }
                    else
                    {
                        return ValidationResult.Success; //property value is null - needs to be selected first - do not trigger
                    }
                }
                else if (proprtyvalue.ToString().Equals(DesiredValue))
                {
                    if (min <= (double)value && (double)value <= max)
                    {
                        return base.IsValid(value, context); // property = intended value and value in range
                    }
                    else
                    {
                        return new ValidationResult($"Value must be within the range of {min} and {max}", new[] { context.MemberName }); //property = inteded value but not in range
                    }
                }
                return ValidationResult.Success; //not required
            }
            catch
            {
                // most likely a null value
                return new ValidationResult($"Value must be within the range of {min} and {max}", new[] { context.MemberName });
            }
        }
    }