2010-04-29 15 views
16

Ho trascorso la maggior parte della settimana scorsa a ginocchia profonde nella nuova funzionalità di template inserita in MVC2. Ho avuto difficoltà a provare a far funzionare un modello DropDownList. Il problema più grande che ho lavorato per risolvere è come ottenere i dati di origine per l'elenco a discesa sul modello. Ho visto molti esempi in cui è possibile inserire i dati di origine nel dizionario ViewData (ViewData ["DropDownSourceValuesKey"]) quindi recuperarli nel modello stesso (var sourceValues ​​= ViewData ["DropDownSourceValuesKey"];) Funziona, ma l'ho fatto non come avere una stringa sciocca come il perno linciaggio per fare questo lavoro.MVC2 EditorTemplate for DropDownList

Di seguito un approccio che è venuta in mente e voleva ottenere pareri su questo approccio:

qui sono i miei obiettivi di progettazione:

  • Il modello di vista dovrebbe contenere i dati di origine per la caduta lista
  • Limite stupide Strings giù
  • Non utilizzare il dizionario Viewdata
  • controller è responsabile per il riempimento della proprietà con i dati di origine per la discesa Lista

Ecco la mia vista Modello:

public class CustomerViewModel 
    { 
     [ScaffoldColumn(false)] 
     public String CustomerCode{ get; set; } 

     [UIHint("DropDownList")] 
     [DropDownList(DropDownListTargetProperty = "CustomerCode"] 
     [DisplayName("Customer Code")] 
     public IEnumerable<SelectListItem> CustomerCodeList { get; set; } 

     public String FirstName { get; set; } 
     public String LastName { get; set; } 
     public String PhoneNumber { get; set; } 
     public String Address1 { get; set; } 
     public String Address2 { get; set; } 
     public String City { get; set; } 
     public String State { get; set; } 
     public String Zip { get; set; } 
    } 

My View modello ha una proprietà CustomerCode, che è un valore che l'utente seleziona da un elenco di valori. Ho una proprietà CustomerCodeList che è un elenco di possibili valori CustomerCode ed è la fonte per un elenco a discesa. Ho creato un attributo DropDownList con DropDownListTargetProperty. DropDownListTargetProperty punta alla proprietà che verrà popolata in base alla selezione dell'utente dal menu a discesa generato (in questo caso, la proprietà CustomerCode).

Avviso che la proprietà CustomerCode ha [ScaffoldColumn (false)] che forza il generatore a saltare il campo nell'output generato.

Il file DropDownList.ascx genererà un elemento del modulo elenco a discesa con i dati di origine dalla proprietà CustomerCodeList. L'elenco a discesa generato utilizzerà il valore di DropDownListTargetProperty dall'attributo DropDownList come Id e gli attributi Name dell'elemento Select form. Quindi il codice generato sarà simile a questa:

<select id="CustomerCode" name="CustomerCode"> 
    <option>... 
</select> 

Questo funziona alla grande, perché quando il modulo viene inviato, MVC popolerà la proprietà di destinazione con il valore selezionato dal menu a tendina in quanto il nome della lista a discesa generato IS la proprietà di destinazione. Io lo visualizzo in quanto la proprietà CustomerCodeList è un'estensione della specie della proprietà CustomerCode. Ho accoppiato i dati di origine alla proprietà.

Ecco il mio codice per il controllore:

public ActionResult Create() 
{ 
    //retrieve CustomerCodes from a datasource of your choosing 
    List<CustomerCode> customerCodeList = modelService.GetCustomerCodeList(); 

    CustomerViewModel viewModel= new CustomerViewModel(); 
    viewModel.CustomerCodeList = customerCodeList.Select(s => new SelectListItem() { Text = s.CustomerCode, Value = s.CustomerCode, Selected = (s.CustomerCode == viewModel.CustomerCode) }).AsEnumerable(); 

    return View(viewModel); 
} 

Ecco il mio codice per la DropDownListAttribute:

namespace AutoForm.Attributes 
{ 
    public class DropDownListAttribute : Attribute 
    { 
     public String DropDownListTargetProperty { get; set; } 
    } 
} 

Ecco il mio codice per il modello (DropDownList.ascx):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<SelectListItem>>" %> 
<%@ Import Namespace="AutoForm.Attributes"%> 

<script runat="server"> 
    DropDownListAttribute GetDropDownListAttribute() 
    { 
     var dropDownListAttribute = new DropDownListAttribute(); 

     if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("DropDownListAttribute")) 
     { 
      dropDownListAttribute = (DropDownListAttribute)ViewData.ModelMetadata.AdditionalValues["DropDownListAttribute"]; 
     } 

     return dropDownListAttribute;   
    } 
