If I have an Action like this:
public ActionResult DoStuff(List stuff)
{
...
ViewData[\"stuff\"] = stuff;
...
return View();
}
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()));
}
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 );
}
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.
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)));
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 ?
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) %>