English 中文(简体)
ASP.Net MVC RouteData and arrays
原标题:

If I have an Action like this:

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

I can hit it with the following URL:

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

But in my ViewPage, I have this code:

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

Unfortunately, MVC is not smart enough to recognize that the action takes an array, and unrolls the list to form the proper url route. instead it just does a .ToString() on the object which just lists the data type in the case of a List.

Is there a way to get Html.ActionLink to generate a proper URL when one of the destination Action s parameters is an array or list?

-- edit --

As Josh pointed out below, ViewData["stuff"] is just an object. I tried to simplify the problem but instead caused an unrelated bug! I m actually using a dedicated ViewPage<T> so I have a tightly coupled type aware Model. The ActionLink actually looks like:

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

Where ViewData.Model.Stuff is typed as a List

最佳回答

I m thinking that a custom HtmlHelper would be in order.

 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 );
}
问题回答

you can suffix your routevalues with an array index like so:

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

this will result in the querystring containing test=val1&test=val2

that might help ?

Combining both methods works nicely.

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;
}

Then use this method in any extension method that requires routeValues with IEnumerable(s) in it.

Sadly, this workaround seams to be needed in MVC3 too.

This will just act as an extension to the UrlHelper and just provide a nice url ready to put anywhere rather than an an entire a tag, also it will preserve most of the other route values for any other specific urls being used... giving you the most friendly specific url you have (minus the IEnumerable values) and then just append the query string values at the end.

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()));
}   

There is a librarly called Unbinder, which you can use to insert complex objects into routes/urls.

It works like this:

using Unbound;

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

I m not at my workstation, but how about something like:

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

or the typed:

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




相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

热门标签