test aspnetcore c# validation fluentvalidation

c# - aspnetcore - fluentvalidation web api core



¿Cómo puedo acceder al elemento de la colección que se está validando cuando uso RuleForEach? (1)

Estoy usando FluentValidation para validar un objeto y, como este objeto tiene un miembro de la colección, estoy intentando usar RuleForEach . Por ejemplo, supongamos que tenemos Customer y Orders , y queremos asegurarnos de que ningún pedido de un cliente tenga un valor total que supere el máximo permitido para ese cliente:

this.RuleForEach(customer => customer.Orders) .Must((customer, orders) => orders.Max(order => order.TotalValue) <= customer.MaxOrderValue)

Hasta ahora tan bueno. Sin embargo, también necesito registrar información adicional sobre el contexto del error (por ejemplo, dónde se encontró el error en un archivo de datos). He encontrado esto bastante difícil de lograr con FluentValidation, y mi mejor solución hasta ahora es usar el método WithState . Por ejemplo, si encuentro que los detalles de la dirección del cliente son incorrectos, podría hacer algo como esto:

this.RuleFor(customer => customer.Address) .Must(...) .WithState(customer => GetErrorContext(customer.Address))

(donde GetErrorContext es un método mío para extraer los detalles relevantes).

Ahora el problema que tengo es que al usar RuleForEach , la firma del método asume que proporcionaré una expresión que haga referencia al Customer , no al Order particular que causó el error de validación. Y parece que no tengo forma de decir qué orden tuvo un problema. Por lo tanto, no puedo almacenar la información de contexto apropiada.

En otras palabras, solo puedo hacer esto:

this.RuleForEach(customer => customer.Orders) .Must((customer, orders) => orders.Max(order => order.TotalValue) <= customer.MaxOrderValue) .WithState(customer => ...)

... cuando realmente quería hacer esto:

this.RuleForEach(customer => customer.Orders) .Must((customer, orders) => orders.Max(order => order.TotalValue) <= customer.MaxOrderValue) .WithState(order => ...)

¿Realmente no hay manera de acceder a los detalles (o incluso al índice) de los elementos de la colección que fallaron?

Supongo que otra forma de verlo es que quiero que WithState tenga un WithStateForEach equivalente ...


Actualmente no hay ninguna funcionalidad en FluentValidation, que permite establecer el estado de validación de la forma que desee. RuleForEach fue diseñado para evitar la creación de validadores triviales para elementos de recopilación simples, y su implementación no cubrió todos los posibles casos de uso.

Puede crear una clase de validador separada para Order y aplicarla usando el método SetCollectionValidator . Para acceder a Customer.MaxOrderValue en el método Must : agregue la propiedad al Order , que hace referencia al Customer hacia atrás:

public class CustomerValidator { public CustomerValidator() { RuleFor(customer => customer.Orders).SetCollectionValidator(new OrderValidator()); } } public class OrderValidator { public OrderValidator() { RuleFor(order => order.TotalValue) .Must((order, total) => total <= order.Customer.MaxOrderValue) .WithState(order => GetErrorInfo(order)); // pass order info into state } }

Si aún desea utilizar el método RuleForEach , puede usar el mensaje de error en lugar del estado personalizado, ya que tiene acceso a los objetos de entidad de elemento principal y secundario en una de las sobrecargas:

public class CustomerValidator { public CustomerValidator() { RuleForEach(customer => customer.Orders) .Must((customer, order) => order.TotalValue) <= customer.MaxOrderValue) .WithMessage("order with Id = {0} have error. It''s total value exceeds {1}, that is maximum for {2}", (customer, order) => order.Id, (customer, order) => customer.MaxOrderValue, (customer, order) => customer.Name); } }

Si necesita recopilar todos los índices (o identificadores) de órdenes fallidas, puede hacerlo con la regla Custom , como aquí:

public CustomerValidator() { Custom((customer, validationContext) => { var isValid = true; var failedOrders = new List<int>(); for (var i = 0; i < customer.Orders.Count; i++) { if (customer.Orders[i].TotalValue > customer.MaxOrderValue) { isValid = false; failedOrders.Add(i); } } if (!isValid){ var errorMessage = string.Format("Error: {0} orders TotalValue exceed maximum TotalValue allowed", string.Join(",", failedOrders)); return new ValidationFailure("", errorMessage) // return indexes of orders through error message { CustomState = GetOrdersErrorInfo(failedOrders) // set state object for parent model here }; } return null; }); }

PD

No olvide que su objetivo es implementar la validación, no usar FluentValidation en todas partes . A veces implementamos la lógica de validación como un método separado, que funciona con ViewModel y rellena ModelState en ASP.NET MVC.

Si no puede encontrar una solución que se ajuste a sus requisitos, entonces la implementación manual sería mejor que la implementación con muletas con la biblioteca.