How to make an Annotation data validator based on another field?

Asked

Viewed 79 times

0

I’m trying to implement a validator based on another field.

My class of domain.

[Required]
public string _tipoDeControle { get; set; }

[NotMapped]
public TipoDeControle TipoDeControle
{
    get { return ExtensaoDeEnumerador.ObterEnumeradorPorNome<TipoDeControle>(_tipoDeControle?.Trim()); }
    set { _tipoDeControle = value.ToString(); }
}

[RequiredEqual("TipoDeControle", "Transacao")]
public decimal? Valor { get; set; }

Meu Enum.

public enum TipoDeControle
{
    [Description("Transação")]
    Transacao,

    [Description("Horário")]
    Horario,

    [Description("Data")]
    Data,

    [Description("Dias")]
    Dias
}

I tried to do it this way;

using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;

namespace Infraestrutura.Utilitario
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public sealed class RequiredEqualAttribute : ValidationAttribute
    {
        public string EnumProperty { get; set; }
        public string EnumValue { get; private set; }

        public override bool RequiresValidationContext { get { return true; } }

        /// <summary>
        /// Faz a validação para um tipo Enum.
        /// </summary>
        /// <param name="_enum"></param>
        /// <param name="enumValue"></param>
        public RequiredEqualAttribute(string objeto, string enumValue)
            : base("'{0}' é requerido porque '{1}' foi preenchido {2}.")
        {
            this.EnumProperty = objeto;
            this.EnumValue = enumValue;
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (validationContext == null)
            {
                throw new ArgumentNullException("validationContext");
            }

            if (this.EnumProperty != null)
                return ValidationEnum(value, validationContext);

            return ValidationResult.Success;
        }

        private ValidationResult ValidationEnum(object value, ValidationContext validationContext)
        {
            string enumValue = meuenum.ObterDescricao();

            if (object.Equals(enumValue, this.EnumValue))
            {
                if (value == null)
                {
                    return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
                }

                string val = value as string;
                if (val != null && val.Trim().Length == 0)
                {
                    return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
                }
            }

            return ValidationResult.Success;
        }

        public override string FormatErrorMessage(string name)
        {
            return string.Format(
                CultureInfo.CurrentCulture,
                base.ErrorMessageString,
                name,
                this.EnumProperty.GetType().Name,
                this.EnumValue);
        }
    }
}

What I want to do is when the Enum is equal to Transaction my field Valor be mandatory.

But I couldn’t get the validation right. How could it be that kind of validation or what I missed?

1 answer

1


you can do it this way.:

public class RequiredEqualAttribute : RequiredAttribute
{
    public RequiredEqualAttribute(Type type, string prop, object value)
    {
        this.Property = type.GetProperty(prop);
        this.Value = value as IComparable;
    }

    public PropertyInfo Property { get; private set; }
    public IComparable Value { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        var currentValue = this.Property.GetValue(context.ObjectInstance) as IComparable;
        if (currentValue.CompareTo(this.Value) == 0)
            return base.IsValid(value, context);
        return ValidationResult.Success;
    }
}

Your attribute would look like this.:

[RequiredEqual(typeof(NomeClasse), nameof(TipoDeControle), TipoDeControle.Transacao)]
public decimal? Valor { get; set; }

If you prefer, you can do it this way.:

public class RequiredEqualAttribute : RequiredAttribute
{
    public RequiredEqualAttribute(string prop, object value)
    {
        this.Property = prop;
        this.Value = value as IComparable;
    }

    public string Property { get; private set; }
    public IComparable Value { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        var type = context.ObjectInstance.GetType();
        var property = type.GetProperty(this.Property);
        var currentValue = property.GetValue(context.ObjectInstance) as IComparable;
        if (currentValue.CompareTo(this.Value) == 0)
            return base.IsValid(value, context);
        return ValidationResult.Success;
    }
}

But in this case, I advise you to make use of the Fastmember, or that stores the PropertyInfo somewhere to be reused.

Testing

public enum TipoDeControle
{
    Transacao,
    Horario,
    Data,
    Dias
}

public class Cadastro
{
    [Required]
    public TipoDeControle TipoDeControle { get; set; }

    [RequiredEqual(typeof(Cadastro), nameof(TipoDeControle), TipoDeControle.Transacao)]
    public decimal? Valor { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var cadastro = new Cadastro();
        var context = new ValidationContext(cadastro, null, null);
        var results = new List<ValidationResult>();

        results.Clear();
        cadastro.TipoDeControle = TipoDeControle.Horario;
        Validator.TryValidateObject(cadastro, context, results);
        Console.WriteLine(results.Count);

        results.Clear();
        cadastro.TipoDeControle = TipoDeControle.Transacao;
        Validator.TryValidateObject(cadastro, context, results);
        Console.WriteLine(results.Count);

        results.Clear();
        cadastro.Valor = 10.256m;
        Validator.TryValidateObject(cadastro, context, results);
        Console.WriteLine(results.Count);
    }
}

Output

0
1
0
  • I tried to use but it was wrong... Severity Code Description Project File Line Suppression State&#xA;Error CS0181 Attribute constructor parameter 'value' has type 'IComparable', which is not a valid attribute parameter type Agillitas.Cadastro.

  • @Marconciliosouza performed the correction, unfortunately IComparable is not a primitive, so it cannot be used in the constructor of an attribute.

  • Cool, it looks good.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.