Mostrando entradas con la etiqueta mvc. Mostrar todas las entradas
Mostrando entradas con la etiqueta mvc. Mostrar todas las entradas

viernes, 11 de mayo de 2012

ASP.NET MVC4 Validando un campo que depende de otro

Parece que estoy destinado a tropezarme con algunos problemas cada vez que quiero validar algo en las clases modelo de mis vistas. Lla parte positiva es que uno sigue aprendiendo, así que, vamos al lio. Como siempre, supongamos esta vista y este modelo simplificados

Vista
<h2>Index</h2>
@using (Html.BeginForm())
{
 <div>Otros @Html.CheckBoxFor(m => m.Otros)</div>
 <div>Comentario @Html.TextBoxFor(m => m.Comentario)</div>
 <div>@Html.ValidationMessageFor(m => m.Comentario)</div>
 <input type="submit" value="Enviar" />
}
Modelo
public class IndexModel
{
 public bool Otros { get; set; }
 public string Comentario { get; set; }
}
Como siempre, nada raro. Se trata de validar el campo Comentario siempre y cuando el valor de campo Otros sea verdadero. Una vez más busqué algo que de serie me hiciera esta validación pero no encontré nada, así que una vez más me tocó hacer mi propio validador. Para crear un validador simplemente tenemos que heredar de la clase ValidationAttribute y sobreescribir el método IsValid. Pero tendremos que sobreescribir el método IsValid que tiene dos argumentos (object value y ValidationContext validationContext) ya que tenemos que acceder al contexto de validación para obtener el valor de una propiedad. El esqueleto básico del validador sería algo así
public class RequiredIfTrue : ValidationAttribute
{
  protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  {
    return base.IsValid(value, validationContext);
  }
}
Bien, lo primero que haremos será definir una propiedad (TrueOrFalsePropertyName) en nuestro validador para indicar el nombre de la propiedad enlazada. En nuestro caso, es la propiedad que deberá tener el valor "verdadero" para seguir con la validación, o sea, la propiedad Otros. Luego, en el método IsValid, accederemos a esa propiedad, obtendremos su valor, y comprobaremos si tenemos o no que continuar con la validación. También haremos algunas validaciones como que el nombre de la propiedad es válido y que el tipo que devuelve es un boolean En estos casos, he optado por lanzar una excepción para detener el flujo del programa ya que considero que son fallos de "programación" y que estos fallos no llegarían al usuario final. Con estás premisas nuestro validador queda de la siguiente forma
public class RequiredIfTrue : ValidationAttribute
{
 public string TrueOrFalsePropertyName { get; set; }

 protected override ValidationResult IsValid(object value, ValidationContext validationContext)
 {
  if (string.IsNullOrEmpty(TrueOrFalsePropertyName))
   throw new Exception("No se ha especificado la propiedad 'TrueOrFalsePropertyName'");

  // Obtenemos la propiedad
  PropertyInfo property = validationContext.ObjectType.GetProperty(TrueOrFalsePropertyName);
  if (property == null) 
   throw new Exception(string.Format("No se encuentra la propiedad '{0}'", TrueOrFalsePropertyName));
  
  // Comprobamos que la propiedad sea de tipo System.Boolean
  if (property.PropertyType != typeof(System.Boolean))
   throw new Exception(string.Format("La propiedad '{0}' no es de tipo System.Boolean", TrueOrFalsePropertyName));

  bool pValue = (bool)property.GetValue(validationContext.ObjectInstance, null);

  // Comprobamos el valor
  if (pValue && (value == null || (string)value == string.Empty)) return new ValidationResult(ErrorMessage);
  
  return null;
 }
}
Y el funcionamiento del validador se puede ver en las siguientes imágenes.



Si no marcamos el campo otros da igual el valor


Si marcamos el campo Otros, el campo Comentario es obligatorio.


Todo correcto otra vez

Happy coding!

Nota: Y para estrenar mi cuenta en SkyDrive, se pueden descargar el código fuente de los siguientes enlaces.
MvcApplication1.rar
MvcApplication1.zip

viernes, 4 de mayo de 2012

ASP.NET MVC4 Validando que un campo sea true o checked

Parece que todo lo que me propongo hacer últimamente se convierte en una odisea, por muy fácil que pueda parecer al principio. Tras mi anterior aventura para validar que dos campos sean iguales en un formulario de registro esta vez le ha tocado el turno a la validación de un valor en un checkbox. Supongamos una vez más un modelo bastante simple

