¡La versión 2.0 de Play ya está lista! Ayúdanos a traducir la documentación de la útlima versión y sigue nuestro progreso.

Manuales, tutoriales & referencias

Consulte

Contenidos

Elija la versión

Buscar

Busque con google

Libros

Validando información HTTP con Play

Las validaciones nos permiten asegurar que la información ingresada por el usuario cumplirá con ciertos requerimientos específicos. Puede utilizar las validaciones para verificar que la información de sus modelos sea correcta antes de grabarlos a la base de datos, o utilizarlas directamente para validar los parámetros HTTP recibidos desde un formulario.

Cómo funcionan las validaciones en Play

Cada pedido HTTP tiene su propio objeto Validation en el cual se guardan los errores. Hay tres maneras de definir validaciones.

  1. En un método de un controlador, llamando métodos en el campo validation del controlador de manera directa. También puede acceder a un subconjunto de la API utilizando los métodos estáticos de la clase play.data.validation.Validation.
  2. Agregando anotaciones de validaciones a los parámetros declarados en la firma de los métodos de un controlador.
  3. Agregando la anotación @Valid a los métodos de acción que reciben un POJO como parámetro, y agregando las correspondientes anotaciones de validación en las propiedades del POJO.

El objeto Validation mantiene una colección de objetos de tipo play.data.validation.Error. Cada uno de estos errores cuenta con dos propiedades:

  • La clave o key. Esta propiedad ayuda a determinar cuál es el elemento que causó el error. La clave puede ser fijada arbitrariamente, pero cuando Play genera un error, por defecto utiliza el nombre de la variable Java.
  • El mensaje o message, el cual contiene la descripción textual del error detectado. El mensaje puede ser un texto plano o referirse a la clave de un mensaje internacionalizado.

Veamos cómo podemos validar un simple parámetro HTTP utilizando el primer enfoque:

public static void hello(String name) {
     validation.required(name);
     …
}

Este código verifica que la variable name haya sido correctamente establecida. En caso contrario, el correspondiente error será agregado a la colección de errores.

Puede repetir esta operación para cada validación que precise efectuar:

public static void hello(String name, Integer age) {
     validation.required(name);
     validation.required(age);
     validation.min(age, 0);
     …
}

Mensajes de error de las validaciones

Al finalizar la validación de la información, puede verificar si ha ocurrido algún error y desplegarlos en la consola:

public static void hello(String name, Integer age) {
     validation.required(name);
     validation.required(age);
     validation.min(age, 0);
     
     if(validation.hasErrors()) {
         for(Error error : validation.errors()) {
             System.out.println(error.message());
         }
     }
}

En caso de que name y age sean null, este controlador desplegaría:

Required
Required

Esto se debe a que el mensaje por defecto, definido en $PLAY_HOME/resources/messages, es:

validation.required=Required

Hay tres maneras de personalizar este mensaje de validación.

  1. Sobreescribiendo el mensaje por defecto, redefiniendo el mensaje en el archivo messages de su aplicación.
  2. Pasando un mensaje personalizado como un parámetro adicional a la validación.
  3. Brindando una clave en un archivo de internacionalización para ese mensaje, pasándolo como un parámetro adicional a la validación.

Mensajes de validación internacionalizados

La manera más simple de sobreescribir estos mensajes, es utilizar la misma clave del mensaje para un mensaje definido en el archivo conf/messages. Por ejemplo:

validation.required = Please enter a value 

También puede proveer mensajes en otros idiomas, tal como se describe en la página de Internacionalización.

Parámetros de los mensajes de validación

Puede utilizar un marcador de posición (placeholder) en el mensaje asociado con una clave de error:

validation.required=%s is required

De esta manera, la salida del error anterior sería:

name is required
age is required

Limitación: Play no puede determinar el nombre del parámetro cuando se define más de una validación de campo requerido utilizando la sintaxis validation.required(age). En ese caso, debe especificar el nombre del campo de la siguiente manera: validation.required("age", age).

