Tengo problemas para implementar un validador para una clase, donde solo se debe establecer una propiedad.
Digamos que tenemos la siguiente clase:
public class SomeClass { public DateTime SomeDate {get; set;} public IEnumerable<int> FirstOptionalProperty {get; set;} public IEnumerable<int> SecondOptionalProperty {get; set;} public IEnumerable<int> ThirdOptionalProperty {get; set;} } Esta clase tiene una propiedad obligatoria: SomeDate . Otras propiedades son opcionales y solo se puede establecer una, por ejemplo, si se establece FirstOptionalProperty - SecondOptionalProperty y ThirdOptionalProperty deben ser nulas, si se establece SecondOptionalProperty - FirstOptionalProperty y ThirdOptionalProperty deben ser nulas y así sucesivamente.
En otras palabras: si se establece uno de los accesorios de IEnumerable, otros IEnumerables deben ser nulos.
¿Algún consejo/idea sobre la implementación del validador para este tipo de clase? Lo único que se me ocurrió fue escribir fragmentos de reglas When , pero esta forma de escribir código es propensa a errores y el resultado se ve feo.
Puede aprovechar la sobrecarga Must que le da acceso a todo el objeto de la clase para que pueda realizar una validación de propiedad con otras propiedades. Consulte la regla FluentValidation para varias propiedades para obtener más detalles.
public class SomeClassValidator : AbstractValidator<SomeClass> { private const string OneOptionalPropertyMessage = "Only one of FirstOptionalProperty, SecondOptionalProperty, or ThirdOptionalProperty can be set."; public SomeClassValidator() { RuleFor(x => x.FirstOptionalProperty) .Must(OptionalPropertiesAreValid) .WithMessage(OneOptionalPropertyMessage); RuleFor(x => x.SecondOptionalProperty) .Must(OptionalPropertiesAreValid) .WithMessage(OneOptionalPropertyMessage); RuleFor(x => x.ThirdOptionalProperty) .Must(OptionalPropertiesAreValid) .WithMessage(OneOptionalPropertyMessage); } // this "break out" method only works because all of the optional properties // in the class are of the same type. You'll need to move the logic back // inline in the Must if that's not the case. private bool OptionalPropertiesAreValid(SomeClass obj, IEnumerable<int> prop) { // "obj" is the important parameter here - it's the class instance. // not going to use "prop" parameter. // if they are all null, that's fine if (obj.FirstOptionalProperty is null && obj.SecondOptionalProperty is null && obj.ThirdOptionalProperty is null) { return true; } // else, check that exactly 1 of them is not null return new [] { obj.FirstOptionalProperty is not null, obj.SecondOptionalProperty is not null, obj.ThirdOptionalProperty is not null } .Count(x => x == true) == 1; // yes, the "== true" is not needed, I think it looks better } }Puede modificar la función de verificación. Tal como está actualmente, si establece 2 o más de las propiedades opcionales, TODAS arrojarán un error. Eso puede o no estar bien para sus necesidades.
También podría crear una RuleFor SOLAMENTE para la primera propiedad opcional, en lugar de para todas ellas, ya que todas las propiedades ejecutarán el mismo código IsValid y devolverán el mismo mensaje, su usuario podría confundirse un poco si recibe un mensaje de error para OptionalProperty1 pero no proporcionaron ese.
La desventaja de este enfoque es que necesita saber en tiempo de compilación cuáles son todas sus propiedades (para que pueda escribir el código), y necesita mantener este validador si agrega/elimina entradas opcionales. Este inconveniente puede o no ser importante para usted.
una cosa que me viene a la mente es usar la reflexión aquí:
SomeClass someClass = new SomeClass { SomeDate = DateTime.Now, FirstOptionalProperty = new List<int>(), //SecondOptionalProperty = new List<int>() // releasing this breakes the test }; var info = typeof(SomeClass).GetProperties() .SingleOrDefault(x => x.PropertyType != typeof(DateTime) && x.GetValue(someClass) != null); básicamente, si la info es null , se crearon instancias de más de 1 de las propiedades opcionales
Yo usaría una función de ayuda para esto.
private static bool OnlyOneNotNull(params object[] properties) => properties.Count(p => p is not null) == 1;Lo usarías así.
SomeClass s = new SomeClass(); /* ... */ if(!OnlyOneNotNull(s.FirstOptionalProperty, s.SecondOptionalProperty, s.ThirdOptionalProperty)) { /* handle error case */ }