Adición de validación

por Rick Anderson

Nota:

Existe una versión actualizada de este tutorial, disponible aquí, donde se usa la versión más reciente de Visual Studio. El nuevo tutorial utiliza ASP.NET Core MVC, que proporciona muchas mejoras en comparación con este tutorial.

En este tutorial se muestra ASP.NET Core MVC con controladores y vistas. Razor Pages es una nueva alternativa en ASP.NET Core, un modelo de programación basado en páginas que facilita la creación de interfaces de usuario web y hace que sea más productiva. Se recomienda probar el tutorial de las páginas de Razor antes que la versión MVC. El tutorial de las páginas de Razor:

  • Es más fácil de seguir.
  • Abarca más características.
  • Es el método preferido para el desarrollo de nuevas aplicaciones.

En esta sección, agregará lógica de validación al modelo Movie y garantizará que las reglas de validación se apliquen cada vez que un usuario intente crear o editar una película mediante la aplicación.

Respetar el principio DRY

Uno de los principales principios de diseño de ASP.NET MVC es DRY ("Una vez y solo una"). ASP.NET MVC le anima a que especifique la función o el comportamiento una sola vez y luego lo refleje en el resto de la aplicación. Esto reduce la cantidad de código que necesita escribir y hace que el código que escribe sea menos propenso a errores y sea más fácil de mantener.

La compatibilidad de validación proporcionada por ASP.NET MVC y Entity Framework Code First es un buen ejemplo del principio DRY. Puede especificar las reglas de validación mediante declaración en un lugar (en la clase del modelo) y las reglas se aplican en toda la aplicación.

Veamos cómo puede aprovechar esta compatibilidad de validación en la aplicación de películas.

Adición de reglas de validación al modelo Movie

Comenzará agregando alguna lógica de validación a la clase Movie.

Abra el archivo Movie.cs. Observe que el espacio de nombres System.ComponentModel.DataAnnotations no contiene System.Web. DataAnnotations proporciona un conjunto integrado de atributos de validación que puede aplicar mediante declaración a cualquier clase o propiedad (también contiene atributos de formato como DataType, que ayudan a aplicar formato y no proporcionan validación).

Ahora, actualice la clase Movie para aprovechar las ventajas de los atributos de validación integrados Required, StringLength, RegularExpression y Range. Reemplace la clase Movie por lo siguiente:

public class Movie
{
    public int ID { get; set; }

    [StringLength(60, MinimumLength = 3)]
    public string Title { get; set; }

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    public DateTime ReleaseDate { get; set; }
  
    [RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
    [Required]
    [StringLength(30)]
    public string Genre { get; set; }

    [Range(1, 100)]
    [DataType(DataType.Currency)]
    public decimal Price { get; set; }

    [RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
    [StringLength(5)]
    public string Rating { get; set; }
}

El atributo StringLength establece la longitud máxima de la cadena y establece esta limitación en la base de datos, por lo que el esquema de la base de datos cambiará. Haga clic con el botón derecho en la tabla Movies en el Explorador de servidores y haga clic en Abrir definición de tabla:

Screenshot that shows the Server Explorer window open and on the d b o dot Movies Design tab.

En la imagen anterior puede ver que todos los campos de cadena están establecidos en NVARCHAR (MAX). Usaremos migraciones para actualizar el esquema. Compile la solución, abra la ventana Consola del Administrador de paquetes y escriba los siguientes comandos:

add-migration DataAnnotations
update-database

Cuando este comando finalice, Visual Studio abrirá el archivo de clase que define la nueva clase derivada DbMigration con el nombre especificado (DataAnnotations) y, en el método Up, puede ver el código que actualiza las restricciones de esquema:

public override void Up()
{
    AlterColumn("dbo.Movies", "Title", c => c.String(maxLength: 60));
    AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false, maxLength: 30));
    AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
}

El campo Genre ya no admite valores NULL (es decir, debe escribir un valor). El campo Rating tiene una longitud máxima de 5 y Title tiene una longitud máxima de 60. La longitud mínima de 3 en Title y el intervalo en Price no creó cambios de esquema.