Modelo
public class IndexModel
{
  public bool AceptoCondiciones { get; set; }
}
Vista
@using (Html.BeginForm())
{   
  <div>@Html.CheckBoxFor(m => m.AceptoCondiciones) Acepto las condiciones de uso</div> 
  <div>@Html.ValidationMessageFor(m => m.AceptoCondiciones)</div>
 
  <input type="submit" value="Enviar" />
}
La idea es comprobar que el usuario acepta las condiciones de uso. Algo bastante típico en formularios de registro. La primera idea fue marcar la propiedad AceptoCondiciones con el DataAnnotation [Required], pero tras escribirlo (no hizo falta ni probarlo) me di cuenta que no iba a funcionar, ya que el checkbox siempre tiene un valor, o verdadero o falso (marcado o desmarcado) por lo que la regla Required no nos valdría.

Tras mirar y mirar por google y darme cuenta que ya llevaba bastante tiempo perdido no me quedo más remedio que lanzarme a hacer mi propio validador. Para hacer una validador propio tan solo debemos heredar de la clase ValidationAttribute y sobreescribir el método IsValid. Pensando en el futuro añadí una propiedad ValidValue que nos indica cual es el valor que damos por válido, no sea que alguna vez necesite tener desmarcado un check. Con esto el validador quedaría así
public class IsTrueOrFalseAttribute : ValidationAttribute
{
  /// 
  /// Valor permitido
  /// 
  public bool ValidValue { get; set; }

  public override bool IsValid(object value)
  {
    if (value == null || value.GetType() != typeof(bool)) return false;

    return ((bool)value == ValidValue ? true : false);
  }
}
Y añadimos el atributo al modelo
public class IndexModel
{
  [IsTrueOrFalse(ValidValue=true)]
  public bool AceptoCondiciones { get; set; }
}
Con esto esto ya podemos probar nuestro validador y ver que funciona de la manera esperada


Pero, siempre hay un pero, este validador presenta un problema y es que siempre irá al servidor para realizar la validación. En mi caso no hay problema porque en el formulario de registro siempre voy al servidor para comprobar algunas cosas, como el nombre de usuario, pero en otros escenarios puede ser que no tengamos que ir al servidor para nada, e ir para solo comprobar si un checkbox está marcado es innecesario.


Como me gusta investigar y aprender cosas nuevas, decidí hacer que mi validador también tuviera soporte para cliente. La verdad es que esta parte está bastante mal documentada pero básicamente lo primero que tenemos que hacer es que nuestro validador implemente la interfaz IClientValidatable. En nuestro caso el validador quedaría de la siguiente manera
public class IsTrueOrFalseAttribute : ValidationAttribute, IClientValidatable
{
  /// 
  /// Valor permitido
  /// 
  public bool ValidValue { get; set; }

  public override bool IsValid(object value)
  {
    if (value == null || value.GetType() != typeof(bool)) return false;

    return ((bool)value == ValidValue ? true : false);
  }

  IEnumerable<modelclientvalidationrule> IClientValidatable.GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
  {
    var rule = new ModelClientValidationRule
    {
        ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.PropertyName) : ErrorMessage,
        ValidationType = "istrueorfalse"
    };
    // Transformamos el valor boolean a un string en minúsculas para que no falle la transformación en cliente de string a boolean.
    rule.ValidationParameters["validvalue"] = (this.ValidValue ? "true" : "false");

    yield return rule;
  }
}
Y luego en un fichero .js debemos añadir el siguiente código
$.validator.addMethod("istrueorfalse", function (value, element, param) {
  // Comparamos el valor checked del elemento con el valor permitido pasado por parámetro
  return element.checked == Boolean(param.validvalue);
});

$.validator.unobtrusive.adapters.add('istrueorfalse', ['validvalue'], function (options) {
  options.rules['istrueorfalse'] = options.params;
  options.messages['istrueorfalse'] = options.message;
});
Si ejecutamos nuestro código veremos que realiza la validación sin la necesidad de ir a cliente.

Nota: Para activar las validaciones en cliente (pese a que en ASP.NET MVC4 ya viene por defecto) debemos asegurarnos que tenemos estas lineas en el web.config
<appSettings>
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
También hay que comprobar que tenemos añadidas las referencias a los scripts jquery-1.6.2.js, jquery.validate.js, jquery.validate.unobtrusive.js y al fichero que creemos para añadir las lineas donde añadimos el validador y el adaptador.

Como vemos tampoco es extraordinariamente complicado validar nuestros datos en cliente así que, lo mejor que podemos hacer es hacerlo...

Happy coding!

miércoles, 2 de mayo de 2012

ASP.NET MVC4 Validando que dos campos sean iguales

Algo muy típico en el registro de usuarios es tener que introducir la contraseña dos veces para asegurarnos que la contraseña introducida es la misma. Lo normal sería poner esta validación como un DataAnnotation en la clase modelo con la que trabaje nuestra vista. Lo que vendría a ser algo así

