传递到 Html.ActionLink 时序列化模型上的 IList 属性

发布于 2024-12-17 18:48:43 字数 848 浏览 1 评论 0原文

我正在尝试使用以下视图模型生成 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 来实现此目的

I'm trying to generate an Html.ActionLink with the following viewmodel:

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

To generate my link I use the following call:

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

Where Model is an instance of the SearchModel

The link generated is something like this:

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

Because it obviously is only calling the ToString method on every property.

What I would like to see generate is this:

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

Is there any way I can achieve this by using Html.ActionLink

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

作业与我同在 2024-12-24 18:48:43

在 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。

In MVC 3 you're just out of luck because the route values are stored in a RouteValueDictionary that as the name implies uses a Dictionary internally which makes it not possible to have multiple values associated to a single key. The route values should probably be stored in a NameValueCollection to support the same behavior as the query string.

However, if you can impose some constraints on the categories names and you're able to support a query string in the format:

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

then you could theoretically plug it into Html.ActionLink since MVC uses TypeDescriptor which in turn is extensible at runtime. The following code is presented to demonstrate it's possible, but I would not recommend it to be used, at least without further refactoring.

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

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

The implementation for the provider and the 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 would need the custom property descriptor to be able to return a custom value in GetValue which is called internally 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 a sample application that mimics the MVC route values creation:

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 ever give here at SO.

八巷 2024-12-24 18:48:43

当然是用 linq...

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

with linq of course...

string.Join("", Model.Categories.Select(c=>"&categories="+c))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文