ASP.Net MVC RouteData и массивы

Если у меня есть действие вроде этого:

public ActionResult DoStuff(List<string> stuff)
{
   ...
   ViewData["stuff"] = stuff;
   ...
   return View();
}

Я могу нажать на него со следующим URL-адресом:

http://mymvcapp.com/controller/DoStuff?stuff=hello&stuff=world&stuff=foo&stuff=bar

Но в моей ViewPage у меня есть этот код:

<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = ViewData["stuff"] }, null) %>

К сожалению, MVC недостаточно умен, чтобы распознать, что действие принимает массив, и разворачивает список, чтобы сформировать правильный маршрут URL. вместо этого он просто делает .ToString() для объекта, который просто перечисляет тип данных в случае списка.

Есть ли способ заставить Html.ActionLink генерировать правильный URL-адрес, когда один из целевых параметров действия - это массив или список?

- изменить -

Как указал Джош ниже, ViewData [ "stuff" ] - это просто объект. Я попытался упростить проблему, но вместо этого создал несвязанную ошибку! Я фактически использую выделенный ViewPage <T> поэтому у меня плотно связанная модель типа. ActionLink на самом деле выглядит так:

<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = ViewData.Model.Stuff }, null) %>

Где ViewData.Model.Stuff вводится как List

Ответ 1

Я думаю, что пользовательский HtmlHelper будет в порядке.

 public static string ActionLinkWithList( this HtmlHelper helper, string text, string action, string controller, object routeData, object htmlAttributes )
 {
     var urlHelper = new UrlHelper( helper.ViewContext.RequestContext );


     string href = urlHelper.Action( action, controller );

     if (routeData != null)
     {
         RouteValueDictionary rv = new RouteValueDictionary( routeData );
         List<string> urlParameters = new List<string>();
         foreach (var key in rv.Keys)
         {
             object value = rv[key];
             if (value is IEnumerable && !(value is string))
             {
                 int i = 0;
                 foreach (object val in (IEnumerable)value)
                 {
                     urlParameters.Add( string.Format( "{0}[{2}]={1}", key, val, i ));
                     ++i;
                 }
             }
             else if (value != null)
             {
                 urlParameters.Add( string.Format( "{0}={1}", key, value ) );
             }
         }
         string paramString = string.Join( "&", urlParameters.ToArray() ); // ToArray not needed in 4.0
         if (!string.IsNullOrEmpty( paramString ))
         {
            href += "?" + paramString;
         }
     }

     TagBuilder builder = new TagBuilder( "a" );
     builder.Attributes.Add("href",href);
     builder.MergeAttributes( new RouteValueDictionary( htmlAttributes ) );
     builder.SetInnerText( text );
     return builder.ToString( TagRenderMode.Normal );
}

Ответ 2

вы можете суффиксом routevalues с индексом массива следующим образом:

RouteValueDictionary rv = new RouteValueDictionary();
rv.Add("test[0]", val1);
rv.Add("test[1]", val2);

это приведет к появлению запроса, содержащего test=val1&test=val2

который может помочь?

Ответ 3

Сочетание обоих методов прекрасно работает.

public static RouteValueDictionary FixListRouteDataValues(RouteValueDictionary routes)
{
    var newRv = new RouteValueDictionary();
    foreach (var key in routes.Keys)
    {
        object value = routes[key];
        if (value is IEnumerable && !(value is string))
        {
            int index = 0;
            foreach (string val in (IEnumerable)value)
            {
                newRv.Add(string.Format("{0}[{1}]", key, index), val);
                index++;
            }
        }
        else
        {
            newRv.Add(key, value);
        }
    }

    return newRv;
}

Затем используйте этот метод в любом методе расширения, который требует routeValues ​​с IEnumerable (s) в нем.

К сожалению, это временные швы необходимо также в MVC3.

Ответ 4

Это будет действовать как расширение для UrlHelper и просто предоставить хороший URL-адрес, готовый разместить куда угодно, а не весь тег, а также сохранит большинство других значений маршрута для любых других используемых URL-адресов... давая вам самый дружественный конкретный url, который у вас есть (минус значения IEnumerable), а затем просто добавляйте значения строки запроса в конец.

public static string ActionWithList(this UrlHelper helper, string action, object routeData)
{

    RouteValueDictionary rv = new RouteValueDictionary(routeData);

    var newRv = new RouteValueDictionary();
    var arrayRv = new RouteValueDictionary();
    foreach (var kvp in rv)
    {
        var nrv = newRv;
        var val = kvp.Value;
        if (val is IEnumerable && !(val is string))
        {
            nrv = arrayRv;
        }

        nrv.Add(kvp.Key, val);

    }


    string href = helper.Action(action, newRv);

    foreach (var kvp in arrayRv)
    {
        IEnumerable lst = kvp.Value as IEnumerable;
        var key = kvp.Key;
        foreach (var val in lst)
        {
            href = href.AddQueryString(key, val);
        }

    }
    return href;
}

public static string AddQueryString(this string url, string name, object value)
{
    url = url ?? "";

    char join = '?';
    if (url.Contains('?'))
        join = '&';

    return string.Concat(url, join, name, "=", HttpUtility.UrlEncode(value.ToString()));
}   

Ответ 5

Существует текстовое выражение Unbinder, которое можно использовать для вставки сложных объектов в маршруты /URL.

Он работает следующим образом:

using Unbound;

Unbinder u = new Unbinder();
string url = Url.RouteUrl("routeName", new RouteValueDictionary(u.Unbind(YourComplexObject)));

Ответ 6

Я не на своей рабочей станции, но как насчет чего-то вроде:

<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = (List<T>)ViewData["stuff"] }, null) %>

или напечатано:

<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = (List<T>)ViewData.Model.Stuff }, null) %>