Examine el esquema Movie:

Screenshot that shows the d b o dot Movies Design tab. Title and Rating are checked in the Allow Nulls column.

Los campos de cadena muestran los nuevos límites de longitud y Genre ya no se comprueba como que admite valores NULL.

Los atributos de validación especifican el comportamiento que quiere aplicar en las propiedades del modelo al que se aplican. Los atributos Required y MinimumLength indican que una propiedad debe tener un valor, pero nada evita que un usuario escriba espacios en blanco para satisfacer esta validación. El atributo RegularExpression se usa para limitar los caracteres que se pueden escribir. En el código anterior, Genre y Rating solamente pueden usar letras (no se permiten espacios en blanco, números ni caracteres especiales). El atributo Range restringe un valor a un intervalo determinado. El atributo StringLength permite establecer la longitud máxima de una propiedad de cadena y, opcionalmente, su longitud mínima. Los tipos de valor (como decimal, int, float, DateTime) son intrínsecamente necesarios y no necesitan el atributo Required.

Code First garantiza que las reglas de validación que especifique en una clase de modelo se aplican antes de que la aplicación guarde los cambios en la base de datos. Por ejemplo, el código siguiente producirá una excepción DbEntityValidationException cuando se llame al método SaveChanges porque faltan varios valores de propiedad Movie necesarios:

MovieDBContext db = new MovieDBContext();
Movie movie = new Movie();
movie.Title = "Gone with the Wind";
db.Movies.Add(movie);
db.SaveChanges();        // <= Will throw server side validation exception

El código anterior produce la siguiente excepción:

Error de validación para una o varias entidades. Vea la propiedad "EntityValidationErrors" para más información.

Tener reglas de validación aplicadas automáticamente por .NET Framework ayuda a que la aplicación sea más sólida. También nos permite asegurarnos de que todo se valida y que no nos dejamos ningún dato incorrecto en la base de datos accidentalmente.

Interfaz de usuario de error de validación en ASP.NET MVC

Ejecute la aplicación y navegue a la URL /Movies.

Haga clic en el vínculo Crear nueva para agregar una nueva película. Rellene el formulario con algunos valores no válidos. En cuanto la validación del lado cliente de jQuery detecta el problema, muestra un mensaje de error.

8_validationErrors

Nota:

Para admitir la validación de jQuery para configuraciones regionales que no sean el inglés y que usan una coma (",") como separador decimal, debe incluir la globalización de NuGet tal como se ha descrito anteriormente en este tutorial.

Observe cómo el formulario ha usado automáticamente un color de borde rojo para resaltar los cuadros de texto que contienen datos no válidos y ha emitido un mensaje de error de validación adecuado junto a cada uno. Los errores se aplican en el lado cliente (con JavaScript y jQuery) y en el lado servidor (cuando un usuario tiene JavaScript deshabilitado).

Una ventaja importante es que no fue necesario cambiar ni una sola línea de código en la clase MoviesController o en la vista Create.cshtml para habilitar esta interfaz de usuario de validación. El controlador y las vistas que creó en pasos anteriores de este tutorial seleccionaron automáticamente las reglas de validación que especificó mediante atributos de validación en las propiedades de la clase del modelo Movie. Pruebe la aplicación mediante el método de acción Edit y se aplicará la misma validación.

Los datos del formulario no se envían al servidor hasta que no hay ningún error de validación del lado cliente. Puede comprobarlo colocando un punto de interrupción en el método HTTP Post, usando la herramienta fiddler o las herramientas de desarrollo F12 de IE.

Cómo se produce la validación en el método Create View y Create Action

Tal vez se pregunte cómo se generó la validación de la interfaz de usuario sin actualizar el código en el controlador o las vistas. En la lista siguiente se muestra el aspecto de los métodos Create de la clase MovieController. No cambian de la forma en que las creó anteriormente en este tutorial.

public ActionResult Create()
{
    return View();
}
// POST: /Movies/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Movies.Add(movie);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