</script>  

<% DropDownListAttribute attribute = GetDropDownListAttribute();%> 

<select id="<%= attribute.DropDownListTargetProperty %>" name="<%= attribute.DropDownListTargetProperty %>"> 
    <% foreach(SelectListItem item in ViewData.Model) 
     {%> 
     <% if (item.Selected == true) {%> 
      <option value="<%= item.Value %>" selected="true"><%= item.Text %></option> 
     <% } %> 
     <% else {%> 
      <option value="<%= item.Value %>"><%= item.Text %></option> 
     <% } %> 
    <% } %> 
</select> 

Ho provato ad utilizzare l'helper Html.DropDownList, ma non mi avrebbe permesso di modificare l'ID e il nome attributi dell'elemento Select generato.

NOTA: è necessario eseguire l'override del metodo CreateMetadata di DataAnnotationsModelMetadataProvider per DropDownListAttribute. Ecco il codice per questo:

public class MetadataProvider : DataAnnotationsModelMetadataProvider 
{ 
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) 
    { 
     var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); 
     var additionalValues = attributes.OfType<DropDownListAttribute>().FirstOrDefault(); 

     if (additionalValues != null) 
     { 
      metadata.AdditionalValues.Add("DropDownListAttribute", additionalValues); 
     } 

     return metadata; 
    } 
} 

poi si deve effettuare una chiamata alla nuova MetadataProvider in Application_Start di Global.asax.cs:

protected void Application_Start() 
{ 
    RegisterRoutes(RouteTable.Routes); 

    ModelMetadataProviders.Current = new MetadataProvider(); 
} 

Beh, spero che questo ha un senso e spero questo approccio potrebbe farti risparmiare tempo. Vorrei un feedback su questo approccio per favore. C'è un approccio migliore?

risposta

1

Perfetto. Questo è quello che sto cercando. Grazie!

Ma il modello di esempio è un modello semplice. Come su un ViewModel complesso come

public class MaintainServicePackageViewModel 
{ 
    public IEnumerable<ServicePackageWithOwnerName> ServicePackageWithOwnerName { get; set; } 
    public ServicePackageWithOwnerName CurrentServicePackage { get; set; } 
    public IEnumerable<ServiceWithPackageName> ServiceInPackage { get; set; } 
} 

public class ServicePackageWithOwnerName : ServicePackage 
{ 
    [UIHint("DropDownList")] 
    [DropDownList(DropDownListTargetProperty = "Owner")] 
    [DisplayNameLocalized(typeof(Resources.Globalization), "OwnerName")] 
    public IEnumerable<SelectListItem> OwnerName { get; set; } 
} 

L'OwnerName è impostato su una DropDownList, ma non è un elemento diretto della ViewModel invece è un elemento figlio di ServicePackageWithOwnerName che è l'elemento del ViewModel. In tali condizioni, non è possibile impostare il valore OwnerName nel controller, come risolvere questo problema? Apprezzare!

saluti

Jack

+0

La prima cosa che viene in mente è quello di creare un modello personalizzato per editor di ServicePackageWithOwnerName. Il modello personalizzato ServicePackageWithOwnerName emetterebbe un dropdownbox per la proprietà OwnerName. In MaintainServicePackageViewModel, si adornerebbe CurrentServicePackage con UIHint ("[Nome tempalte nome server ServicePackageWithOwner]"). Sto pensando di esplorare questo concetto, ma non ho ancora avuto il tempo di farlo. – tschreck

+0

Jack- Controlla questo link, potrebbe essere quello che ti serve: http://dotnetslackers.com/articles/aspnet/asp-net-mvc-2-0-templating.aspx – tschreck

