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 () для объекта, который просто перечисляет тип данных в случае List.

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

-- редактировать --

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

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

Где ViewData.Model.Stuff набирается как список


person puffpio    schedule 17.11.2009    source источник
comment
ViewData [материал] - это просто объект. Что происходит, когда вы переходите в реальный список, например {Stuff = (List ‹string›) ViewData [stuff]} или {Stuff = ViewData [stuff] as List ‹string›} или {Stuff = new List ‹string› (. ..)}?   -  person Josh Pearce    schedule 18.11.2009
comment
та же проблема ... В моей реальной реализации в настоящее время я использую тесно связанный ViewPage ‹T›, поэтому эта строка больше похожа на: ‹% = Html.ActionLink (щелкните здесь, DoMoreStuff, MoreStuffController, новый {stuff = ViewData.Model.Stuff} , null)% ›где ViewData.Model.Stuff набирается в виде списка ‹string›   -  person puffpio    schedule 18.11.2009


Ответы (6)


Думаю, подойдет специальный 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 );
}
person tvanfosson    schedule 18.11.2009
comment
Да, это точно сработает. В более общем плане, какой уровень совместимости следует ожидать между генераторами URL-адресов, такими как UrlHelper.Action, и привязкой параметров действия? Я имею ввиду, это ошибка что ли? - person Josh Pearce; 18.11.2009
comment
Нет, это не ошибка. Я бы назвал это дизайнерским решением. Случай, когда у вас есть несколько значений для параметра, довольно редок, особенно при получении. Я думаю, что ожидание того, что объект представляет собой карту простых типов значений, хорошо служит большинству людей. - person tvanfosson; 18.11.2009
comment
Спасибо за это, это направило мои мысли в правильное русло. Это точно не сработает, потому что строка - это IEnumerable, который будет перечислять символы. Он также выполняет все параметры строки запроса параметров маршрута, тогда как исходный ActionLink принимает во внимание маршруты, созданные в global.asax.cs, для форматирования параметров маршрута в стиле REST, который его поддерживает. - person puffpio; 19.11.2009
comment
Я воспринял вашу идею и использовал LINQ, чтобы получить все элементы маршрутизируемых данных в виде IEnumerable ‹string› .. и сгенерировал с ними частичную строку запроса. Затем я взял оставшиеся данные маршрута и добавил к ним еще одну запись, например replacemequerystringname, replacmequerystringvalue. Затем я использовал обычный метод ActionLink с оставшимися данными маршрута. Затем я заменяю указанную выше запись маршрута, которая была преобразована в параметр строки запроса, на мою вышеупомянутую частичную строку запроса. - person puffpio; 19.11.2009
comment
Хм. Может быть, проверьте, реализует ли он IList вместо IEnumerable? Или специально исключить строку. - person tvanfosson; 19.11.2009
comment
@tvanfosson в вашем внутреннем цикле foreach, у вас поменялись местами val и i? - person Brian Sweeney; 03.03.2016
comment
@BrianSweeney Я так не думаю, параметры упорядочены 0, 2, 1, так что значение i находится внутри скобок, а значение val справа от знака равенства. - person tvanfosson; 03.03.2016
comment
Отлично работает после того, как я изменил тип возвращаемого значения на MvcHtmlString - person Patrick Koorevaar; 09.12.2019

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

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

это приведет к тому, что строка запроса будет содержать test=val1&test=val2

что может помочь?

person Community    schedule 07.01.2010
comment
спасибо, это великолепно ... о, подождите, неважно, это не сработало, как ожидалось - person JarrettV; 21.11.2010
comment
@JarrettV URL-адрес вроде в порядке, он просто закодирован (и должен быть) - person Mathias F; 19.09.2014

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

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.

person Emanuel    schedule 06.03.2011

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

Работает это так:

using Unbound;

Unbinder u = new Unbinder();
string url = Url.RouteUrl("routeName", new RouteValueDictionary(u.Unbind(YourComplexObject)));
person Krisztián Balla    schedule 23.05.2014

Это будет действовать как расширение для 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()));
}   
person Community    schedule 15.06.2011

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

<%= 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) %>
person Chaddeus    schedule 18.11.2009
comment
Он по-прежнему будет вызывать ToString () для объекта. - person Omar; 18.11.2009