El primer método de acción Create (HTTP GET) muestra el formulario de creación inicial. La segunda versión ([HttpPost]) controla el envío de formulario. El segundo método Create (la versión HttpPost) comprueba ModelState.IsValid para ver si la película tiene errores de validación. Al obtener esta propiedad se evalúan todos los atributos de validación que se hayan aplicado al objeto. Si el objeto tiene errores de validación, el método Create vuelve a mostrar el formulario. Si no hay ningún error, el método guarda la nueva película en la base de datos. En nuestro ejemplo de película, el formulario no se publica en el servidor cuando se detectan errores de validación en el lado cliente; nunca se llama al segundo métodoCreate. Si deshabilita JavaScript en el explorador, la validación de cliente se deshabilitará y el método HTTP POST Create obtiene ModelState.IsValid para comprobar si la película tiene errores de validación.

Puede establecer un punto de interrupción en el método HttpPost Create y comprobar si nunca se llama al método. La validación del lado cliente no enviará los datos del formulario cuando se detecten errores de validación. Si deshabilita JavaScript en el explorador y después envía el formulario con errores, se alcanzará el punto de interrupción. Puede seguir obteniendo validación completa sin JavaScript. En la imagen siguiente se muestra cómo deshabilitar JavaScript en Internet Explorer.

Screenshot that shows the Internet Options window open and on the security tab. The Custom level window is open and Active scripting is disabled.

Screenshot that shows the H t t p post and if Model state dot is valid is highlighted.

En la siguiente imagen se muestra cómo deshabilitar JavaScript en el explorador Firefox.

Screenshot that shows the Options window. Content is selected and Enable Java Script is circled in red.

En la siguiente imagen se muestra cómo deshabilitar JavaScript en el explorador Chrome.

Screenshot that shows the Java Script setting and the option to allow or disable.

Abajo se muestra la plantilla de vista Create.cshtml a la que se aplicó scaffolding en un paso anterior de este tutorial. Los métodos de acción que se muestran arriba la usan para mostrar el formulario inicial y para volver a mostrarlo en caso de error.

