传递给Html.ActionLink时,在模型上序列化IList属性

时间:2011-11-24 19:02:16

标签: asp.net-mvc asp.net-mvc-routing

我正在尝试使用以下viewmodel生成Html.ActionLink:

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

要生成我的链接,请使用以下调用:

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

其中Model是SearchModel的实例

生成的链接是这样的:

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

因为它显然只是在每个属性上调用ToString方法。

我希望看到的是:

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

有什么办法可以通过Html.ActionLink

实现这一目标

2 个答案:

答案 0 :(得分:2)

在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

然后我们需要自定义属性描述符能够返回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));
}

最后证明它适用于模仿MVC路由值创建的示例应用程序:

{{1}}

该死的,这可能是我在这里给出的最长的答案。

答案 1 :(得分:0)

当然还有linq ......

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