Extreme ASP.NET

Validación de modelo y metadatos en ASP.NET MVC 2

K. Scott Allen

Descargar el ejemplo de código

Una de las características que se agregaron a la versión ASP.NET MVC 2 es la capacidad de validar entradas de usuario en el servidor y el cliente. Todo lo que es necesario hacer es proporcionar al marco información acerca de los datos que necesita validar; y el marco se encargará del trabajo duro y los detalles.

Esta característica representa una gran ayuda para aquellos de nosotros que escribimos código de validación personalizado y enlazadores de modelo personalizados para realizar una validación de modelo simple con ASP.NET MVC 1.0. En este artículo, analizaré la compatibilidad de validación integrada en ASP.NET MVC 2.

Sin embargo, antes de analizar las nuevas capacidades, voy a volver a revisar la metodología antigua. Las características de validación en ASP.NET WebForms me han servido mucho durante varios años. Creo que es útil revisarlas para entender qué proporciona un marco de validación ideal.

Control de validación

Si alguna vez ha utilizado ASP.NET WebForms, sabe que es relativamente fácil agregar lógica de validación a un WebForm. Exprese las reglas de validación mediante el uso de controles. Por ejemplo, si desea asegurarse de que el usuario ingrese texto en un control TextBox, sólo agregue un control RequiredFieldValidator que apunte a TextBox de la siguiente manera:

<form id="form1" runat="server">
  <asp:TextBox runat="server" ID="_userName" />
  <asp:RequiredFieldValidator runat="server" ControlToValidate="_userName"
                               ErrorMessage="Please enter a username" />
  <asp:Button runat="server" ID="_submit" Text="Submit" />
</form>

RequiredFieldValidator encapsula lógica del lado cliente y del lado servidor para asegurarse de que el usuario proporcione un nombre de usuario. Para proporcionar validación del lado cliente, el control emite JavaScript en el explorador del cliente y este script asegura que el usuario satisfaga todas las reglas de validación antes de volver a publicar el formulario en el servidor.

Piense en lo que ofrecen estos controles de validación de WebForm: son increíblemente eficaces.

  • Puede expresar reglas de validación mediante declaración para una página en una sola ubicación.
  • Una validación de cliente evita un viaje de ida y vuelta al servidor si el usuario no cumple las reglas de validación.
  • La validación de servidor impide que un usuario malintencionado burle el script de cliente.
  • La lógica de validación de servidor y de cliente permanece en sincronización sin transformarse en un problema de mantenimiento.

Pero en ASP.NET MVC, puede usar estos controles de validación y mantenerse fiel al espíritu del modelo de diseño de MVC. Afortunadamente, con la versión 2 del marco, existe algo incluso mejor.

Controles frente a modelos

Puede pensar en un control WebForm, como TextBox, como un simple contenedor para datos de usuario. Puede rellenar el control con un valor inicial y mostrar el valor al usuario, y puede recuperar cualquier valor que el usuario escriba o edite mediante la inspección del control después de una devolución (postback). Cuando use el modelo de diseño de MVC, M (modelo) cumple este mismo rol que un contenedor de datos. Rellene un modelo con información que necesite entregar a un usuario y éste devolverá los valores actualizados a su aplicación. De ese modo, el modelo es un lugar ideal para expresar reglas de validación y restricciones.

Este es un ejemplo estándar. Si crea una nueva aplicación ASP.NET MVC 2, uno de los controladores que encontrará en el nuevo proyecto es AccountController. Es responsable de controlar las nuevas solicitudes de registro de usuario, así como también solicitudes de inicio de sesión y de cambio de contraseña. Cada una de estas acciones usa un objeto de modelo dedicado. Puede encontrar estos modelos en el archivo AccountModels.cs en la carpeta Modelos. Por ejemplo, la clase RegisterModel, sin reglas de validación, es similar a esto:

public class RegisterModel
{
  public string UserName { get; set; }
  public string Email { get; set; }
  public string Password { get; set; }
  public string ConfirmPassword { get; set; }
}

La acción Register de AccountController toma una instancia de esta clase RegisterModel como un parámetro:

[HttpPost]
public ActionResult Register(RegisterModel model)
{
    // ...
}

Si el modelo es válido, la acción Register reenvía la información del modelo a un servicio que pueda crear un nuevo usuario.

El modelo RegisterModel es un buen ejemplo de un modelo específico de vista o un modelo de vista. No es un modelo para trabajar con una tabla de base de datos específica, una llamada de servicio web o un objeto de negocios. En lugar de eso, está diseñado para trabajar con una vista específica (la vista Register.aspx, parte de la cual se muestra en la figura 1). Cada propiedad en el modelo se asigna a un control de entrada en la vista. Le recomiendo que use modelos de vista, ya que simplifican varios escenarios en el desarrollo de MVC, incluida la validación.


Figura 1 Información de Register

Sobre modelos y metadatos

Cuando el usuario escribe información de cuenta en la vista Register, el marco de MVC se asegura de que el usuario proporcione UserName y Email. El marco también se asegura de que coincidan las cadenas Password y ConfirmPassword y que la contraseña tenga una longitud mínima de seis caracteres. ¿Cómo hace todo esto? Inspeccionando y actuando en metadatos adjuntos a la clase RegisterModel. En la figura 2 se muestra la clase RegisterModel con sus atributos de validación.