Como clave del error se asume por defecto el nombre del parámetro, y es utilizao para buscar el mensaje. Por ejemplo, el parámetro name en el método de acción hello podría ser internacionalizado de la siguiente manera:

name = Customer name

De manera que la salida del error quedaría como:

Customer name is required
age is required

También puede sobreescribir la clave del error utilizando el método error.message(String key). Por ejemplo:

Error error = validation.required(name).error;
if(error != null) {
    System.out.println(error.message("Customer name"));
}

Buena parte de las validaciones que vienen incluidas con play aceptan parámetros de los mensajes adicionales que corresponden con los valores a ser validados. Por ejemplo, la validación ‘match’ acepta un segundo parámetro de tipo String para especificar la expresión regular a utilizar para validar el valor ingresado. Para diferenciarlo del parámetro especificado más arriba como %s, especificamos el índice ‘2’ de la siguiente manera:

validation.match=Must match %2$s 

De manera similar, una validación de ‘rangos’ define dos parámetros numéricos adicionales, con los índices 2 y 3 respectivamente:

validation.range=Not in the range %2$d through %3$d 

Consulte el archivo $PLAY_HOME/resources/messages para ver qué otras validaciones aceptan parámetros adicionales.

Mensajes de validación personalizados según el idioma (internacionalizados)

Los mensajes de validación definidos en el archivo $PLAY_HOME/resources/messages utilizan las claves por defecto de los mensajes para cada una de las validaciones predefinidas. Puede especificar diferentes claves para los mensajes, de la siguiente manera:

validation.required.em = You must enter the %s! 

Utilice esta nueva clave para los mensajes de la validación, pasándolo como parámetro al método message:

validation.required(manualKey).message("validation.required.em"); 

También puede utilizar esta clave en el parámetro message de la anotación:

public static void hello(@Required(message="validation.required.em") String name) { 
   …
} 

Puede utilizar la misma técnica con las anotaciones de validación en las propiedades de los JavaBeans:

public static void hello(@Valid Person person) { 
   …
} 
 
public class Person extends Model { 
   @Required(message = "validation.required.emphasis")
   public String name; 
   … 
} 

Mensajes de validación personalizados (sin internacionalizar)

Play retornará directamente la clave del mensaje en caso de no encontrar un mensaje asociado a dicha clave. Esto quiere decir que puede utilizar directamente un mensaje literal en vez de una clave, si así lo prefiere. Siguiendo el mismo ejemplo anterior, para especificar manualmente el mensaje a desplegar:

validation.required(manualKey).message("Give us a name!"); 

Y en caso de untilizar un método de acción con la anotación Required:

public static void save(@Required(message = "Give us a name!") String name) { 
   …
} 

De igual manera, para una propiedad de un JavaBean anotado:

public static void save(@Valid Person person) { 
   …
}
 
public class Person extends Model {
   @Required(message = "Give us a name!")
   public String name; 
   … 
} 

Mostrando errores de validación en un template

Por lo general, querrá desplegar los errores en un template de la vista. Puede acceder a los errores a través de la colección errors desde cualquier template. Algunos tags lo ayudarán a mostrar estos errores:

Veamos un ejemplo:

public static void hello(String name, Integer age) {
   validation.required(name);
   validation.required(age);
   validation.min(age, 0);
   render(name, age);
}

y en el template:

#{ifErrors}
 
   <h1>Oops…</h1>
 
   #{errors}
       <li>${error}</li>
   #{/errors}
 
#{/ifErrors}
#{else}
 
   Hello ${name}, you are ${age}.
 
#{/else}

Pero en un aplicación real, seguramente querrá volver a desplegar el formulario original. De manera que tendrá dos acciones: una para mostrar el formulario, y la otra para encargarse del POST.

Por supuesto que la validación ocurrirá en la segunda acción, y si se detecta algún error deberá redireccionar al usuario a la primera acción. En este caso precisará alguna manera de guardar los errores mientras ocurre el redirect. Para ello utilice el método validation.keep(). De esta manera la colección de errores estará disponible en la próxima acción.

Veamos un ejemplo real:

public class Application extends Controller {
 
   public static void index() {
      render();
   }
 
   public static void hello(String name, Integer age) {
      validation.required(name);
      validation.required(age);
      validation.min(age, 0);
      if(validation.hasErrors()) {
          params.flash(); // add http parameters to the flash scope
          validation.keep(); // keep the errors for the next request
          index();
      }
      render(name, age);
   }
 
}

Y el template view/Application/index.html:

#{ifErrors}
   <h1>Oops…</h1>
 
   #{errors}
       <li>${error}</li>
   #{/errors}
#{/ifErrors}
 
#{form @Application.hello()}
   <div>
      Name: <input type="text" name="name" value="${flash.name}" />
   </div>
   <div>
      Age: <input type="text" name="age" value="${flash.age}" /> 
   </div>
   <div>
      <input type="submit" value="Say hello" /> 
   </div>
#{/form}

Puede mejorar la experiencia de usuario mostrando cada mensaje junto con el campo que generó el error:

#{ifErrors}
   <h1>Oops…</h1>
#{/ifErrors}
 
#{form @Application.hello()}
   <div>
      Name: <input type="text" name="name" value="${flash.name}" />
      <span class="error">#{error 'name' /}</span>
   </div>
   <div>
      Age: <input type="text" name="age" value="${flash.age}" /> 
      <span class="error">#{error 'age' /}</span>
   </div>
   <div>
      <input type="submit" value="Say hello" /> 
   </div>
#{/form}

Anotaciones de validaciones

Las anotaciones disponibles en el paquete play.data.validation proveen una manera más concisa y simple de especificar validaciones. Para usar una de estas validaciones, simplemente anote el parámetro del método de acción del controlador con la correspondiente anotación:

public static void hello(@Required String name, @Required @Min(0) Integer age) {
   if(validation.hasErrors()) {
       params.flash(); // add http parameters to the flash scope
       validation.keep(); // keep the errors for the next request
       index();
   }
   render(name, age);
}

Validando objetos complejos

También puede utilizar estas anotaciones para agregar validaciones a las propiedades de sus objetos del modelo, y luego en el controlador especificar que todas las validaciones deben ser verificadas. Reescribamos el anterior ejemplo utilizando la clase de modelo User.

Primero, anotaremos la clase User agregando las validaciones de cada propiedad:

package models;
 
public class User {
    
    @Required
    public String name;
 
    @Required
    @Min(0)
    public Integer age;
}

Luego modificaremos el método de acción hello, el cual contará con la anotación @Valid para especificar que todas las propiedades del objeto User deben cumplir con las restricciones especificadas en las validaciones:

public static void hello(@Valid User user) {
   if(validation.hasErrors()) {
       params.flash(); // add http parameters to the flash scope
       validation.keep(); // keep the errors for the next request
       index();
   }
   render(name, age);
}

Y finalmente, el formulario modificado para desplegar los errores:

#{ifErrors}
   <h1>Oops…</h1>
#{/ifErrors}
 
#{form @Application.hello()}
   <div>
      Name: <input type="text" name="user.name" value="${flash['user.name']}" />
      <span class="error">#{error 'user.name' /}</span>
   </div>
   <div>
      Age: <input type="text" name="user.age" value="${flash['user.age']}" /> 
      <span class="error">#{error 'user.age' /}</span>
   </div>
   <div>
      <input type="submit" value="Say hello" /> 
   </div>
#{/form}

Validaciones incluidas en Play

Play cuenta con varias validaciones predefinidas incluidas en el package play.data.validation, y que puede utilizar utilizando el objeto Validation o mediante anotaciones.

Validaciones personalizadas utilizando @CheckWith

Si la validación que precisa no se encuentra en el paquere play.data.validation package, puede escribir sus propias validaciones. Para ello deberá utilizar la anotación genérica @CheckWith para usar su propia implementación del un objeto Check.

Por ejemplo:

public class User {
    
    @Required
    @CheckWith(MyPasswordCheck.class)
    public String password;
    
    static class MyPasswordCheck extends Check {
        
