Creare un EditorTemplate per gli enum con MVC 2

Intro

Nelle mie applicazioni faccio largo uso di enums (e chi non lo fa?), mi sono quindi creato una serie di helpers / providers per la gestione della UI bindata con gli enum.

In WebForms utilizzo:

  • IEnumDescriptionProvider: servizio che estrapola le descrizioni.
    • LocalizedEnumDescriptionProvider per le descrizioni localizzate
    • AttributeEnumDescriptionProvider per leggere degli attributi custom
    • AutomaticCaseEnumDescriptionProvider per la conversione automatica al cambio di case (UnValore –> Un Valore)
    • DefaultEnumDescriptionProvider che fa il semplice ToString()
  • IDropDownItemsProvider per creare le liste di valori da passare alle dropdown.
    • DefaultEnumDropDownItemsProvider che ha per chiave il valore numerico dell’enum e per testo la descrizione estrapolata tramite i provider.

 

Visto che MVC2 è estendibile ho provato a riportare le funzionalità di base su questa piattaforma.

Questo è il model

public class Movie
{
    [Required]
    public string Title { get; set; }
    public Rating Rating { get; set; }
}

e questo l’enum

public enum Rating 
{
    None,
    Horrible,
    Bad,
    Poor,
    BelowAverage,
    Average,
    AboveAverage,
    Good,
    VeryGood,
    Superb,
    Excellent
}
Sfruttando il nuovo motore di template possiamo scrivere la form di inserimento dati come
<% using (Html.BeginForm()){%>
   <%= Html.EditorForModel() %>
   <input type="submit" value="Create" />
<%}%>

Il risultato è il seguente:

image

Decisamente poco user friendly.

Step 1 – gestire le descrizioni tramite attributo

MVC2 utilizza DataAnnotations e l’attributo DisplayNameAttribute per poter impostare una label custom sui controlli. Purtroppo l’attributo DisplayNameAttribute non è utilizzabile direttamente sul singolo valore dell’enum. Bypassiamo il problema creando la nostra versione e cambiando l’AttributeUsage

[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field)]
public class EnumDisplayNameAttribute : DisplayNameAttribute
{
    public EnumDisplayNameAttribute(string displayName)
        :base(displayName)
    {
    }
}

In questo modo è possibile decorare l’enum

[EnumDisplayName("** Rating")]
public enum Rating 
{
    None,
    Horrible,
    Bad,
    Poor,
    [EnumDisplayName("Below Average")]
    BelowAverage,
    Average,
    [EnumDisplayName("Above Average")]
    AboveAverage,
    Good,
    [EnumDisplayName("Very Good")]
    VeryGood,
    Superb,
    [EnumDisplayName("Just... WOW!")]
    Excellent
}

Step 2 – estendere l’enum

Abbiamo bisogno di leggere il valore degli enum e di poter creare una lista di valori da passare al controllo DropDown. Implementiamo entrambe le funzionalità come Extension Methods

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace EnumTemplates.Helpers
{
    public static class EnumExtensions
    {
        public static string GetDisplayName(this Enum enumItem)
        {
            var mi = enumItem.GetType().GetMember(enumItem.ToString()).FirstOrDefault();
            if(mi != null)
            {
                var dn = (EnumDisplayNameAttribute)(mi.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false)).FirstOrDefault();
                if (dn != null)
                    return dn.DisplayName;
            }

            return enumItem.ToString();
        }

        public static IEnumerable<SelectListItem> ToSelectList(this Enum enumItem)
        {
            var enumType = enumItem.GetType();

            return (from object value in Enum.GetValues(enumType)
                    select new SelectListItem
                               {
                                   Selected = value.ToString() == enumItem.ToString(), 
                                   Text = ((Enum)value).GetDisplayName(), 
                                   Value = ((int)value).ToString()
                               }).ToList();
        }
    }
}

Step 3 – Creare un template di default per gli Enum

Nella cartella Views\Shared\EditorTemplates creiamo un nuovo usercontrol Enum.ascx

