How to create keyless URL parameters in ASP.NET - c#

I have seen this answer describing ASP.NET support for keyless (not valueless) parameters, like http://some.url?param1&param2, and confirmed them to be viewable on Request.QueryString like:
var values = this.Request.QueryString.GetValues(null);
values.Any(o => o == "param1");
This is fine and dandy but now I want to generate urls like this. My first intuition was to use the RouteValueDictionary: routeValues parameter of Url.Action with null as a key:
#{
var dict = new RouteValueDictionary();
dict.Add(null, "param1");
dict.Add(null, "param2");
}
Very link, amaze
But apparently C# forbids nulls as dictionary keys because of reasons.
I have also tried the empty string as the key, but it results in a query string like: ?=param1,=param2 which contains 2 more equal signs that I want it to.
Of course I can string manipulate the heck out of my URL and add the &param1 part to the query string, but I was hoping for a concise solution.

You want to add the key values, but leaving the value null isn't allowed.
RouteValueDictionary ignores empty values
You could add a value like 1 for instance, but you lose your fine and dandy solution.
#{
var dict = new RouteValueDictionary();
dict.Add("param1",1);
}
Very link, amaze
For another solution you will have to write some custom code.

Since there's no built-in helper for this why don't you roll your own:
public static class UrlHelperExtensions
{
public static string MyAction(this UrlHelper urlHelper, string actionName, IList<string> parameters)
{
string url = urlHelper.Action(actionName);
if (parameters == null || !parameters.Any())
{
return url;
}
return string.Format("{0}?{1}", url, string.Join("&", parameters));
}
}
and then:
#{
var parameters = new List<string>();
parameters.Add("param1");
parameters.Add("param2");
}
#Url.MyAction("ActionName", parameters)

Related

Edit anonymous object passed into IUrlHelper

ASP.NET Core's UrlHelperExtensions is not that useful, because it only accepts object for route values, so I can't add/remove/merge route values. Before Core the equivalent in UrlHelper had an overload that took a RouteValueDictionary, which made it easy.
So I have a method that performs routing. It receives object routeValues, I want to add to it, and then pass to IUrlHelper.Action(...).
I can convert from object to RouteValueDictionary, but not back to anonymous object. Maybe there's another way?
public string GenerateOrderRoute(object routeValues = null)
{
var newRouteValues = //...I want to add/remove/merge to routeValues
// e.g. I want to add an area
return _urlHelper.Action("details", "orders", newRouteValues);
}
I found a way, but it uses dynamic, which I don't like. If you spot problems with this approach please let me know. And if there is a better/different way then please let me know.
public string GenerateOrderRoute(object routeValues = null)
{
// here I merge given route values with some of my own
var dict = (routeValues != null) ? new RouteValueDictionary(routeValues) : new RouteValueDictionary();
dict.Add("area", "commercial");
dict.Add("foo", "bar");
var expandoObject = new ExpandoObject();
var expandoDictionary = (IDictionary<string, object>)expandoObject;
foreach (var keyValuePair in dict)
{
expandoDictionary.Add(keyValuePair);
}
return _urlHelper.Action("details", "orders", expandoDictionary);
}

How do I pass query string parameters with names containing hyphens?

