Client Side Validation con ASP.NET MVC 2

En un post anterior, IDataErrorInfo y MVC, se hablaba de la posibilidad de validar nuestros objetos desde el modelo y comprobar el resultado de las validaciones través de ModelState.IsValid. Con ASP.NET MVC 2 llegaremos más allá de la mano de JQuery y Data Annotations.

Desde las primeras revisiones de esta segunda versión, tenemos la posibilidad de reutilizar las validaciones descritas en nuestros objetos en el lado del cliente. Para ello, necesitamos especificar las validaciones oportunas en los objetos, utilizando Data Annotations.
Siguiendo con los ejemplos basados en Twitter, en esta ocasión he creado un cliente que recupera el time line de tus amigos y además te permite responderlos o mandarles un mensaje directo.

Los mensajes directos se utilizan para realizar envíos privados a una persona en concreto. Para poder envíar un mensaje de este tipo, es necesario escribir un texto de tamaño limitado y, además, que tenga un destinatario.

using System.ComponentModel.DataAnnotations;

namespace ClientSideValidation.Models.Objects
{
public class DirectMessage
{
public long ID { get; set; }
[Required(ErrorMessage = "Tweet message is required!")]
[StringLength(131, ErrorMessage = "Your direct message is too long!")]
public string Message { get; set; }
public TwittUser Addressee { get; set; }
}
}

Los dos atributos, Required y StringLength, pertenecen a la propiedad Message donde se está indicando que el mismo es requerido y además que tiene un tamaño máximo de 131 caracteres.
La última propiedad corresponde al remitente, el cual tiene sus propias validaciones en la clase correspondiente.

using System.ComponentModel.DataAnnotations;

namespace ClientSideValidation.Models.Objects
{
public class TwittUser
{
public long ID { get; set; }
[Required(ErrorMessage = "User name is required!")]
public string UserName { get; set; }
public string PhotoURL { get; set; }
public string Status { get; set; }
}
}

Quizás la opción de decorar las propiedades de una clase dificultan la compresión de la misma. Si queremos hacer una separación de las validaciones, podemos hacer uso del atributo MetaDataType creando una clase independiente.

using System.ComponentModel.DataAnnotations;

namespace ClientSideValidation.Models.Objects
{
[MetadataType(typeof(TweetReplyMetadata))]
public class TweetReply
{
public long ID { get; set; }
public string Text { get; set; }
public TwittUser Addressee { get; set; }
public Tweet TweetToReply { get; set; }
}
}

Se decorda la principal indicando en el typeof la clase que contiene las validaciones.

using System.ComponentModel.DataAnnotations;

namespace ClientSideValidation.Models.Objects
{
public class TweetReplyMetadata
{
[Required(ErrorMessage = "Reply message is required!")]
[StringLength(131, ErrorMessage = "Your reply is too long!")]
public string Text { get; set; }
}
}

Por otro lado, debemos generar el controlador donde seguiremos utilizando ModelState para comprobar el estado de las validaciones desde el servidor.

using System.Web.Mvc;
using ClientSideValidation.Models;
using ClientSideValidation.Models.Objects;

namespace ClientSideValidation.Controllers
{
public class TweetController : Controller
{
private readonly ITweetService _twitterService;

public TweetController()
{
_twitterService = new TweetService();
}

public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}


public ActionResult Reply(long id)
{
var reply = new TweetReply { TweetToReply = _twitterService.ShowTweet(id) };
return View(reply);
}

[HttpPost]
public ActionResult Reply(TweetReply reply)
{
if (ModelState.IsValid)
{
_twitterService.ReplyTweet(reply);
return RedirectToAction("Index");
}
return View(reply);
}

public ActionResult SendDM(long ID)
{
var DM = new DirectMessage { Addressee = _twitterService.RetrieveUser(ID) };
return View(DM);
}

[HttpPost]
public ActionResult SendDM(DirectMessage DM)
{
if (ModelState.IsValid)
{
_twitterService.SendDM(DM);
return RedirectToAction("Index");
}
return View(DM);
}
}
}

Una vez creadas las vistas, podríamos intentar el envío de un mensaje directo (sin introducir el texto del mismo) y obtendríamos el siguiente resultado:

Actualmente, cuando pulsamos en el botón Send, estamos acudiendo al servidor y ModelState está comprobando si nuestros objetos son válidos o no para mantenernos en la misma vista y mostrar el error o regresar de nuevo al Time Line. Quizás, si un usuario hace una acción de este tipo, no nos interesa siquiera que acuda al servidor. Aquí es donde viene la mágia 🙂

  1. Referenciamos las librerías javascript, ubicadas en la carpeta Scripts, en la Master Page en este orden:
    <%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
    <title>
    <asp:ContentPlaceHolder ID="TitleContent" runat="server" />
    </title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />

    <script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>

    <script src="../../Scripts/jquery.validate.min.js" type="text/javascript"></script>

    <script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>

    <script src="../../Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>

    <script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>


    </head>

  2. Habilitamos la línea Html.EnableClientValidation(); en las vistas donde queramos realizar la validación en el lado del cliente.
    <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ClientSideValidation.Models.Objects.DirectMessage>" %>

    <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Direct Message
    </asp:Content>
    <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>
    Direct Message</h2>
    <%Html.EnableClientValidation(); %>
    <% using (Html.BeginForm())
    {%>
    <div id="tweet">
    <img src="<%=Model.Addressee.PhotoURL%>" />
    <div>
    <%=Html.DisplayFor(m=>m.Addressee.Status) %>
    </div>
    <%=Html.HiddenFor(m=>m.Addressee.ID) %>
    </div>
    <fieldset>
    <legend>
    <%=Model.Addressee.UserName %></legend>
    <div class="editor-label">
    <%= Html.LabelFor(model => model.Message) %>
    </div>
    <div class="editor-field">
    <%= Html.TextAreaFor(model => model.Message) %>
    <%= Html.ValidationMessageFor(model => model.Message) %>
    </div>
    <p>
    <input type="submit" class="buttonStyle" value="Send!" />
    </p>
    </fieldset>
    <% } %>
    <div>
    <%=Html.ActionLink("Back to TimeLine", "Index") %>
    </div>
    </asp:Content>

Si lanzamos de nuevo la aplicación, y realizamos la misma operación que antes ¡El resultado es exactamente el mismo pero sin ir al servidor!
Si observamos el código fuente de la página, observamos que la forma de trabajar es mediante la creación de un objeto JSON donde almacena cada unas de las validaciones alojadas en nuestras clases.

//<![CDATA[CommunityServer
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"FieldName":"Message","ReplaceValidationMessageContents":true,"ValidationMessageId":"form0_Message_validationMessage","ValidationRules":[{"ErrorMessage":"Your direct message is too long!","ValidationParameters":{"minimumLength":0,"maximumLength":131},"ValidationType":"stringLength"},{"ErrorMessage":"Tweet message is required!","ValidationParameters":{},"ValidationType":"required"}]}],"FormId":"form0","ReplaceValidationSummary":false});
//]]>

Como siempre, facilito el código fuente para poder probar el ejemplo completo.

¡Saludos y feliz 2010!

2 comentarios en “Client Side Validation con ASP.NET MVC 2”

  1. Me ha arrancado una carcajada el ver a HardBit en la imagen de tu aplicación… maldito twitter, hace el mundo mas pequeño… Aaa, como siempre, excelente información… Gracias

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *