Сериализация свойства IList для модели при передаче в Html.ActionLink - PullRequest
6 голосов
/ 24 ноября 2011

Я пытаюсь сгенерировать 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 ]

2 голосов
/ 25 ноября 2011

В 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.

0 голосов
/ 24 ноября 2011

с linq конечно ...

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