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.
Related
I have seen this answer describing ASP.NET support for keyless (not valueless) parameters, like http://some.url?param1¶m2, 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 ¶m1 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)
I need to make a query with a string, variable long and can have several simultaneous queries:
String:
http://localhost:39051/api/values/punto1?name=125.25
http://localhost:39051/api/values/punto1?name=125.25&name1=1&name2=23.98
http://localhost:39051/api/values/punto1?name=125.25&name1=1&name2=23.98&name3=12.5
http://localhost:39051/api/values/punto1?name=125.25&name1=1&name2=23.98&name3=12.5&name6=34&name23=3
I have this configuration in webApiConfig.cs
config.Routes.MapHttpRoute(
name: "name",
routeTemplate: "api/{controller}/{id}/{name}/{name1}/{name2}",
defaults: new { id = RouteParameter.Optional, action = "GetProductsByName", name = string.Empty, name1 = string.Empty, name2 = string.Empty });
And I call GetProductsByName (....), which is in ValuesController.cs, where is the GET, POST, etc.
public string GetProductsByName (string name, string name1, string name2)
{
return "Requested name:" + ":" + name + ":" + name1 + ":" + name2;
}
It works and brings me the parameters name, name1 name2 and. But if I want to see more parameters, I have to define the in config.Routes. Which makes the system more complicated.
Need to separate the data into two parts and put them in string variables.
For example:
http://localhost:39051/api/values/punto1?name=125.25&name1=1&name2=23.98
string1: punto1
and the other string is put everything after the?
string2: name=125.25&name1=1&name2=23.98
or
string2: name=125.25&name1=1&name2=23.98&name3=12.5&name6=34&name23=3
Depending on the case
Then string2 separate process to scan values.
It works perfectly, I tried it and brings the data correctly.
One more query, if I get 4 different parameters, in consultation, as would be the separation of parameters
for example:
http://localhost:39051/api/values/punto1.25?name=5.25&name1=1&valor1=23.98&valor2=0.125&book2=17&book1=8&nivel15=9&nivel20=8
So you are searching by name(s) but you could have any number of names passed in?
You could bind to a List<string> to prevent you needing to add parameters and edit your routes.
public string Get([FromUri]List<string> Names)
{
string output = "Requested name";
foreach (var item in Names)
{
output += ":" + item;
}
return output;
}
To get that to work with your URL you need to send the parameters in the form
names[0]=firstparam&names1=secondParam
for example:
http://localhost:39051/api/values?names[0]=name1&names[1]=name2&names[2]=name3
will produce the output
Requested name:name1:name2:name3
Alternatively, as long as you keep the name the same you can remove the indexing and pass the parameters like this:
http://localhost:39051/api/values?names=name1&names=name2&names=name3
Note the [FromUri] is telling the model binder to take the values from the Uri. If you have a Post rather than a Get then you won't need the [FromUri] as the data will be in the body.
This way you won't need to change your routing at all when you want to add new parameters.
Be aware that there are limits to the length of the Uri that browsers allow. See this Stackoverflow question for more information.
Edit
As you can't change the URL, the only thing I think you could do is create your own ModelBinder.
You could create a binder that checks the parameters that match a particular criteria and then convert those into a list or dictionary in your action method.
To create a ModelBinder you need to create a class that implements IModelBinder then in your action method you need to decorate the parameter you wish to be bound using your binder with the ModelBinder attribute.
A very rough example that only handles parameters on the querystring would be:
public class MyModelBinder : IModelBinder
{
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
List<string> names = new List<string>();
if (!string.IsNullOrEmpty(actionContext.Request.RequestUri.Query))
{
foreach (var item in actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value))
{
if (item.Key.ToLower().StartsWith("name"))
{
//it's a "nameX" parameter so let's add it to the list
names.Add(item.Value);
}
}
}
//this is what will be bound to "name" in the action method
bindingContext.Model = names;
return true;
}
}
Then in the action method you can wire up your binder:
public string Get([ModelBinder(typeof(MyModelBinder))] List<string> name)
{
string output = "Requested name";
foreach (var item in name)
{
output += ":" + item;
}
return output;
}
Given the querystring
http://localhost:39051/api/values?name=name1&name1=name2&name2=name3
will again produce the output
Requested name:name1:name2:name3
It works perfectly, I tried it and brings the data correctly.
One more query, if I get 4 different parameters, in consultation, as would be the separation of parameters
for example:
http://localhost:39051/api/values/punto1.25?name=5.25&name1=1&valor1=23.98&valor2=0.125&book2=17&book1=8&nivel15=9&nivel20=8
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.
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.
This is hopefully a one liner.
I've read Phil Haack's article http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx and other questions, but those are about <form>s and using multiple names, rather than formatting a JS object for a $.ajax() request.
I have:
var elements$ = $('.someClass');
if(elements$.Length === 0){
//no need for call to server if no elements on page
return;
}
var elementsList = elements$.map(function (i, element) {
return $(element).data('some-data-attribute');
})
var data = getOtherObjectContainingData();
//encode in a format that can be automatically bound to list
data.ListOfString = elementsList.get().serializeArray(); //THIS LINE
$.post("ControllerName/ActionName", data, handler);
with controller action
public JsonResult ActionName(SomeObject OtherObject, List<string> ListOfString)
I have tried serialize(), serializeArray() and can solve this with a CSV string, but I don't want to faff about with it if the Model Binder can automatically do it if the request is formatted correctly.
How do I go about binding $('string', 'string', 'string') properly?
Create ListOfStrings as an array, then extend it as an object
data.ListOfStrings = $.extend({}, ListOfStrings);
Should do the trick but obviously I have no way of telling ;)
Have you tried .toArray() ?
var elementsList = elements$.map(function (i, element) {
return $(element).data('some-data-attribute');
}).toArray();
The .map() method returns a jquery array, calling .toArray() turns it into a pure javascript array.