Modelo
using System.ComponentModel.DataAnnotations;

public class IndexModel
{
  [Required(ErrorMessage = "La constraseña es obligatoria")]
  public string Password { get; set; }
  public string ConfirmPassword { get; set; }
}
Vista
@using (Html.BeginForm())
{
  <div class="editor-label">
    <label>Password</label>
    @Html.TextBoxFor(m => m.Password)
    @Html.ValidationMessage("Password")
  </div>
  <div class="editor-label">
    <label>Confirm password</label>
    @Html.TextBoxFor(m => m.ConfirmPassword)
    @Html.ValidationMessage("ConfirmPassword")
  </div>
  <input type="submit" value="Submit" />
}
Como vemos es un modelo muy simple en el que la contraseña es obligatoria y en el que tenemos que validar que ambas contraseñas introducidas sean iguales. Pues bien, lo primero que hice fue mirar en los DataAnnotations si había algún modificador para lo que yo estaba buscando, y cual fue mi sorpresa al ver que no había nada.

Lo siguiente que se me ocurrió fue hacer un validador personalizado, pero tras mirar como se hace me pareció que esto era demasiado complicado para lo que yo quería hacer y que seguro que algo tenía que haber en el framework para resolverlo.

Tras mucho indagar logré encontrarlo y en atributo en concreto se llama Compare (que original), pero en vez de estar definiddo en el namespace System.ComponentModel.DataAnnotations está definido en el namespace System.Web.Mvc. Así que nuevo modelo quedaría definido de la siguiente manera
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

public class IndexModel
{
  [Required(ErrorMessage = "La constraseña es obligatoria")]
  [Compare("ConfirmPassword", ErrorMessage = "Las constraseñas no son iguales")]
  public string Password { get; set; }
  public string ConfirmPassword { get; set; }
}
Mucho más simple que hacer nosotros el validador.
Happy coding!


sábado, 18 de febrero de 2012

Ya tenemos aquí la beta de ASP.NET MVC4

Desde hace un par de días ya tenemos disponible la versión beta de ASP.NET MVC4. Podemos descargarlo desde el Download Center de Microsoft o via NuGet. Antes de instalar esta nueva versión debemos desinstalar la versión preview en caso de tenerla instalada, y no es necesario hacer nadar con la instalación de ASP.NET MVC3 o anteriores versiones ya que seguirán funcionando.

La primera de la novedades que veremos será la aparición de nuevas plantillas a la hora de crear un proyecto ASP.NET MVC4 Beta.


  • Empty, Internet Application y Intranet Application, sin cambios respecto a la versión anterior.
  • Mobile Application, es una plantilla creada si queremos crear un sitio específico para dispositivos móviles. Esta plantilla está basada en la utilización de jQuery Mobile. Esta plantilla contiene la misma estructura que la plantilla Internet Applicacion. De hecho el código de la controlladora es igual, pero la interfaz está modelada utilizando los estilos de jQuery Mobile.


  • ASP.NET Web API, para mi una de las grandes novedades de esta entrega, y que es un framework que nos facilita la creación de aplicaciones que publican servicios y datos utilizando HTTP.
  • Single Page Application, es otra de las novedad de esta versión, y que incluye un conjunto de herramientas y componentes específicos para implementar aplicaciones en una única página web, mediante el uso de Ajax. Un ejemplo de este tipo de aplicaciones lo tenemos en gmail donde todo se mueve en la misma página a base de llevar al límite el uso de Ajax.
Dejaré para otra entrada algún ejemplo sobre la nueva ASP.NET Web Api y la Simple Page Application.

Mas referencias | jQuery Mobile | Gmail

lunes, 6 de febrero de 2012

ASP.NET MVC3 validateRequest

Al usar un editor html en ASP.NET MVC3 y hacer un POST he obtenido el siguiente error

A potentially dangerous Request.Form value was detected from the client (Descripcion="párrafo").


Description: Request Validation has detected a potentially dangerous client input value, and processing of the request has been aborted. This value may indicate an attempt to compromise the security of your application, such as a cross-site scripting attack. To allow pages to override application request validation settings, set the requestValidationMode attribute in the httpRuntime configuration section to requestValidationMode="2.0". Example: . After setting this value, you can then disable request validation by setting validateRequest="false" in the Page directive or in the configuration section. However, it is strongly recommended that your application explicitly check all inputs in this case.


