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:
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)); }
Damn, this is probably the longest answer I've ever given here at SO.