MVC Templated Helper - DropDown
-
10-07-2019 - |
Domanda
Usando gli helper basati su modelli in MVC2.0 mi sono imbattuto in un dillema, come ottenere gli elementi per riempire un elenco a discesa.
Sto usando un attributo [UIHint (BadgesDropDown)]
, ma come posso ottenere gli elementi dell'elenco senza violare il modello MVC, il controller dovrebbe posizionarli in ViewData? BadgesDropDown.ascx
dovrebbe invocare un aiutante per ottenerli?
In questo momento vado per:
BadgesDropDown.ascx
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%= Html.DropDownList("", ViewData["Badges"] as IEnumerable<SelectListItem>)%>
Regolatore
ViewData["Badges"] = new SelectList(SiteRepository.GetBadges(), "RowKey", "BadgeName");
È questa la strada da percorrere?
Soluzione
In MVC 2 un nuovo fantastico metodo ... che se usato si basa su tutti i dati degli attributi.
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<glossaryDB.EntityClasses.AssociationEntity>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Association: Edit
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">
<h3>Association: Edit</h3>
<% using (Html.BeginForm()) { %>
<fieldset style="padding: 1em; margin: 0; border: solid 1px #999;">
<%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>
<%= Html.EditorForModel() %>
<input type="submit" value=" Submit " />
</fieldset>
<% } %>
<p><%= Html.ActionLink("Details", "Index") %></p>
</asp:Content>
Perché questo funzioni ci sono 2 opzioni. OIHint deve fornire l'origine dei dati o il controller deve. Se UIHint lo fa, i dati forniti al menu a discesa vengono corretti. L'altra opzione è il controller, che ci consente di scambiare i dati a discesa con un diverso set di dati come richiesto.
Ci sono alcuni esempi correlati che ho trovato:
Cena da nerd
[1]: ricerca per codeclimber.net.nz e how-to-create-a-dropdownlist-with-asp.net-mvc [2]: bradwilson.typepad.com e modelli-parte-5-pagina-principale-modelli
Altri suggerimenti
Di recente si è discusso molto su questo argomento. Blocchi stradali simili si incontrano con date, intervalli di date ed elenchi di caselle di selezione multipla. Ovunque potresti voler usare un ricco set di controlli html. Ho sperimentato il concetto di ViewModels figlio e penso che la soluzione sia più pulita di altri approcci che ho provato.
Il concetto di base è che si definisce un modello di vista piccolo che è strettamente accoppiato a un EditorTemplate personalizzato.
Nel tuo esempio, inizieremmo con un ViewModel (figlio) specifico per un singolo elenco di selezione:
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));
}
}
Nel modello di visualizzazione che desidera utilizzare il menu a discesa, componi il modello selezionato (stiamo tutti utilizzando i modelli di visualizzazione, giusto?):
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" });
...
}
Nota che questo è un semplice esempio con un set fisso di elementi a discesa. Se provengono dal livello di dominio, il controller li passa nel ViewModel.
Quindi aggiungi un modello di editor SelectModel.ascx in 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>
Nota: OptionFor è un'estensione personalizzata che fa l'ovvio
Il trucco qui è che l'id e il nome sono impostati usando il formato composto che ModelBinder predefinito si aspetta. Nel nostro esempio " Priority.Value " ;. Pertanto, la proprietà Value basata su stringa definita come parte di SelectModel viene impostata direttamente. Il setter si occupa di aggiornare l'elenco degli elementi per impostare l'opzione di selezione predefinita se è necessario visualizzare nuovamente il modulo.
Dove questo "modello di visualizzazione figlio" l'approccio brilla davvero è più complesso "snippet di controllo di markup". Ora ho modelli di visualizzazione figlio che seguono un approccio simile per elenchi MultiSelect, intervalli di date di inizio / fine e combinazioni di data + ora.
Non appena percorri questa strada, la successiva domanda ovvia diventa validazione.
Ho finito per avere tutti i ViewModel di mio figlio implementare un'interfaccia standard:
public interface IValidatable
{
bool HasValue { get; }
bool IsValid { get; }
}
Quindi, ho un ValidationAttribute personalizzato:
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;
}
}
Ora puoi semplicemente mettere gli attributi su proprietà che sono ViewModel figlio come qualsiasi proprietà scalare:
[IsValid(ErrorMessage = "Please enter a valid start date/time")]
public DateAndTimeModel Start { get; set; }
Ho implementato la soluzione come nell'esempio sopra. Una cosa da notare è che gli helper dovrebbero lavorare solo con i dati forniti, vedere Visualizza dipendenza
La migliore pratica è scrivere HTML aiutanti ignari dei controller e contesti. Dovrebbero fare il loro lavoro basato solo sui dati forniti da il chiamante.
Sono d'accordo sulla dichiarazione di cui sopra. È solo che bisogna fare molto lavoro rispetto al normale sviluppo ASP.Net.