@model MvcMovie.Models.Movie
@{
    ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>
        @*Fields removed for brevity.*@        




        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Observe cómo el código usa un asistente Html.EditorFor para generar el elemento <input> para cada propiedad Movie. Junto a este asistente, se llama al método asistente Html.ValidationMessageFor. Estos dos métodos auxiliares funcionan con el objeto de modelo que pasa el controlador a la vista (en este caso, un objeto Movie). Buscan automáticamente los atributos de validación especificados en el modelo y muestran los mensajes de error según corresponda.

Lo realmente bueno de este enfoque es que ni el controlador ni la plantilla de vista Create saben que las reglas de validación actuales se están aplicando ni conocen los mensajes de error específicos que se muestran. Las reglas de validación y las cadenas de error solo se especifican en la clase Movie. Estas mismas reglas de validación se aplican automáticamente a la vista Edit y a cualquier otra vista de plantillas creada que edite el modelo.

Si desea cambiar la lógica de validación más tarde, puede hacerlo exactamente en un solo lugar mediante la adición de atributos de validación al modelo (en este ejemplo, la clase movie). No tendrá que preocuparse de que diferentes partes de la aplicación sean incoherentes con el modo en que se aplican las reglas: toda la lógica de validación se definirá en un solo lugar y se usará en todas partes. Esto mantiene el código muy limpio y hace que sea fácil de mantener y evolucionar. También significa que respeta totalmente el principio DRY.

Uso de atributos DataType

Abra el archivo Movie.cs y examine la clase Movie. El espacio de nombres System.ComponentModel.DataAnnotations proporciona atributos de formato además del conjunto integrado de atributos de validación. Ya hemos aplicado un valor de enumeración DataType en la fecha de lanzamiento y los campos de precio. En el código siguiente se muestran las propiedades ReleaseDate y Price con el atributo DataType adecuado.

[DataType(DataType.Date)] 
public DateTime ReleaseDate { get; set; }
       
[DataType(DataType.Currency)] 
public decimal Price { get; set; }

Los atributos DataType solo proporcionan sugerencias para que el motor de vista aplique formato a los datos (y suministre atributos como <a> para las direcciones URL y <a href="mailto:EmailAddress.com"> para el correo electrónico). Puede usar el atributo RegularExpression para validar el formato de los datos. El atributo DataTypeno es un atributo de validación, sino que se usa para especificar un tipo de datos más específico que el tipo intrínseco de la base de datos. En este caso solo se quiere realizar el seguimiento de la fecha, no de la fecha y la hora. La enumeración DataType proporciona muchos tipos de datos, comoDate, Time, PhoneNumber, Currency, EmailAddress, etc. El atributo DataType también puede permitir que la aplicación proporcione automáticamente características específicas del tipo. Por ejemplo, se puede crear un vínculo mailto: para DataType.EmailAddress y se puede proporcionar un selector de fechas para DataType.Date en exploradores compatibles con HTML5. Los atributos DataType emiten atributos data- de HTML 5 que los exploradores HTML 5 pueden comprender. Los atributos DataType no proporcionan ninguna validación.

DataType.Date no especifica el formato de la fecha que se muestra. De manera predeterminada, el campo de datos se muestra según los formatos predeterminados basados en el elemento CultureInfo del servidor.

El atributo DisplayFormat se usa para especificar el formato de fecha de forma explícita:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

El valor ApplyFormatInEditMode especifica que el formato indicado se debe aplicar también cuando el valor se muestra en un cuadro de texto para su edición. (Es posible que no le interese ese comportamiento para algunos campos, por ejemplo, para los valores de moneda, es posible que no quiera que el símbolo de la moneda se incluya en el cuadro de texto para modificarlo).

Puede usar el atributo DisplayFormat por sí solo, pero normalmente se recomienda usar también el atributo DataType. El atributo DataType transmite la semántica de los datos en contraposición a cómo se representan en una pantalla y ofrece las siguientes ventajas que no se obtienen con DisplayFormat:

  • El explorador puede habilitar características de HTML5 (por ejemplo, mostrar un control de calendario, el símbolo de moneda adecuado según la configuración regional, vínculos de correo electrónico, etc.).
  • De manera predeterminada, el explorador representa los datos con el formato correcto según la configuración regional.
  • El atributo DataType puede habilitar MVC para que elija la plantilla de campo adecuada para representar los datos (DisplayFormat, si se usa por sí solo, utiliza la plantilla de cadena). Para más información, vea Plantillas de ASP.NET MVC 2 de Brad Wilson. (Aunque está escrito para MVC 2, este artículo todavía se aplica a la versión actual de ASP.NET MVC).

Si usa el atributo DataType con un campo de fecha, también debe especificar el atributo DisplayFormat para asegurarse de que el campo se representa correctamente en los exploradores Chrome. Para más información, vea esta conversación de StackOverflow.

Nota:

La validación de jQuery no funciona con el atributo Range ni DateTime. Por ejemplo, el código siguiente siempre muestra un error de validación del lado cliente, incluso cuando la fecha está en el intervalo especificado:

[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]

Debe deshabilitar la validación de fechas de jQuery para usar el atributo Range con DateTime. Por lo general no se recomienda compilar fechas fijas en los modelos, así que se desaconseja el empleo del atributo Range y DateTime.

El código siguiente muestra la combinación de atributos en una línea:

public class Movie
{
   public int ID { get; set; }
   [Required,StringLength(60, MinimumLength = 3)]
   public string Title { get; set; }
   [Display(Name = "Release Date"),DataType(DataType.Date)]
   public DateTime ReleaseDate { get; set; }
   [Required]
   public string Genre { get; set; }
   [Range(1, 100),DataType(DataType.Currency)]
   public decimal Price { get; set; }
   [Required,StringLength(5)]
   public string Rating { get; set; }
}

En la siguiente parte de la serie de tutoriales, revisaremos la aplicación y realizaremos algunas mejoras a los métodos Details y Delete generados automáticamente.