I need to pass a query string to a redirect. I know you can do this like so:
return RedirectToAction("Index", new { param1 = "hello" });
will go to /Index?param1=hello.
But I need to pass a parameter which has a hyphen in the name. Let's call it "data-param". Since hyphens aren't allowed in C# names, I can't do this in the above way. I know in some places in MVC, underscores are used to handle this, but that doesn't work here either, the underscore is passed to the query string as-is.
So my question is, how do I redirect to /Index?data-param=hello?
If you are trying to build a url, you can always slightly bypass the MVC routing and just pass the complete url in the old-fashioned way.
string dataParam="hello";
int otherParam=5;
return Redirect( String.Format("/Index?data-param={0}&data-param2={1}", dataParam, otherParam) );
If you are going outside of your own MVC application then you may not want RedirectToAction anyway unless you are redirecting to an action.
This works (I am using ASP.NET Core 3.1 and can't speak to earlier versions):
var params = new Dictionary<string, string>()
{
{ "data-param1", "hello" }
{ "data-param1", "2" }
};
return RedirectToAction("Index", params);
You can use Dictionary<string, object>() instead if you want, though you'll give up some control (e.g. false gets translated to "False" in the URL instead of "false"). And you should use nameof() for better maintainability:
var params = new Dictionary<string, object>()
{
{ "data-param1", "hello" }
{ "data-param2", 2 }
};
return RedirectToAction(nameof(Index), params);
Also note that this dictionary technique works with other methods too like RedirectToRoute().

RedirectToAction with unnamed querystring parameters

In MVC 2 I have a RedirectToAction call which I need to pass all of the querystring parameters. Unfortunately I can only find a way to pass named querystring parameters is there a way of passing all querystring parameters regardless.
We have one named parameter, id but we just want to append all of the rest onto the end of the URL without setting them explicitly.
return RedirectToAction("index", "enquiry", new { id = enquiryCacheId.ToString()});
You cannot pass COMPLEX objects in URLs, so that kills the option of passing on Complex types in new { }.
One option what you are left with is to encode the querystring and then send that in 'id'. Say for examples, you have querystring as follows name=rami&gender=male. Then you can encode it with HttpUtility.UrlEncode(), then set it to id=[encoded string]. On the other action (retrieving side) you can get id value and then use HttpUtility.UrlDecode() to decode the string. Then finally you can use HttpUtility.ParseQueryString() to split the querystring into NameValueCollection.
If above suggestion is not what you are looking for then. you need to add all querystring parameters to new { } in the RedirectToAction(). If you want to customize it then you might need to go to ASP.Net MVC Source code #CodePlex and make your own builds (which I think not worthy for this kind of requirement).
I have an extension method that I use to modify the querystring of the current URL:
public static string ModifyQueryString(this UrlHelper helper, NameValueCollection updates, IEnumerable<string> removes)
{
var request = helper.RequestContext.HttpContext.Request;
var url = request.Url.AbsolutePath;
var query = HttpUtility.ParseQueryString(request.QueryString.ToString());
updates = updates ?? new NameValueCollection();
foreach (string key in updates.Keys)
{
query.Set(key, updates[key]);
}
removes = removes ?? new List<string>();
foreach (string param in removes)
{
query.Remove(param);
}
if (query.HasKeys())
{
return string.Format("{0}?{1}", url, query.ToString());
}
else
{
return url;
}
}
But, if you need to modify an arbitrary URL, it should be easy enough to modify. You would just need to add a parameter to accept an arbitrary URL, and then instead of getting the URL/querystring from the HttpContext, you just split the passed URL at ?. The rest of the code should work the same.

Passing an array to Html.ActionLink()

I followed this example:
ASP.NET MVC - Pass array object as a route value within Html.ActionLink(...)
But, my Action is always called with null. What am I doing wrong?
foreach (OrderDetail od in order.OrderDetails)
{
rvd.Add("key" + count++, productID);
rvd.Add("key" + count++, productName);
}
#Html.ActionLink(linkText, "Renew", "Orders", rvd, new Dictionary<string, object>())
The query string is correctly generated, like ?key0=dog&key1=cat&key2=fish..., but I get a null parameter in my Action below:
public ActionResult Renew(RouteValueDictionary rvd)
{
// 'rvd' is null here!
}
Please note: I don't know the number of parameters in advance.
The query string is correctly generated, like ?key0=dog&key1=cat&key2=fish...
No, this is not a correct url. A correct url would have looked like this:
?%5B0%5D.Key=123&%5B0%5D.Value=dog&%5B1%5D.Key=456&%5B1%5D.Value=cat...
which would have mapped to:
public ActionResult Renew(Dictionary<int, string> rvd)
{
...
}
You could write a custom ActionLink to generate this url:
public static class LinkExtensions
{
public static IHtmlString MyActionLink(
this HtmlHelper html,
string linkText,
string actionName,
string controllerName,
IDictionary<string, string> parameters
)
{
var a = new TagBuilder("a");
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
var query = string.Join("&", parameters.Select((x, i) => string.Format("[{0}].Key={1}&[{0}].Value={2}", i, urlHelper.Encode(x.Key), urlHelper.Encode(x.Value))));
var url = string.Format(
"{0}?{1}",
urlHelper.Action(actionName, controllerName, null, html.ViewContext.HttpContext.Request.Url.Scheme),
query
);
a.Attributes["href"] = url;
a.SetInnerText(linkText);
return new HtmlString(a.ToString());
}
}
which you could use like this in your view:
#Html.MyActionLink(
linkText,
"Renew",
"Orders",
order.OrderDetails.ToDictionary(x => x.ProductID.ToString(), x => x.ProductName)
)
You can read more about the correct wire format for binding to various collections in this blog post.
I imagine what is happening is you are expecting the model binder to bind your array to a RouteValueDictionary, but the model binder doesn't know that key0=dog&key1=cat&key2=fish is supposed to be a dictionary. I would recommend changing your code to accept a string array. To do this, your query string needs to look something like this: ?rvd=dog&rvd=cat&rvd=fish
And your Action...
public ActionResult Renew(string[] rvd)
{
// 'rvd' is no longer null here!
}
The important part is rvd is the parameter name in your action, as well as the name of each element in the querystring: ?rvd=dog&rvd=cat&rvd=fish. If you really want to use a dictionary instead of a string array, then your querystring should look like this: ?rvd[0]=dog&rvd[1]=cat&rvd[2]=fish, giving each item an array index, but you would probably have to change your parameter from RouteValueDictionary to Dictionary<string,string>, I'm not quite sure. More info here. EDIT: See Darin's comment about binding to a dictionary, as I believe his is correct.
You may have to write your own extension for Html.ActionLink that accepts an array (or whatever OrderDetails is) and creates the querystring as an array. This looks like a pretty good starting place.

URL.Action with a string array?

I have an array of strings that I need to pass in a query string of Url.Action.
Url.Action("Index", "Resource", new { FormatIds = Model.FormatIDs})
Right now the link is showing up in my browser as System.String[] instead of a query string. Is it possible to have MVC do this automatically with model binding?
I need it to bind with my controller action like:
public ActionResult Index(string[] formatIDs)
To get the list of string to automatically bind using the default binder, you will need to provide them as:
name=value&name=value2&name=value3
So you'll need to convert your list to something like:
Index?formatIDs=1&formatIDs=2&formatIDs=3
For use the default model binder, you should end up with something like :
Index?formatIDs=value1&formatIDs=value2&formatIDs=value3
you can returns a private collection named HttpValueCollection even the documentation says it's a NameValueCollection using the ParseQueryString utility.
Then add the keys manually, HttpValueCollection do the encoding for you.
And then just append the QueryString manually :
var qs = HttpUtility.ParseQueryString("");
new string[] { "value1", "value2", "value3" }.ToList().ForEach(x => qs.Add("formatIDs", x));
Url.Action("Index", "Resource")?#qs
There is another way using the RouteValueDictionary with an array:
#{
var parameters = new RouteValueDictionary();
for (var i = 0; i < Model.CustomList.Count; ++i)
{
parameters.Add($"customListId[{i}]", Model.CustomList[i]);
}
}
usage:
var url = '#Html.Raw(Url.Action("ControllerActioon", "Controller", parameters))';
Still not very elegant - but working.

Categories

Resources