I have a method defined like this:
public ActionResult MatchedBusinesses(List<Business> businesses)
{
if (businesses != null)
{
return View(businesses);
}
return View("NoMatchFound");
}
Then, in my other method I have something similar to this one:
var list = results.AsEnumerable().OrderBy(b => Math.Abs(Convert.ToInt32(temp) - Convert.ToInt32(b.Zip))).Take(5).ToList();
return RedirectToAction("MatchedBusinesses", "Home", list);
The point is that, for the list variable I get the 5 entries that I select using the query. But, then I want to pass that result to my other method, which will be used in other method's view. The problem is, when I call the other method, the businesses parameter is always null. How can I solve the problem? Clearly, I'm not passing the parameter to my MatchedBusinesses method correctly. Any idea, how to solve the problem?
You are using the overload of RedirectToAction where the 3rd parameter is object routeValues. Internally the method uses reflection to build the route values based on the names and the ToString() values of the objects properties.
It works only for properties that are value types, but for properties that are complex types, including collections, it will not bind because (in your case) the value is a string "List<YourAssembly.Business>" and a string cannot be bound to a collection.
You need to persist the collection before redirecting (e.g. database, session, TempData) and then retrieve the collection in the action result.
For example
var list = results.AsEnumerable()....
TempData["results"] = list;
return RedirectToAction("MatchedBusinesses", "Home");
public ActionResult MatchedBusinesses()
{
List<Business> businesses = (List<Business>)TempData["results"];
}
but use TempData with caution (if the user refreshes the browser, the data will be lost). Its better to persist the information to the database with some key, and then pass the key as a route parameter to the MatchedBusinesses() method so that you can retrieve the data from the database.
Edit
What you're trying to do doesn't make much sense. You cannot, and should not, attempt to send large and/or complex objects, like a List, using Route. Instead you should use POST, or follow Stephen Muecke's suggestion in using TempData
However, here's how you can correctly send simple values using RouteValue
You pass parameters by using
return RedirectToAction("ActionName", "ControllerName",
new { paramName = paramValue });
Or if the target Action it's in the same controller
return RedirectToAction("ActionName", new { paramName = paramValue });
The parameter name, is optional. But using
return RedirectToAction("ActionName", new { paramName = paramValue });
Implies that the target action accepts a parameter with the name paramValue.
Here are all the overloads for RedirectToAction
http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.redirecttoaction%28v=vs.118%29.aspx
Try wrapping your parameter in your return statement in a blank object variable like so:
return RedirectToAction("MatchedBusinesses", "Home", new { businesses = list });
All of the route values for an action have to be one parameter, so it's passed as an object, and then split into the various parameters of the receiving action. Even if you have only one param, it's still looking for an object to split.
Related
I'm trying to make a service to AB-test different views on a page. The service has a method that takes a variable amount of ViewResults and returns one of them after determining which version to serve. The problem is that the model applied to the last ViewResult in the arguments list is applied to all arguments. The below code is a simplified version of my code just to illustrate the setup:
This is the method in the service:
public ViewResult GetTestVariantView(params ViewResult[] views){
...omitted randomization code that returns an index
return views[index] ?? views[0]
}
This is how it's used in the controller:
public ActionResult Index(){
var modelOne = new PageViewModel(letter: "A");
var modelTwo = new PageViewModel(letter: "B");
var modelThree = new PageViewModel(letter: "C");
return _testingService.GetTestVariantView(
View("~/Views/_PageView.cshtml", modelOne),
View("~/Views/_SomeOtherPageView.cshtml", modelTwo ),
View("~/Views/_PageView.cshtml", modelThree));
}
I expect to get for example _PageView.cshtml with modelOne if the service returns index 0 of the params list - but no, I get _PageView.cshtml with modelThree. Somehow just the model is taken from the last entry though, because if the service returns index 1 I get _SomeOtherPageView.cshtml with modelThree.
I can't find any documentation or similar questions asked about how to resolve this or even do it in another way. Is there a more proper way to determine which view to present using an external method? I know I could return an index from the method and have a switch/ifelse statement in the controller but I'd like to avoid that if possible simply because it clutters the controller.
What are my options?
Looking at the source code, the model passed to the View method is stored in the ViewData collection, not the returned ViewResult. If you call View multiple times with different non-null models, you will overwrite the ViewData.Model property each time, and only the last value will be used.
As Camilo said, change your code so that you only call View once. For example:
public (string viewName, object model) GetTestVariantView(
params (string viewName, object model)[] views)
{
...
}
(string viewName, object model) = _testingService.GetTestVariantView(
("~/Views/_PageView.cshtml", modelOne),
("~/Views/_SomeOtherPageView.cshtml", modelTwo ),
("~/Views/_PageView.cshtml", modelThree));
return View(viewName, model);
I have two action methods inside a controller. With the help of first action method, I am fetching the required data from database and I need to get this fetched data inside the second action method. I need to achieve this without passing any parameter to the second method. I don’t know how to do this as I am new to MVC.
Method 1:
public ActionResult GetData(HttpPostedFileBase file)
{
string folderPath = Server.MapPath(ConfigurationManager.AppSettings["BackupPath"]);
List<string> invalidRecords = new List<string>();
string backupFileName = FileUpload.BackupFile(file, folderPath);
invalidRecords = File.GetDataFromDB(backupFileName);
List<DataVM> lst = serviceData.GetAllData();
return View(lst);
}
Method 2:
[HttpPost]
public JsonResult GetInvalidRecords()
{
List<string> InvalidVehicleId = InvalidRecords;
return new JsonResult { Data = InvalidRecords };
}
You can use a session variable to hold the value of your 'lst' variable in your 'GetData' action:
Session["data"] = lst
Then you can retrieve it as follows and cast it to the appropriate type:
var data = (List<DataVM>)Session["data"]
https://msdn.microsoft.com/en-us/library/ms178581.aspx
You can use TempData as well
TempData["Name of Tempdata variable "]= File.GetDataFromDB(backupFileName)
and in other action you can recieve it
var getdatafromanotherAction= TempData["Name of Tempdata variable"]
If you want to call both action methods in single call, You can use
return RedirectToAction("GetInvalidRecords") in the first method. Also, you can create a class level private variable _listdata. Set it in first method , get in second method.
If you want to keep data between the 2 calls, use Tempdata.Keep and Tempdata.Peek but you need to make sure that call for second method immediately follows first method. Tempdata works between 2 requests only unless you extend its duration by using .Keep and .Peek once more.
If these two methods are to be called few calls apart, you can use session variable to keep the data.
Alternatively, you can keep the parameter of the second method call optional. So, it won't hurt other calls of the jsonresult method. Inside the method, you can check whether parameter is empty or not.
I have a website with two search forms, both calling the same controller and action. First form is for cars, second for motorcycles. Each form has multiple search filters. These filters are multivalued.
My route file:
routes.MapRoute("Cars",
"search/cars",
new { controller = "Search", action = "Index", SearchType = "Cars", Param1 = "", Param2="" }, null);
routes.MapRoute("Motorcycles",
"search/moto",
new { controller = "Search", action = "Index", SearchType = "Moto", Param3 = "", Param4="" }, null);
So calling "mywebsite.com/search/cars?Param1=BMW&Param1=VW" should get me these values into my controller:
SearchType = "Cars"
Param1[] = {"BMW", "VW"}
Is there any way to avoid having action in Search controller declared as:
public ActionResult Index(string SearchType, string Param1, string Param2, string Param3, string Param4){}
But instead have one params[] variable which would then contain key value pairs? Parameters in both cases have different names, so I can't always use the same name. Also each search page has different number of parameters.
You may want to consider using JSON and doing a POST with a JSON object. If you're method is going to take a variety of parameters to the point where it could have 3-4+ params, then you should reconsider how the data is transferred. Perhaps create a model for the search that has the filters as fields? You could then just accept the model as an object and handle it that way.
You should also consider using two different actions on the Search controller. Even though you're "searching", you've stated that each form handles different data, which means you will probably need to handle the search differently. Maybe use a Cars action and a Moto action?
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 have an ASP.net MVC controller called Designs that has an action with the following signature:
public ActionResult Multiple(int[] ids)
However, when I try to navigate to this action using the url:
http://localhost:54119/Designs/Multiple?ids=24041,24117
The ids parameter is always null. Is there any way to get MVC to convert the ?ids= URL query parameter into an array for the action? I've seen talk of using an action filter but as far as I can tell that will only work for POSTs where the array is passed in the request data rather than in the URL itself.
The default model binder expects this url:
http://localhost:54119/Designs/Multiple?ids=24041&ids=24117
in order to successfully bind to:
public ActionResult Multiple(int[] ids)
{
...
}
And if you want this to work with comma separated values you could write a custom model binder:
public class IntArrayModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value == null || string.IsNullOrEmpty(value.AttemptedValue))
{
return null;
}
return value
.AttemptedValue
.Split(',')
.Select(int.Parse)
.ToArray();
}
}
and then you could apply this model binder to a particular action argument:
public ActionResult Multiple([ModelBinder(typeof(IntArrayModelBinder))] int[] ids)
{
...
}
or apply it globally to all integer array parameters in your Application_Start in Global.asax:
ModelBinders.Binders.Add(typeof(int[]), new IntArrayModelBinder());
and now your controller action might look like this:
public ActionResult Multiple(int[] ids)
{
...
}
To extend on Darin Dimitrov's answer, something you can get away with is accepting a simple string in your URL parameter and converting it to an array yourself:
public ActionResult Multiple(string ids){
int[] idsArray = ids.Split(',').Select(int.Parse).ToArray();
/* ...process results... */
}
If you get a parse error while doing this (because someone passed you a malformed array), you can cause your exception handler to return a 400 Bad Request error instead of the default, more unfriendly 404 Not Found error that MVC returns when an endpoint is not found.
You can also use this URL format, and ASP.NET MVC will do everything for you. But, remember to apply URL encoding.
?param1[0]=3344¶m1[1]=2222
I don't know where Groky's URL string was coming from, but I had the same problem with some javascript calling my controller/action. It would build up a URL of null, 1, or many "IDs" from a multiple-select list (which is unique to the solution I'm going to share).
I copy/pasted Darin's custom model binder and decorated my action/parameter, but it didn't work. I still got null valued int[] ids. Even in the "safe" case where I actually did have many IDs.
I ended up changing the javascript to produce an ASP.NET MVC friendly parameter array like
?ids=1&ids=2
I had to do some silly stuff, though
ids || [] #=> if null, get an empty array
[ids || []] #=> if a single item, wrap it in an array
[].concat.apply([], ...) #=> in case I wrapped an array, flatten it
So, the full block was
ids = [].concat.apply([], [ids || []])
id_parameter = 'ids=' + ids.join('&ids=')
It's messy, but it's the first time I had to hack like this in javascript.
.Net Core Answer
For those coming here in recent times, you can do this in .Net Core with:
http://localhost:54119/Designs/Multiple?ids=24041&ids=24117
and:
public ActionResult Multiple([FromQuery] int[] ids)
{
...
}