Básicamente el error es debido a que en el post va código html y esto es interpretado como un problema de seguridad. Así que si estamos seguro de lo que estamos haciendo, podemos evitar la comprobación desactivando la validación en la acción de nuestra controladora. Hay que tener en cuenta que quitando la validación estaremos expuestos a que nuestros usuarios introduzcan código javascript en nuestro formulario con el riesgo de seguridad que eso implica, así que, hacer una limpieza de la cadena de caracteres antes de guardarla en la base de datos puede ser altamente recomendable.

//
// POST: /edit/
[HttpPost]
[ValidateInput(false)]
public ActionResult edit(Model model) {
  // ...
}

Eso es todo, happy coding !

Más info | ASP.NET MVC3jHtmlArea

viernes, 13 de enero de 2012

Como publicar una aplicación ASP.NET MVC3 en un IIS5

Esta mañana, en un pequeño desarrollo que estoy haciendo en ASP.NET MVC3 me he tropezado con un pequeño problema al publicar la aplicación en un Windows Small Business Server 2003. Este versión usa el IIS 5, y al publicar me tropecé con este error al acceder a la URL http://miaplicacion/home/index


El error básicamente es un error 404 debido a que el IIS no puede resolver las redirecciones que usa el ASP.NET MVC3 del tipo midominio.com/controladora/accion/parametos.

Los primero que hice fue comprobar que tanto el Framework 4.0 y el ASP.NET MVC3 estaban instalados en el servidor y tras comprobarlo no quedo más remedio que recurrir a San Google para buscar la solución al problema.

Me di cuenta que mi problema era bastante común y tras buscar un poco por varios foros encontre dos tipo soluciones

- En entornos donde no es posible acceder al IIS (por ejemplo en hosting contratados) lo mejor es modificar los ruteos que se configuran en el Global.asax. Esta solución no la probé pero hay varios ejemplos en Internet que dicen que ruta hay añadir.

- En caso donde tengamos acceso al IIS la solucion es bastante simple
  1. Hacemos click con la botón derecho sobre las 'Propiedades' de nuestro sitio web.
  2. En la pestaña 'Directorio' hacemos click en el botón que pone 'Configuración'.
  3. Seleccionamos la pestaña 'Asignaciones'
  4. Hacemos click en el botón 'Insertar'
  5. Pegamos la ruta hacia el fichero aspnet_isapi.dll en el campo que pone 'Ejecutable'. En mi caso la ruta  es 'c:\windows\microsoft.net\framework\v4.0.30319\aspnet_isapi.dll'. Se copia la ruta del mapeo de los ficheros .aspx.
  6. Desmarcamos el check que pone 'Comprobar si el archivo existe'.
  7. Le damos a 'Aceptar'


Hecho esto la aplicación funcionaba perfectamente, evitando tener que tocar las tablas de ruteo, aunque esto no es siempre posible.

Happy coding!

martes, 3 de enero de 2012

Como mostrar una imagen con ASP.NET MVC3

Supongamos que queremos cargar una imagen que se encuentra en nuestro sistema de fichero de manera dinámica. Bien, en nuestro cshtml debemos poner el siguiente código

<img src="@Url.Action("showimage", "rest", new { id = 1 })" alt="" />

Y en nuestra controladora deberemos declarar la siguiente acción

public ActionResult showimage(int id) 
{
    var path = Server.MapPath("~/content/image");
    var file = string.format("{0}.png", id);
    var fullPath = Path.Combine(path, file);
    return File(fullPath, "image/png", file);
}

Happy coding!

sábado, 17 de diciembre de 2011

Error al depurar una aplicación ASP.NET MVC3

Tras tener varios problemas con mi anterior ordenador no me ha quedado más remedio que cambiarlo. Una vez que mi nuevo equipo ha estado en mis manos me he puesto manos a la obra a instalar el entorno de desarrollo en una maquina virtual (todavía no he recuperado la información de los discos duros del otro ordenador). El orden de instalación que he seguido es el siguiente
  • Visual Studio Professional 2010 (más información aquí)
  • ASP.NET MVC3 (más información aquí)
  • IIS
  • SQL Server 2008 R2 Express Edition (más información aquí)
Al abrir un proyecto en el que estoy trabajando en ASP.NET MVC3 e intentar depurarlo obtenía un mensaje de error. Este mensaje de error me informaba que el Visual Studio no podía lanzar el depurador web. Siguiendo las indicaciones del mensaje lancé la aplicación sin depurar (CTRL+F5 en mi configuración) y el mensaje de error que obtuve fue este

Error HTTP 500.21 - Internal Server Error 
El controlador "BlockViewHandler" tiene una condición previa incorrecta "ManagedPipelineHandler" en su lista de módulos

Tras mirar la MSDN y algún foro encontré la solución que no es otra que lanzar el script aspnet_regiis.exe desde la carpeta del framework
%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe -ir

Espero que les sea de utilidad...