        public boolean isSatisfied(Object user, Object password) {
            return notMatchPreviousPasswords(password);
        }
    }
}

El mensaje de validación por defecto será validation.invalid. Para usar una clave diferente, llame al método Check.setMessage pasando una clave y un mensaje como parámetro.

static class MyPasswordCheck extends Check {
 
    public boolean isSatisfied(Object user, Object password) {
        final Date lastUsed = dateLastUsed(password);
        setMessage("validation.used", JavaExtensions.format(lastUsed));
        return lastUsed == null;
    }
}

Este método siempre tomará el nombre del campo como primer parámetro, y los parámetros de su propio mensaje como parámetros sucesivos. De forma tal que, en el ejemplo precedente, podría definir el mensaje de error de la siguiente manera:

validation.used = &{%1$s} already used on date %2$s
user.password = Password

en donde &{%1$s} se refiere al argumento con índice 1 (el nombre del campo) como la clave para buscar el texto del mensaje, mientras que %2$s es el segundo argumento del mensaje que se refiere a la fecha formateada.

La sintaxis %s, %s2$s and &{…} es explicada en a sección Obteniendo mensajes internacionalizados.

Anotaciones personalizadas

También puede escribir sus propias anotaciones de validación, lo cual es más complicado, pero simplifica el código del modelo, a la vez que le permite introducir parámetros a las validaciones.

Por ejemplo, supongamos que queremos una versión menos restrictiva de la validación @URL, de forma tal que podamos permitir URLs de diversos tipos, como puede ser un url del tipo file://, y con un parámetro que nos permita indicar qué tipo especifico de URL queremos permitir.

Primero, escribiremos una anotación de validación personalizada, con un parámetro para sobreescribir el mensaje por defecto:

import net.sf.oval.configuration.annotation.Constraint;
import java.lang.annotation.*;
 
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Constraint(checkWith = URICheck.class)
public @interface URI {
    String message() default URICheck.message;
}

Esta anotación se refiere a una implementación de net.sf.oval.configuration.annotation.AbstractAnnotationCheck.

public class URICheck extends AbstractAnnotationCheck<URI> {
 
    /** Error message key. */
    public final static String message = "validation.uri";
 
    /** URI schemes allowed by validation. */
    private List<String> schemes;
 
    @Override
    public void configure(URI uri) {
        setMessage(uri.message());
        this.schemes = Arrays.asList(uri.schemes());
    }
 
    /**
     * Add the URI schemes to the message variables so they can be included
     * in the error message.
     */
    @Override
    public Map<String, String> createMessageVariables() {
        final Map<String, String> variables = new TreeMap<String, String>();
        variables.put("2", JavaExtensions.join(schemes, ", "));
        return variables;
    }
 
    @Override
    public boolean isSatisfied(Object validatedObject, Object value,
        OValContext context, Validator validator) throws OValException {
 
        requireMessageVariablesRecreation();
        try {
            final java.net.URI uri = new java.net.URI(value.toString());
            final boolean schemeValid = schemes.contains(uri.getScheme());
            return schemes.size() == 0 || schemeValid;
        } catch (URISyntaxException e) {
            return false;
        }
    }
}

El método isSatisfied llama a requireMessageVariablesRecreation() para indicarle a OVal que llame a createMessageVariables() antes de emitir el mensaje. Éste retornará un mapa de variables que serán pasadas al formateador del mensaje. Las claves del mapa no serán utilizadas, el "2" en este ejemplo indica el índice del parámetro. Al igual que en el caso anterior, el primer parámetro es el nombre del campo.

Para utilizar esto, use la nueva anotación en alguna propiedad de un modelo.

public class User {
    
    @URI(message = "validation.uri.schemes", schemes = {"http", "https"})
    public String profile;
}

Y podemos definir los mensajes de la siguiente manera:

validation.uri = Not a valid URI
validation.uri.schemes = &{%1$s} is not a valid URI - allowed schemes are %2$s

Próximos pasos

La última capa de una aplicación de Play: Objetos del modelo.