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, 20 de diciembre de 2011
ASP.NET MVCHace pocas semanas profundizamos en los mecanismos de obtención de metadatos del modelo en ASP.NET MVC y vimos cómo extender el framework para dotarlo de vías alternativas desde las que obtener esta información usando un proveedor personalizado.

Sin embargo, no es este el único mecanismo de extensión del framework a este respecto: también podemos crear fácilmente nuevos atributos que aporten información extra de metadatos a las clases y propiedades del Modelo. Y esta es la razón de ser del interfaz IMetadataAware.

El interfaz IMetadataAware

Definido en System.Web.Mvc e introducido con la tercera versión del framework, este interfaz permite crear atributos que encajen muy suavemente en el sistema de generación y obtención de metadatos de ASP.NET MVC, evitando en muchos escenarios la necesidad de escribir proveedores personalizados.
Como podemos ver a continuación, su definición es de lo más simple:
public interface IMetadataAware
{
    void OnMetadataCreated(ModelMetadata metadata);
}
ModelMetadata (parcial)¿Y cómo se utiliza esto internamente? Pues la respuesta la podemos encontrar buceando un poco en el código fuente del framework.

Durante el proceso de obtención de metadatos, el proveedor DataAnnotationsModelMetadataProvider (en conjunción con su tipo base AssociatedMetadataProvider), obtiene los metadatos “estándar” y los introduce en un objeto de tipo ModelMetadata.

Una vez tenemos ya esta información, obtenida según el comportamiento por defecto desde los atributos conocidos que decoran las propiedades del Modelo, se ejecuta el método estático ApplyMetadataAwareAttributes(), que tiene la siguiente pinta:
private static void ApplyMetadataAwareAttributes(
                          IEnumerable<Attribute> attributes, 
                          ModelMetadata result)
{
    foreach (IMetadataAware awareAttribute in attributes.OfType<IMetadataAware>())
    {
        awareAttribute.OnMetadataCreated(result);
    }
} 
Como se puede deducir a la vista del código, el método es invocado suministrándole, por una parte, todos los atributos localizados en la clase del Modelo, y por otra, el objeto ModelMetadata que contiene la información de metadatos obtenidos hasta el momento.

Ya en su interior, lo único que se hace es obtener todos aquellos atributos que implementen el interfaz IMetadataAware y llamar a su único método OnMetadataCreated() con objeto de que actualicen los metadatos con la información que necesiten.

Por tanto, en la práctica, si queremos crear un atributo que introduzca información adicional de metadatos, o simplemente modifique los existentes, debemos:
  • crear un atributo como siempre, heredando de Attribute,
  • hacer que la clase implemente el interfaz IMetadataAware,
  • implementar en el método OnMetadataCreated() para introducir la metainformación que nos interese en el objeto ModelMetadata que recibimos como parámetro .
Por ejemplo:
public class MyMetadataAttribute: Attribute, IMetadataAware
{
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        // Set properties...
        metadata.DisplayName = "My text";
 
        // ... or add new properties to AdditionalValues
        metadata.AdditionalValues["MyKey"] = "My value";
    }
}
Observad que en el cuerpo del método tenemos acceso a todas las propiedades de metadatos, así como a su diccionario AdditionalValues, donde podemos introducir cualquier tipo de información que nos interese poner a disposición de la Vista.

¿Un ejemplo rápido?

Vamos a desarrollar un atributo de metadatos personalizado al que llamaremos Important, y que hará lo siguiente:
  • en primer lugar, añadirá al DisplayName de la propiedad (el texto que aparece en su correspondiente etiqueta) el sufijo “important!”.
  • añadirá a la colección AdditionalValues un valor que permita a la vista destacar el editor de la propiedad visualmente.
(Ojo, que hay otras formas para conseguir este mismo resultado, pero el post trata sobre IMetadataAware, así que tendremos que quedarnos con esta ;-))
public class ImportantAttribute: Attribute, IMetadataAware
{
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.DisplayName = metadata.GetDisplayName() + " (important!)";
        metadata.AdditionalValues["Important"] = true;
    }
}
Eso es todo lo que necesitamos en nuestro atributo. Hecho esto, ya podemos utilizarlo en una clase del Modelo como se muestra a continuación:
public class Friend
{
    [Important]
    public string Name { get; set; }
 
    public string Email { get; set; }
 
    [Important]
    public string Phone { get; set; }
 
    public string Fax { get; set; }
 
}
El simple hecho de decorar las propiedades Name y Phone con nuestro flamante atributo [Important] hará que sus etiquetas descriptivas aparezcan ya con el sufijo “important!”, puesto que estamos modificando directamente su DisplayName. Si queremos además destacar los editores necesitaremos inyectar un poco de lógica en la vista, cosa que podemos hacer de forma relativamente sencilla implementando un helper o modificando las plantillas de edición por defecto para los tipos utilizados (en este caso, strings).

Una implementación rápida de un helper que genere código script para destacar las propiedades importantes podría ser la siguiente. Si os fijáis, lo único que hace es obtener aquellas propiedades en cuyos metadatos exista la entrada de AdditionalValues establecida en el atributo [Important], y establecer en sus editores la clase CSS “important” para darles un poco de color:
public static class HtmlExtensions
{
    public static MvcHtmlString HighlightImportant(this HtmlHelper html)
    {
        var template = html.ViewData.TemplateInfo;
        var importantProps = from property in html.ViewData.ModelMetadata.Properties
                                where property.AdditionalValues.Any(
                                    a => a.Key == "Important" && (bool)a.Value)
                                select template.GetFullHtmlFieldId(property.PropertyName);
                                       
        if (importantProps.Any())
        {
            string commaIds = string.Join(",#", importantProps);
            string script = "<script type='text/javascript'>" +
                            "$(function() { $('#" + commaIds + "').addClass('important'); });" +
                            "</script>";
            return MvcHtmlString.Create(script);
        }
        return null;
    }
}
El atributo Important en acciónObviamente, tendríamos que incluir una llamada al helper Html.HighlightImportant() en las vistas o, si queremos mayor comodidad, en el Layout del sitio web.

En la captura de pantalla de la derecha podéis ver el resultado de este ejemplo en funcionamiento. Y si queréis probarlo y juguetear un rato con él, podéis descargar el proyecto para Visual Studio y ASP.NET MVC 3 desde mi Skydrive.


Publicado en Variable not found.

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