Figura 2 La clase RegisterModel con atributos de validación

[PropertiesMustMatch("Password", "ConfirmPassword", 
  ErrorMessage = "The password and confirmation password do not match.")]
public class RegisterModel
{
  [Required]        
  public string UserName { get; set; }

  [Required]
  public string Email { get; set; }

  [Required]
  [ValidatePasswordLength]
  public string Password { get; set; }

  [Required]
  public string ConfirmPassword { get; set; }
}

Cuando el usuario envía la vista Register, el enlazador de modelo predeterminado en ASP.NET MVC intentará crear una nueva instancia de la clase RegisterModel para transmitir como el parámetro a la acción Register de AccountController. El enlazador de modelo recupera información en la solicitud actual para rellenar el objeto RegisterModel. Por ejemplo, puede encontrar automáticamente el valor POST de un control de entrada de HTML denominado UserName y utilizar ese valor para rellenar la propiedad UserName de RegisterModel. Este comportamiento ha estado en ASP.NET MVC desde la versión 1.0, de modo que no será una novedad que ya haya utilizado el marco.

Las novedades en la versión 2 son cómo el enlazador de modelo predeterminado también preguntará a un proveedor de metadatos si existen metadatos disponibles para el objeto RegisterModel. Este proceso finalmente produce objeto derivado ModelMetaData, cuyo propósito es describir no sólo las reglas de validación asociadas al modelo, sino que también información relacionada con la presentación del modelo en una vista. Brad Wilson, miembro del equipo de ASP.NET, escribió una serie de publicaciones especializadas sobre cómo estos metadatos de modelo pueden influir en la presentación de un modelo a través de plantillas. La primera publicación de la serie se encuentra disponible en bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html.

Una vez que el enlazador de modelo tiene un objeto ModelMetaData asociado al modelo, puede utilizar los metadatos de validación internos para validar el objeto de modelo. De manera predeterminada, ASP.NET MVC usa los metadatos desde atributos de anotación de datos como [Required]. Por supuesto, ASP.NET MVC es acoplable y extensible, de modo que si desea concebir una fuente diferente para los metadatos de modelo, puede implementar su propio proveedor de metadatos. Ben Scheirman entrega importante información sobre este tema en el artículo “Personalización de ASP.NET MVC 2: metadatos y validación”, disponible en dotnetslackers.com/articles/aspnet/customizing-asp-net-mvc-2-metadata-and-validation.aspx.

Anotaciones de datos

En resumen, puede crear sus propios atributos de validación, como se observará más adelante, pero [Required] es uno de varios atributos de validación estándar que residen en el ensamblado System.ComponentModel.DataAnnotations. En la figura 3 aparece una lista completa de los atributos de validación del ensamblado de anotaciones.

Figura 3 Atributos de validación del ensamblado de anotaciones

Atributo Descripción
StringLength Especifica la longitud máxima de la cadena permitida en el campo de datos.
Required Especifica que se necesita un valor de campo de datos.
RegularExpression Especifica que un valor de campo de datos debe coincidir con la expresión regular especificada.
Range Especifica las restricciones de intervalo numérico para el valor de un campo de datos.
DataType Especifica el nombre de un tipo adicional para asociarlo a un campo de datos (uno de los valores enumerados DataType, como EmailAddress, Url o Password).

Estos atributos de anotación de datos se vuelven rápidamente dominantes en todo Microsoft .NET Framework. No sólo puede usar estos atributos en una aplicación ASP.NET MVC, sino que los servicios de ASP.NET Dynamic Data, Silverlight y Silverlight RIA también los entienden.

Vista de validaciones

Con los metadatos de validación implementados, los errores aparecerán automáticamente en una vista cuando el usuario escriba datos incorrectos. En la figura 4 se muestra cuál es el aspecto de la vista Register cuando un usuario obtiene Register sin suministrar ninguna información.


Figura 4 Error de validación

La presentación en la figura 4 se creó usando algunas de las nuevas aplicaciones auxiliares HTML en ASP.NET MVC 2, que incluye la aplicación auxiliar ValidationMessageFor. ValidationMessageFor controla la colocación de un mensaje de validación cuando hay un error de validación en un campo de datos específico. En la figura 5 se muestra un extracto de Register.aspx que demuestra cómo utilizar las aplicaciones auxiliares ValidationMessageFor y ValidationSummary.

Figura 5 Cómo usar las nuevas aplicaciones auxiliares HTML

