2011-11-24 15 views
6

Próbuję wygenerować Html.ActionLink z następującym viewmodel:nieruchomość Serializować IList od modelu, kiedy przeszedł do Html.ActionLink

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

Aby wygenerować mój link używam następujące połączenia:

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

Jeżeli model jest instancją SearchModel

Łącze generowanych jest mniej więcej tak:

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

Ponieważ oczywiście wywołuje metodę ToString dla każdej właściwości.

Co chciałbym zobaczyć wygenerować to:

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

Czy jest jakiś sposób można to osiągnąć za pomocą Html.ActionLink

+0

Wygląda na duplikat: http://stackoverflow.com/q/1752721/25727. Zobacz pierwszą odpowiedź na rozwiązanie z niestandardowym HtmlHelper. – Jan

Odpowiedz

2

W MVC 3 masz po prostu pecha, ponieważ wartości trasy są przechowywane w RouteValueDictionary, które jak sama nazwa wskazuje używa Dictionary wewnętrznie, co uniemożliwia przypisanie wielu wartości do jednego klucza. Wartości trasy powinny być prawdopodobnie przechowywane w pliku NameValueCollection, aby obsługiwać to samo zachowanie, co ciąg zapytania.

Jednak czy można nałożyć pewne ograniczenia na nazwy kategorii i jesteś w stanie obsługiwać ciąg kwerendy w formacie:

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

następnie można teoretycznie podłączyć go do Html.ActionLink od MVC używa TypeDescriptor które z kolei jest rozszerzalny w czasie wykonywania. Poniższy kod przedstawiono w celu wykazania, że ​​jest to możliwe, , ale nie zalecałbym używania go pod, przynajmniej bez dalszego refaktoryzacji.

Mimo, że trzeba by zacząć od kojarzenia niestandardowy typ Opis Provider:

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

Implementacja dostawcy i niestandardowych deskryptora który nadpisuje deskryptor właściwości dla właściwości Categories:

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); 
    } 
} 

Następnie potrzebowalibyśmy niestandardowego deskryptora właściwości, aby móc zwrócić niestandardową wartość w GetValue, która jest wewnętrznie wywoływana przez 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(); 
    } 
} 

I wreszcie udowodnić, że działa przykładową aplikację, która naśladuje Wartości trasy tworzenia 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)); 
} 

Cholera, to prawdopodobnie najdłuższy odpowiedź kiedykolwiek dać tutaj na SO.

+0

Dziękuję za tę odpowiedź. Zbliża się do tego, aby wypluć odpowiednie wartości, po prostu jeśli użyjesz | jako ogranicznik musisz użyć niestandardowego segregatora modelu w drodze powrotnej, więc zmieniłem wersję, tak aby ogranicznik był ciągiem.Format ("& {0}", właściwość.nazwa) ... działa jak urok! – lomaxx

+2

słowo "overengineering" naprawdę ma tu swoje miejsce :) jest to absurdalnie kompleksowe rozwiązanie dla czegoś, co można rozwiązać za pomocą linii linq :), ale niezły dowód koncepcji .. – rouen

+0

@rouen to nie nadmierne przesuwanie i nie jest tak naprawdę rozwiązane linq. Łączenie łańcucha jest, ale to nie jest cały problem, to tylko część problemu. Druga część polega na tym, że jest on używany jako pierwszorzędny obywatel w modelu rutowania. Właśnie to Joao próbuje zademonstrować – lomaxx