image

il cui sorgente è il seguente

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Enum>" %>
<%@ Import Namespace="EnumTemplates.Helpers" %>
<%= Html.DropDownListFor(x => x,Model.ToSelectList()) %>

Step 4 – Abilitare il template per il nostro Enum

Piuttosto che decorare con l’attributo UIHint ogni property del nostro ViewModel (che è un approccio decisamente poco DRY) proviamo a decorare direttamente l’enum Ratings.

image

Anche in questo caso ci sono limitazioni sull’utilizzo dell’attributo, la soluzione la conosciamo già ;D

using System;
using System.ComponentModel.DataAnnotations;

namespace EnumTemplates.Helpers
{
    [AttributeUsage(AttributeTargets.Enum)]
    public class EnumUIHintAttribute : UIHintAttribute
    {
        public EnumUIHintAttribute(string uiHint)
            : base(uiHint)
        {
            
        }
    }
}

a questo punto possiamo definire il nostro enumerativo come

using EnumTemplates.Helpers;

namespace EnumTemplates.Models
{
    [EnumDisplayName("** Rating")]
    [EnumUIHint("Enum")]
    public enum Rating 
    {
        None,
        Horrible,
        Bad,
        Poor,
        [EnumDisplayName("Below Average")]
        BelowAverage,
        Average,
        [EnumDisplayName("Above Average")]
        AboveAverage,
        Good,
        [EnumDisplayName("Very Good")]
        VeryGood,
        Superb,
        [EnumDisplayName("Just... WOW!")]
        Excellent
    }
}

Step 5 – Up & Running

Step fondamentale…. F5 e test nel browser..

 

image 

e se diamo un’occhiata al sorgente HTML troviamo

image

decisamente meglio.

Sorgenti

3 commenti

Aspnetmvc, Attributes, Enums, Templates

[top]

Related Post

  1. #1 da onof - mercoledì maggio 2010 alle 01.05

    Soluzione molto interessante.

    Personalmente evito come la peste di inserire gli enumerati nel codice, perché spesso causano il proliferare di tante funzioncine che contengono uno switch. Un enumerato è un singleton e generalmente lo tratto in questo modo. Comunque quando sono stato costretto ho usato l'attributo System.ComponentModel.Description.

  2. #2 da Andrea Balducci - mercoledì maggio 2010 alle 02.02

    Se hai singleton che necessariamente condividono la famiglia (classe base) allora hai un enum meno performante; non ho mai visto controindicazioni nell'utilizzo degli enum. Per evitare la profilerazione degli switch basta centralizzare la logica applicativa basata sull'enum: è un problema di responsabilità e design, non di tipo.

    Per il discorso System.ComponentModel.Description l'ho scartato (anche se sul web è pieno di esempi simili) perché ha un significato differente; qui serve il DisplayName (cosa voglio a video) e non Description (a cosa serve l'enum).

  3. #3 da onof - mercoledì maggio 2010 alle 02.36

    "Per evitare la profilerazione degli switch basta centralizzare la logica applicativa basata sull'enum: è un problema di responsabilità e design, non di tipo."
    Sono d'accordo ma spesso capita che, come dice credo Fowler in "Refactoring", cambiando le specifiche, si è costretti ad aggiungere complessità ed effettuare certe operazioni in base al valore assunto dal campo enumerato, ma ormai potrebbe essere troppo tardi. Purtroppo C# non ha la flessibilità che ha Java con gli enumerati.


(will not be published)
(es: http://www.mysite.com)

 
  1. #1 da http://topsy.com/trackback?utm_source=pingback&utm_campaign=L1&url=http://www.ienumerable.it/blog/post/creare-un-editortemplate-per-gli-enum-con-mvc-2

    Twitter Trackbacks for www.ienumerable.it - Creare un EditorTemplate per gli enum con MVC 2 [ienumerable.it] on Topsy.com

Andrea Balducci - IEnumerable.it