2011-11-24 2 views
6

Sto cercando di generare un Html.ActionLink con il seguente ViewModel:proprietà Serialize IList del modello quando passò nelle Html.ActionLink

public class SearchModel 
{ 
    public string KeyWords {get;set;} 
    public IList<string> Categories {get;set;} 
} 

Per generare il mio link Io uso la seguente chiamata:

@Html.ActionLink("Index", "Search", Model) 

dove il modello è un'istanza della SearchModel

il link generato è qualcosa di simile:

http://www.test.com/search/index?keywords=bla&categories=System.Collections.Generic.List

Perché ovviamente si chiama solo il metodo ToString su ogni proprietà.

Quello che mi piacerebbe vedere generare è questo:

http://www.test.com/search/index?keywords=bla&categories=Cat1&categories=Cat2

non v'è alcun modo per raggiungere questo obiettivo utilizzando Html.ActionLink

+0

Sembra un duplicato: http://stackoverflow.com/q/1752721/25727. Vedere la prima risposta per una soluzione con un HtmlHelper personalizzato. – Jan

risposta

2

In MVC 3 non hai fortuna perché i valori del percorso sono memorizzati in un RouteValueDictionary che come suggerisce il nome utilizza uno Dictionary internamente che non consente di avere più valori associati a una singola chiave. I valori del percorso dovrebbero probabilmente essere memorizzati in un NameValueCollection per supportare lo stesso comportamento della stringa di query.

Tuttavia, se si può imporre alcuni vincoli sui nomi categorie e siete in grado di supportare una stringa di query nel formato:

http://www.test.com/search/index?keywords=bla&categories=Cat1|Cat2 

allora si potrebbe teoricamente collegarlo Html.ActionLink dal MVC utilizza TypeDescriptor che a sua volta è estensibile in fase di runtime. Il seguente codice è presentato per dimostrare che è possibile, ma non lo consiglierei di essere utilizzato, almeno senza ulteriori refactoring.

Detto questo, si avrebbe bisogno di iniziare associando una descrizione tipo personalizzato fornitore:

[TypeDescriptionProvider(typeof(SearchModelTypeDescriptionProvider))] 
public class SearchModel 
{ 
    public string KeyWords { get; set; } 
    public IList<string> Categories { get; set; } 
} 

L'implementazione per il provider e il descrittore personalizzato che sostituisce il descrittore di proprietà per la Categories immobile:

class SearchModelTypeDescriptionProvider : TypeDescriptionProvider 
{ 
    public override ICustomTypeDescriptor GetTypeDescriptor(
     Type objectType, object instance) 
    { 
     var searchModel = instance as SearchModel; 
     if (searchModel != null) 
     { 
      var properties = new List<PropertyDescriptor>(); 

      properties.Add(TypeDescriptor.CreateProperty(
       objectType, "KeyWords", typeof(string))); 
      properties.Add(new ListPropertyDescriptor("Categories")); 

      return new SearchModelTypeDescriptor(properties.ToArray()); 
     } 
     return base.GetTypeDescriptor(objectType, instance); 
    } 
} 
class SearchModelTypeDescriptor : CustomTypeDescriptor 
{ 
    public SearchModelTypeDescriptor(PropertyDescriptor[] properties) 
    { 
     this.Properties = properties; 
    } 
    public PropertyDescriptor[] Properties { get; set; } 
    public override PropertyDescriptorCollection GetProperties() 
    { 
     return new PropertyDescriptorCollection(this.Properties); 
    } 
} 

Quindi avremmo bisogno del descrittore di proprietà personalizzato per poter restituire un valore personalizzato in GetValue che viene chiamato internamente da MVC:

class ListPropertyDescriptor : PropertyDescriptor 
{ 
    public ListPropertyDescriptor(string name) 
     : base(name, new Attribute[] { }) { } 

    public override bool CanResetValue(object component) 
    { 
     return false; 
    } 
    public override Type ComponentType 
    { 
     get { throw new NotImplementedException(); } 
    } 
    public override object GetValue(object component) 
    { 
     var property = component.GetType().GetProperty(this.Name); 
     var list = (IList<string>)property.GetValue(component, null); 
     return string.Join("|", list); 
    } 
    public override bool IsReadOnly { get { return false; } } 
    public override Type PropertyType 
    { 
     get { throw new NotImplementedException(); } 
    } 
    public override void ResetValue(object component) { } 
    public override void SetValue(object component, object value) { } 
    public override bool ShouldSerializeValue(object component) 
    { 
     throw new NotImplementedException(); 
    } 
} 

E infine per dimostrare che funziona un'applicazione di esempio che imita la creazione valori percorso MVC:

static void Main(string[] args) 
{ 
    var model = new SearchModel { KeyWords = "overengineering" }; 

    model.Categories = new List<string> { "1", "2", "3" }; 

    var properties = TypeDescriptor.GetProperties(model); 

    var dictionary = new Dictionary<string, object>(); 
    foreach (PropertyDescriptor p in properties) 
    { 
     dictionary.Add(p.Name, p.GetValue(model)); 
    } 

    // Prints: KeyWords, Categories 
    Console.WriteLine(string.Join(", ", dictionary.Keys)); 
    // Prints: overengineering, 1|2|3 
    Console.WriteLine(string.Join(", ", dictionary.Values)); 
} 

Accidenti, questa è probabilmente la risposta più lunga che io abbia mai dato qui a SO.

+0

Grazie per questa risposta. Diventa molto vicino a farlo sputare i valori giusti, è solo che se usi | come delimitatore devi utilizzare un modello di raccoglitore personalizzato al momento del ritorno, quindi ho cambiato la mia versione in modo che il delimitatore fosse string.Format ("& {0}", property.Name) ... funziona come un incantesimo! – lomaxx

+2

la parola "overengineering" ha davvero il suo posto qui :) questa è una soluzione ridicolmente complessa per qualcosa di risolvibile con una linea di linq :) ma bella prova del concetto .. – rouen

+0

@rouen non è overengineering e in realtà non è risolvibile da linq. L'unione della stringa è, ma non è l'intero problema, è solo una parte del problema. L'altra parte è farla diventare un cittadino di prima classe nel modello di routing. Questo è ciò che Joao sta cercando di dimostrare – lomaxx