Serialize the IList property on the model when passing to Html.ActionLink

I am trying to create an Html.ActionLink with the following view model:

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

To create my link, I use the following call:

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

Where Model is an instance of SearchModel

The generated link looks something like this:

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

Because it explicitly calls the ToString method for each property.

What I would like to see is the following:

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

Is it possible to achieve this using Html.ActionLink

+6
source share
2 answers

In MVC 3, you were just out of luck, because route values ​​are stored in RouteValueDictionary , which, as the name implies, is used inside the Dictionary , which makes it impossible to have multiple values ​​associated with a single key. NameValueCollection likely, route values ​​should be stored in NameValueCollection to support the same behavior as the query string.

However, if you can impose some restrictions on category names, and you can support the query string in the format:

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

then you can theoretically connect it to Html.ActionLink , since MVC uses a TypeDescriptor , which, in turn, expands at run time. The following code is presented to demonstrate this, but I would not recommend using it , at least without further refactoring.

Having said that, you need to start by combining a custom type description provider:

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

A provider implementation and a custom descriptor that overrides the property descriptor for the Categories property:

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

Then we need a custom property descriptor to be able to return a custom value to GetValue , which is internally called by 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(); } } 

And finally, to prove that it works with a sample application that simulates the creation of MVC route values:

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

Damn, this is probably the longest answer I've ever given here at SO.

+2
source

with linq, of course ...

 string.Join("", Model.Categories.Select(c=>"&categories="+c)) 
0
source

Source: https://habr.com/ru/post/902324/


All Articles