Blazor tiene un soporte para formularios bastante bien montado. Por un lado, existe el componente “padre”, que representa al propio formulario. Este componente padre (solemos utilizar EditForm) es el encargado de crear “el contexto de edición” y proveerlo a todos los componentes hijos que están dentro de él. Este contexto consta básicamente de un modelo (el objeto que se está editando) y el estado de la validación de sus propiedades.
Dentro de un EditForm
usamos los componentes de formulario (tales como InputText
). Esos controles consultan en dicho contexto de edición si el valor de la propiedad es válido o inválido y se renderizan en consecuencia. Pero esos componentes no modifican nunca el estado de una propiedad. Esto se reserva a otros componentes (que también deben ser hijos del EditForm
), que son los validadores. Así pues la responsabilidad está claramente separada:
- El formulario padre (
EditForm
) provee del contexto de edición - Los componentes de edición usan dicho contexto para consultar si el valor de la propiedad a la que están enlazados es correcto o no (y renderizarse en consecuencia)
- Los validadores validan los valores de las propiedades, modificando el contexto de edición
Blazor viene de serie con un componente de validación que es el DataAnnotationsValidator
que examina el modelo asociado al contexto y busca atributos de Data Annotations. Así si tenemos un modelo tal que:
|
|
Podemos crear un formulario para editarlo y validarlo de forma sencilla:
|
|
Ahora bien, si en lugar de Data Annotations prefieres usar algun otro mecanismo de validación, ¿como lo puedes implementar? Pues la solución pasa por crearte un componente validador. En la documentación está muy bien explicado aunque el ejemplo es un poco rebuscado. En este post os voy a mostrar como crear un validador que valide en base al código de FluentValidation que tengáis.
Creando el validador de FluentValidation
Primero, el código entero y luego lo comentamos por partes :)
|
|
Este es el componente, que lo único que hace es recojer el contexto de edición que provee el EditForm
. Para ello declara una propiedad de tipo EditContext
decorada con [CascadingParameter]
, ya que EditForm
provee del contexto de edición usando una cascading value.
Hay trampa claro, ese código depende del método AddFluentValidation
que es un método de extensión sobre EditContext
, y que es el que realmente hace el trabajo.
|
|
Ahora sí, podemos ir método a método.
- El método
AddFluentValidation
es el método inicial que lo que hace es suscribirse a los eventos que lanza elEditContext
. Hay dos eventos a los que debemos suscribirnos:OnFieldChanged
que se lanza cuando un componente de edición pide validar la propiedad a la que está enlazado yOnValidationRequested
que se lanza cuando se debe validar todo el formulario. Así en el primero debemos validar solo una propiedad y en el segundo pues todo el modelo. Luego este método también crea laValidationMessageStore
que es, básicamente, un diccionario de propiedades y errores de validación asociados. - El método
ValidateModel
es el que se ejecuta en respuesta al eventoOnValidationRequested
y a partir de ese punto ya todo es código de FluentValidation: Se obtiene el validador asociado al modelo (GetValidatorForModel
) y se crea el contexto de validación de FluentValidation (CreateValidationContextForModel
), luego se valida el modelo, se añaden los errores a laValidationMessageStore
y finalmente se notifica alEditContext
que ha habido cambios en el estado de la validación. - El método
ValidateField
es parecido, simplemente se cambia como se crea el contexto de validación de FluentValidation (ahora es un contexto para validar solo una propiedad no todo el modelo). Para ello se usa el parámetroFieldIdentifier
que (básicamente) contiene el nombre de la propiedad que debemos validar.
Luego los siguientes métodos son métodos usados por esos dos, y son un poco “complejos” porque se debe usar reflection. La API de FluentValidation no está pensada para “obtener un validador de un tipo que solo conocemos en runtime”, pero vamos… tampoco son gran cosa. Por ejemplo, para validar nuestro tipo UserData
se debe crear un ValidationContext<UserData>
. Eso está perfecto si conoces en tiempo de compilación el tipo, pero no es nuestro caso, de ahí la necesidad de usar reflection. Lo mismo ocurre con el método GetValidatorForModel
, que debe crear una instancia de cualquier clase que herede de AbstractValidator<UserData>
.
Usando nuestro validador
Bueno, eso no tiene ningún secreto. Ahora, en lugar de decorar con [Required]
la propiedad Name
de UserData
tendríamos que crear un validador de FluentValidation:
|
|
Y en nuestro EditForm
en lugar de usar el DataAnnotationsValidator
, pues usamos nuestro nuevo validador:
|
|
¡Y listos! Ya hemos integrado FluentValidation en nuestro proyecto de Blazor.
Combinando los validadores
Un formulario no debe por que estar validado por un único validador. Este modelo de validación en el que los validadores son componentes adicionales que comparten el EditContext
provisto por el EditForm
permite que en un mismo formulario haya dos, tres o los validadores que sea:
|
|
Ahora se aplicarán tanto las validaciones de Data Annotations como las de FluentValidation. Un validador no puede interferir con las validaciones que aplique otro validador (para ello deberían compartir el ValidationMessageStore
y en este caso cada uno está creando el suyo propio). Así si UserData
tuviera tanto el [Required]
aplicado y también existiese el validador de FluentValidation, si dejas el nombre en blanco recibirias dos errores en la propiedad Name
. Si p. ej. usaras un <ValidationSummary>
para mostrar el resúmen de errores, te aparecerían dos mensjaes de error asociados al nombre del usuario.
Y… ¡nada más!