1

Imagine an object defined like :

public class MyViewModel{
    public List<string> MyList { get; set; }
}

In my view, i have this Action link :

@Ajax.ActionLink("<", "Index", new MyViewModel() { MyList = new List<string>() {"foo", "bar"}}, new AjaxOptions())

The html result of the ActionLink will be :

<a class="btn btn-default" data-ajax="true" href="/Index?MyList=System.Collections.Generic.List%601%5BSystem.String%5D">&lt;</a>

My question is, how get this result rather :

<a class="btn btn-default" data-ajax="true" href="/Index?MyList=foo&MyList=bar">&lt;</a>
GGO
  • 2,678
  • 4
  • 20
  • 42

3 Answers3

2

You can try string.Join. Something like this

@Ajax.ActionLink(
            "Your text", -- <
            "ActionName", -- Index
            new
            {
               MyList =string.Join(",", new List<string>() {"foo", "bar"}),
              otherPropertiesIfyouwant = YourValue
            }, -- rounteValues
            new AjaxOptions { UpdateTargetId = "..." }, -- Your Ajax option --optional
            new { @id = "back" } -- Your html attribute - optional
            )   
Chinh Phan
  • 1,459
  • 19
  • 22
1

You cannot use @Html.ActionLink() to generate route values for a collection. Internally the method (and all the MVC methods that generate urls) uses the .ToString() method of the property to generate the route/query string value (hence your MyList=System.Collections.Generic.List%601%5BSystem.String%5D" result).

The method does not perform recursion on complex properties or collections for good reason - apart from the ugly query string, you could easily exceed the query string limit and throw an exception.

Its not clear why you want to do this (the normal way is to pass an the ID of the object, and then get the data again in the GET method based on the ID), but you can so this by creating a RouteValueDictionary with indexed property names, and use it in your@Ajax.ActionLink() method.

In the view

@{
    var rvd = new RouteValueDictionary();
    rvd.Add("MyList[0]", "foo");
    rvd.Add("MyList[1]", "bar");
}
@Ajax.ActionLink("<", "Index", rvd, new AjaxOptions())

Which will make a GET to

public ActionResult Index(MyViewModel model)

However you must also make MyList a property (the DefaultModelBinder does not bind fields)

public class MyViewModel{
    public List<string> MyList { get; set; } // add getter/setter
}

and then the value of model.MyList in the POST method will be ["foo", "bar"].

  • Thanks Stephen, it's for a form filter without many items in MyList Collection, but i keep your warning about query string limit ! Do we have any way to do the RouteValueDictionary conversion automatically ? I'll try to develop one from your example using reflection – GGO Sep 20 '17 at 08:15
  • 1
    Well you can write your own extension method to do it. But your question just shows 2 hard-coded values so its not clear now what you really want –  Sep 20 '17 at 08:18
1

With Stephen's anwser, i have develop a helper extension method to do this.

Be careful of the URL query string limit : if the collection has too many values, the URL can be greater than 255 characters and throw an exception.

public static class AjaxHelperExtensions
{
    public static MvcHtmlString ActionLinkUsingCollection(this AjaxHelper ajaxHelper, string linkText, string actionName, object model, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
    {
        var rv = new RouteValueDictionary();
        foreach (var property in model.GetType().GetProperties())
        {
            if (typeof(ICollection).IsAssignableFrom(property.PropertyType))
            {
                var s = ((IEnumerable<object>)property.GetValue(model));
                if (s != null && s.Any())
                {
                    var values = s.Select(p => p.ToString()).Where(p => !string.IsNullOrEmpty(p)).ToList();
                    for (var i = 0; i < values.Count(); i++)
                        rv.Add(string.Concat(property.Name, "[", i, "]"), values[i]);
                }
            }
            else
            {
                var value = property.GetGetMethod().Invoke(model, null) == null ? "" : property.GetGetMethod().Invoke(model, null).ToString();
                if (!string.IsNullOrEmpty(value))
                    rv.Add(property.Name, value);
            }
        }
        return AjaxExtensions.ActionLink(ajaxHelper, linkText, actionName, rv, ajaxOptions, htmlAttributes);
    }
}
GGO
  • 2,678
  • 4
  • 20
  • 42