<% using (Html.BeginForm()) { %>
    <%= Html.ValidationSummary(true, "Account creation was unsuccessful. " +
    "Please correct the errors and try again.") %>
    <div>
        <fieldset>
            <legend>Account Information</legend>
            
            <div class="editor-label">
                <%= Html.LabelFor(m => m.UserName) %>
            </div>
            <div class="editor-field">
                <%= Html.TextBoxFor(m => m.UserName) %>
                <%= Html.ValidationMessageFor(m => m.UserName) %>
            </div>

Validaciones personalizadas

No todos los atributos de validación en la clase RegisterModel son atributos del ensamblado de anotaciones de datos de Microsoft. [PropertiesMustMatch] y [ValidatePasswordLength] son atributos personalizados que encontrará definidos en el mismo archivo AccountModel.cs que tiene la clase RegisterModel. No debe preocuparse con respecto a los proveedores de metadatos personalizados o las clases de metadatos si sólo desea proporcionar una regla de validación personalizada. Todo lo que necesita hacer es derivar de la clase abstracta ValidationAttribute y proporcionar una implementación para el método IsValid. La implementación del atributo ValidatePasswordLength se muestra en la figura 6.

Figura 6 Implementación del atributo ValidatePasswordLength

[AttributeUsage(AttributeTargets.Field | 
                AttributeTargets.Property, 
                AllowMultiple = false, 
                Inherited = true)]
public sealed class ValidatePasswordLengthAttribute 
    : ValidationAttribute
{
    private const string _defaultErrorMessage = 
        "’{0}’ must be at least {1} characters long.";

    private readonly int _minCharacters = 
        Membership.Provider.MinRequiredPasswordLength;

    public ValidatePasswordLengthAttribute()
        : base(_defaultErrorMessage)
    {
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentUICulture, 
            ErrorMessageString,
            name, _minCharacters);
    }

    public override bool IsValid(object value)
    {
        string valueAsString = value as string;
        return (valueAsString != null && 
            valueAsString.Length >= _minCharacters);
    }
}

El otro atributo, PropertiesMustMatch, es un gran ejemplo de un atributo de validación que puede aplicar en el nivel de clase para realizar validaciones en todas las propiedades.

Validación de cliente

La validación RegisterModel que hemos observado hasta ahora tiene lugar en el servidor. Afortunadamente, también es fácil habilitar la validación en el cliente. Intento usar validación de cliente cada vez que sea posible porque puede proporcionar a un usuario comentarios rápidos mientras descarga algún trabajo desde mi servidor. Sin embargo, la lógica del lado servidor necesita permanecer implementada, en caso de que alguien no tenga scripting habilitado en un explorador (o que intente enviar de forma intencional datos malintencionados al servidor).

La habilitación de la validación de cliente es un proceso de dos pasos. El paso 1 se asegura de que la vista incluya los scripts de validación adecuados. Todos los scripts que necesita residen en la carpeta Scripts de una nueva aplicación MVC. El script MicrosoftAjax.js es el núcleo de las bibliotecas de Microsoft AJAX y es el primer script que deberá incluir. El segundo script es MicrosoftMvcValidation.js. Generalmente, agrego ContentPlaceHolder a la página principal de mi aplicación MVC para contener scripts, como se muestra a continuación:

<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat=
"server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type=
"text/css" />

    <asp:ContentPlaceHolder ID="Scripts" runat="server">
       
    </asp:ContentPlaceHolder>
    
</head>

Después, una vista puede incluir los scripts mediante el uso de un control Content. El siguiente código asegurará que los scripts de validación estén presentes:

<asp:Content ContentPlaceHolderID="Scripts" runat="server">
    <script src="../../Scripts/MicrosoftAjax.js" 
            type="text/javascript"></script>
    <script src="../../Scripts/MicrosoftMvcValidation.js" 
            type="text/javascript"></script>
</asp:Content>

El segundo paso en el uso de validación del lado cliente es invocar el método auxiliar HTML EnableClientValidation dentro de la vista en que se necesite compatibilidad de validación. Asegúrese de invocar este método antes de usar la aplicación auxiliar HTML BeginForm, como se muestra a continuación:

<%
       Html.EnableClientValidation(); 
       using (Html.BeginForm())
        {
     %>
     
     <!-- the rest of the form ... -->
     
     <% } %>

Observe que la lógica de validación del lado cliente sólo funciona con los atributos de validación integrados. Para la vista Register, esto significa que la validación del lado cliente asegurará que los campos necesarios estén presentes, pero no sabrá cómo validar la longitud de la contraseña o cómo confirmar que los dos campos de contraseña coinciden. Afortunadamente, es fácil agregar una lógica de validación personalizada de JavaScript que se conecte al marco de validación de ASP.NET MVC JavaScript. Phil Haack entrega detalles en su entrada de blog, “Validación personalizada de ASP.NET MVC 2”, que se encuentra disponible en haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx.

En conclusión, puede ver que la compatibilidad integrada para escenarios de validación comunes es una enorme nueva adición para ASP.NET MVC 2. Las reglas de validación no sólo son fáciles de agregar mediante atributos en un objeto de modelo, pero las características de validación mismas son flexibles y fáciles de ampliar. Empiece a aprovechar estas características para ahorrar tiempo y líneas de código con su siguiente aplicación ASP.NET MVC.

K. Scott Allenes miembro del equipo técnico de Pluralsight y fundador de OdeToCode. Puede ponerse en contacto con Allen en scott@OdeToCode.com, leer su blog en odetocode.com/blogs/scott o seguirlo en twitter.com/OdeToCode.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Brad Wilson