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

lom*_*axx 6 asp.net-mvc asp.net-mvc-routing

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

public class SearchModel
{
    public string KeyWords {get;set;}
    public IList<string> Categories {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

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

@Html.ActionLink("Index", "Search", Model)
Run Code Online (Sandbox Code Playgroud)

其中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

Joã*_*elo 2

在 MVC 3 中,您只是运气不好,因为路由值存储在 a 中RouteValueDictionary,顾名思义,它Dictionary在内部使用 a ,这使得不可能将多个值关联到单个键。路由值可能应该存储在 a 中NameValueCollection以支持与查询字符串相同的行为。

但是,如果您可以对类别名称施加一些限制,并且您能够支持以下格式的查询字符串:

http://www.test.com/search/index?keywords=bla&categories=Cat1|Cat2
Run Code Online (Sandbox Code Playgroud)

那么理论上你可以将其插入Html.ActionLink,因为 MVC 使用TypeDescriptor它在运行时又是可扩展的。下面的代码是为了证明它是可能的,但我不建议使用它,至少在不进一步重构的情况下。

话虽如此,您需要首先关联自定义类型描述提供程序:

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

提供者和覆盖属性描述符的自定义描述符的实现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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我们需要自定义属性描述符能够返回一个自定义值,其中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();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,为了证明它可以运行一个模拟 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));
}
Run Code Online (Sandbox Code Playgroud)

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

  • “过度工程”这个词在这里确实有一席之地:)这是一个极其复杂的解决方案,可以用一行 linq 解决:)但是很好的概念证明.. (2认同)