Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 25 de octubre de 2016
ASP.NET Core MVCHace muuuchos muchos años ya hablé por aquí de lo extraño que resultaba que el framework MVC, que por aquellos entonces rondaba su segunda versión, no contase de serie con un mecanismo para generar automáticamente el atributo maxlength en los cuadros de texto, máxime cuando esta información solíamos incluirla en anotaciones de las clases del modelo con atributos como StringLength o MaxLength.

Y ha llovido bastante desde entonces, incluso el framework MVC se ha "reseteado" y ahora es ASP.NET Core MVC, pero seguimos sin disponer de esa posibilidad, que cubre un escenario muy frecuente al desarrollar aplicaciones con este framework.

En este post vamos a ver, paso a paso, cómo utilizar los maravillosos tag helpers para incluir este atributo de forma automática en los tags <input>  vinculados a propiedades del modelo cuyos metadatos indiquen un tamaño máximo para el campo.

1. Declaración de intenciones

Lo que vamos a desarrollar es un tag helper que intervenga en el renderizado de tags <input> que estén enlazados a propiedades del modelo, para lo que deben presentar el atributo asp-for. Por ejemplo, dado un view model como el siguiente:
public class PersonViewModel
{
    [Required, StringLength(25), Display(Name = "First name")]
    public string FirstName { get; set; }

    [Required, MaxLength(50), Display(Name = "Last name")]
    public string LastName { get; set; }

    [EmailAddress, MaxLength(200), Display(Name = "Email address")]
    public string Email { get; set; }
}
Nuestro tag helper se procesará cuando utilicemos <input asp-for="property"> para generar los controles de edición, como en el siguiente ejemplo:
<form asp-action="Edit" method="post">
    <div class="form-group">
        <label asp-for="FirstName"></label>
        <input asp-for="FirstName" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="LastName"></label>
        <input asp-for="LastName" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Email"></label>
        <input asp-for="Email" class="form-control" maxlength="180" />
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Save changes</button>
    </div>
</form>
Durante el proceso, primero comprobaremos si el tag original ya incluye el atributo maxlength, como en el campo "Email" del ejemplo anterior, en cuyo caso no haremos nada para no sobrescribir el valor introducido por el desarrollador, pero en otros casos examinaremos los metadatos de la propiedad y, en caso de emplearse anotaciones como [StringLength] o [MaxLength] generaremos sobre el tag <input> el atributo maxlength con el valor correctamente establecido.

Así, en tiempo de ejecución obtendríamos el siguiente resultado en el ejemplo anterior (simplificado un poco):
<form method="post" action="/">
    <div class="form-group">
        <label for="FirstName">First name</label>
        <input class="form-control" type="text" id="FirstName" 
               name="FirstName" value="" maxlength="25" />
    </div>
    <div class="form-group">
        <label for="LastName">Last name</label>
        <input class="form-control" type="text" id="LastName" 
               name="LastName" value="" maxlength="50" />
    </div>
    <div class="form-group">
        <label for="Email">Email address</label>
        <input class="form-control" type="email" id="Email" 
               name="Email" value="" maxlength="180" />
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Save changes</button>
    </div>
</form>
Observad que en los campos "FirstName" y "LastName" introduciríamos el tamaño máximo indicado en sus respectivos atributos como [StringLength] y [MaxLength], mientras que en "Email" simplemente dejaríamos intacto el valor indicado expresamente sobre la etiqueta en el código de la vista.

2. Manos a la obra: la clase MaxLengthTagHelper

Como sabemos, los tag helpers se implementan en clases que heredan de TagHelper; en nuestro caso, siguiendo convenciones habituales de nombrado, la llamaremos MaxLengthTagHelper, y utilizaremos sobre ella el atributo [HtmlTargetElement] para indicar que el tag helper debe aplicarse sobre los tags <input> que presenten el atributo asp-for. La estructura básica, por tanto, sería la siguiente:
[HtmlTargetElement("input", Attributes = "asp-for")]
public class MaxLengthTagHelper : TagHelper
{
    ...
}
A continuación, dado que necesitaremos tener acceso a la propiedad del modelo especificada por el atributo asp-for, debemos añadir a nuestra clase una propiedad de tipo ModelExpression , por ejemplo de la siguiente manera:
[HtmlTargetElement("input", Attributes = "asp-for")]
public class MaxLengthTagHelper : TagHelper
{
    [HtmlAttributeName("asp-for")]
    public ModelExpression For { get; set; }

    ...
}
Con esto estamos consiguiendo que el runtime cargue en el miembro For de nuestra clase información de todo tipo acerca de la propiedad que estamos indicando en el atributo asp-for, como su valor, tipo, metadatos, validadores, etc.

3. Añadiendo el atributo maxlength durante el renderizado

Para participar en el renderizado de una etiqueta, un tag helper debe sobrescribir el método Process (o su versión asíncrona ProcessAsync), incuyendo en él toda la lógica de generación del resultado. En nuestro caso esta lógica es bastante simplita, como puedes ver a continuación:
[HtmlTargetElement("input", Attributes = "asp-for")]
public class MaxLengthTagHelper : TagHelper
{
    [HtmlAttributeName("asp-for")]
    public ModelExpression For { get; set; }
    
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        base.Process(context, output);
        // Process only if 'maxlength' attr is not present
        if (context.AllAttributes["maxlength"] == null)
        {
            var maxLength = GetMaxLength(For.ModelExplorer.Metadata.ValidatorMetadata);
            if (maxLength > 0)
                output.Attributes.Add("maxlength", maxLength);
        }
    }
    ...
}
Lo único que hacemos es comprobar si existe el atributo maxlength en el código original y, sólo en caso negativo, llamar al método GetMaxLength(), cuyo código veremos a continuación, suministrándole los metadatos de validación de la propiedad a editar para obtener su tamaño máximo. En caso de que sea mayor o igual a cero, añadiremos el atributo al tag resultante.

4. Obtener el tamaño máximo

Como hemos visto, el método GetMaxLength() recibe información sobre los validadores aplicados a la propiedad actual. Éstos se presentan en forma de una colección de object donde podemos encontrar las instancias de los atributos de validación aplicados sobre la propiedad, como RequiredAttribute, StringLengthAttribute o MaxLengthAttribute.

Por tanto, lo único que debemos hacer es recorrer esta colección buscando los atributos que pueden indicar tamaños máximos para el campo, y retornar su valor cuando lo hayamos encontrado:
private static int GetMaxLength(IReadOnlyList<object> validatorMetadata)
{
    for (var i = 0; i < validatorMetadata.Count; i++)
    {
        var stringLengthAttribute = validatorMetadata[i] as StringLengthAttribute;
        if (stringLengthAttribute != null && stringLengthAttribute.MaximumLength > 0)
        {
            return stringLengthAttribute.MaximumLength;
        }
        var maxLengthAttribute = validatorMetadata[i] as MaxLengthAttribute;
        if (maxLengthAttribute != null && maxLengthAttribute.Length > 0)
        {
            return maxLengthAttribute.Length;
        }
    }
    return 0;
}
Por si queréis probarlo en vivo o usarlo en vuestros proyectos, he dejado el código en Github.

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!