Il y a eu beaucoup de discussions sur ce sujet récemment. Des barrages routiers similaires sont rencontrés avec des dates, des plages de dates et des listes de cases à cocher à sélection multiple. Partout où vous voudrez utiliser un riche ensemble de contrôles html. J'ai expérimenté avec le concept de ViewModels enfant et je pense que la solution est plus propre que d'autres approches que j'ai essayées.
Le concept de base est que vous définissiez un petit modèle de vue étroitement associé à un EditorTemplate personnalisé.
Dans votre exemple, nous commencerions avec un (enfant) ViewModel qui est spécifique à une seule liste de sélection:
public class SelectModel
{
#region SelectModel(string value, IEnumerable<SelectListItem> items)
public SelectModel(string value, IEnumerable<SelectListItem> items)
{
_value = value;
Items = new List<SelectListItem>(items);
_Select();
}
#endregion
// Properties
public List<SelectListItem> Items { get; private set; }
public string Value
{
get { return _value; }
set { _value = value; _Select();}
}
private string _value;
// Methods
private void _Select()
{
Items.ForEach(x => x.Selected = (Value != null && x.Value == Value));
}
}
Dans le modèle de vue qui veut utiliser le menu déroulant vous rédigez le modèle de sélection (nous » utilisez-vous tous les modèles de vue, n'est-ce pas?):
public class EmailModel
{
// Constructors
public EmailModel()
{
Priority = new SelectModel("normal", _ToPrioritySelectItems());
}
// Properties
public SelectModel Priority { get; set; }
// Methods
private IEnumerable<SelectListItem> _ToPrioritySelectItems()
{
List<SelectListItem> result = new List<SelectListItem>();
result.Add(new SelectListItem() { Text = "High", Value = "high" });
...
}
Notez qu'il s'agit d'un exemple simple avec un ensemble fixe d'éléments déroulants. Si elles proviennent de la couche de domaine, le contrôleur les passe dans le ViewModel.
Puis ajouter un modèle d'éditeur SelectModel.ascx dans Shared/EditorTemplates
<%@ Control Inherits="System.Web.Mvc.ViewUserControl<SelectModel>" %>
<div class="set">
<%= Html.LabelFor(model => model) %>
<select id="<%= ViewData.ModelMetadata.PropertyName %>_Value" name="<%=ViewData.ModelMetadata.PropertyName %>.Value">
<% foreach (var item in Model.Items) { %>
<%= Html.OptionFor(item) %>
<% } %>
</select>
</div>
Note: OptionFor est une extension personnalisée qui fait l'évidence
L'astuce ici est que l'identifiant et le nom sont définis à l'aide le format composé attendu par ModelBinder par défaut. Dans notre exemple "Priority.Value". Ainsi, la propriété Value basée sur une chaîne définie dans le cadre de SelectModel est définie directement. Le régleur prend soin de mettre à jour la liste des éléments pour définir l'option de sélection par défaut si nous devons réafficher le formulaire. Lorsque cette approche "child view model" brille vraiment, c'est plus complexe "des extraits de contrôle de balisage". J'ai maintenant des modèles de vue enfant qui suivent une approche similaire pour les listes MultiSelect, les plages de dates de début/fin et les combinaisons date/heure.
Dès que vous suivez ce chemin, la prochaine question évidente devient la validation.
J'ai fini tout mon enfant mettre en œuvre une interface standard de ViewModel:
public interface IValidatable
{
bool HasValue { get; }
bool IsValid { get; }
}
Ensuite, j'ai une coutume ValidationAttribute:
public class IsValidAttribute : ValidationAttribute
{
// Constructors
public IsValidAttribute()
{
ErrorMessage = "(not valid)";
}
// Properties
public bool IsRequired { get; set; }
// Methods
private bool Is(object value)
{
return value != null && !"".Equals(value);
}
public override bool IsValid(object value)
{
if (!Is(value) && !IsRequired)
return true;
if (!(value is IValidatable))
throw new InvalidOperationException("IsValidAttribute requires underlying property to implement IValidatable");
IValidatable validatable = value as IValidatable;
return validatable.IsValid;
}
}
Maintenant, vous pouvez simplement mettre des attributs sur les propriétés qui sont enfant ViewModel basé comme toute propriété scalaire:
[IsValid(ErrorMessage = "Please enter a valid start date/time")]
public DateAndTimeModel Start { get; set; }