+1

Inoltre, controlla questo link. Brad Wilson ha creato una serie molto buona sul fare modelli. Questo collegamento riguarda i tipi complessi nidificati. http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html – tschreck

2

Credo di avere trovato una soluzione per farlo funzionare quando si utilizza Html.EditorForModel(); Quando si utilizza EditorForModel(), MVC utilizza Object.ascx per scorrere tutte le proprietà del modello e chiama il modello corrispondente per ogni proprietà nel modello. ASP.Net MVC out of the box ha Object.ascx nel codice, ma puoi creare il tuo Object.ascx. Basta creare una sottocartella EditorTemplates nella cartella della vista condivisa. Creare un file Object.ascx lì. (Leggere questo post per ulteriori informazioni: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html)

Ecco la mia Object.ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> 
<%@ Import Namespace="WebAppSolutions.Helpers" %> 
<% if (ViewData.TemplateInfo.TemplateDepth > 1) { %> 
<%= ViewData.ModelMetadata.SimpleDisplayText%> 
<% } 
else { %>  
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %> 

    <% var htmlFieldName = Html.HtmlFieldNameFor(prop.PropertyName);%> 

    <% if (prop.HideSurroundingHtml) { %> 
     <%= Html.Editor(htmlFieldName)%> 
    <% } 
     else { %> 
     <div id="<%= htmlFieldName %>Container" class="editor-field"> 
      <% if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())) { %> 
       <%= Html.Label(prop.PropertyName, Html.HtmlDisplayName(prop.PropertyName), prop.IsRequired)%> 
      <% } %> 
      <%= Html.Editor(prop.PropertyName, "", htmlFieldName)%> 
      <%= Html.ValidationMessage(prop.PropertyName, "*") %> 
     </div> 
    <% } %> 
<% } %> 

<%}%>

ho qualche codice custome nei miei WebAppSolutions.Helpers per HtmlFieldNameFor e HtmlDisplayName . Questi helper recuperano i dati dagli attributi applicati alle proprietà nel modello di vista.

public static String HtmlFieldNameFor<TModel>(this HtmlHelper<TModel> html, String propertyName) 
    { 
     ModelMetadata modelMetaData = GetModelMetaData(html, propertyName); 
     return GetHtmlFieldName(modelMetaData, propertyName); 
    } 

    public static String HtmlDisplayName<TModel>(this HtmlHelper<TModel> html, String propertyName) 
    { 
     ModelMetadata modelMetaData = GetModelMetaData(html, propertyName); 
     return modelMetaData.DisplayName ?? propertyName; 
    } 
    private static ModelMetadata GetModelMetaData<TModel>(HtmlHelper<TModel> html, String propertyName) 
    { 
     ModelMetadata modelMetaData = ModelMetadata.FromStringExpression(propertyName, html.ViewData); 
     return modelMetaData; 
    } 

    private static String GetHtmlFieldName(ModelMetadata modelMetaData, string defaultHtmlFieldName) 
    { 
     PropertyExtendedMetaDataAttribute propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute(modelMetaData); 
     return propertyExtendedMetaDataAttribute.HtmlFieldName ?? defaultHtmlFieldName; 
    } 

La chiave per ottenere questo al lavoro utilizzando EditorModelFor() è questo (dovrebbe essere la linea 20 o giù di lì in Object.ascx sopra):

<%= Html.Editor(prop.PropertyName, "", htmlFieldName)%> 

prop.PropertyName è la proprietà del ViewModel contenente l'elenco di dati che diventeranno DropDownList. htmlFieldName è il nome della proprietà nascosta che la proprietà DropDownList sta sostituendo. Ha senso?

Spero che questo ti aiuti.

+0

Hai un riferimento a 'PropertyExtendedMetaDataAttribute propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute (modelMetaData); ', ma non sono stato in grado di trovare quel tipo ovunque. È qualcos'altro che hai creato o è in qualche libreria di terze parti come MvcContrib? – TLS

+0

Se hai difficoltà ad accettare la tua risposta, contrassegna questo post per l'attenzione del moderatore. –