В MVC 3 вам просто не повезло, потому что значения маршрута хранятся в RouteValueDictionary
, что, как следует из названия, использует Dictionary
внутри, что не позволяет иметь несколько значений, связанных с одним ключом. Значения маршрута, вероятно, должны храниться в NameValueCollection
для поддержки того же поведения, что и строка запроса.
Однако, если вы можете наложить некоторые ограничения на имена категорий, и вы можете поддерживать строку запроса в формате:
http://www.test.com/search/index?keywords=bla&categories=Cat1|Cat2
тогда вы можете теоретически подключить его к Html.ActionLink
, поскольку MVC использует TypeDescriptor
, который, в свою очередь, расширяем во время выполнения. Следующий код представлен, чтобы продемонстрировать, что это возможно, , но я бы не рекомендовал использовать его , по крайней мере, без дальнейшего рефакторинга.
Сказав это, вам нужно начать с привязки провайдера описания пользовательских типов:
[TypeDescriptionProvider(typeof(SearchModelTypeDescriptionProvider))]
public class SearchModel
{
public string KeyWords { get; set; }
public IList<string> Categories { get; set; }
}
Реализация для провайдера и пользовательского дескриптора, который переопределяет дескриптор свойства для свойства 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);
}
}
Тогда нам потребуется дескриптор пользовательского свойства, чтобы иметь возможность возвращать пользовательское значение в GetValue
, которое вызывается внутренне 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();
}
}
И, наконец, чтобы доказать, что работает пример приложения, имитирующего создание значений маршрута 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));
}
Черт, это, наверное, самый длинный ответ, который я когда-либо давал здесь, в SO.