Twitter Trackbacks for www.ienumerable.it - Creare un EditorTemplate per gli enum con MVC 2 [ienumerable.it] on Topsy.com
Creare un EditorTemplate per gli enum con MVC 2
Pubblicato da andrea in MVC il 05 maggio 2010 alle 9.00
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:
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
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.
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..
e se diamo un’occhiata al sorgente HTML troviamo
decisamente meglio.
Pubblicato da andrea in MVC il 05 maggio 2010 alle 9.00

#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 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 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.