How do you persist querystring values in asp.net mvc? - asp.net-mvc

What is a good way to persist querystring values in asp.net mvc?
If I have a url:
/questions?page=2&sort=newest&items=50&showcomments=1&search=abcd
On paging links I want to keep those querystring values in all the links so they persist when the user clicks on the "next page" for example (in this case the page value would change, but the rest would stay the same)
I can think of 2 ways to do this:
Request.Querystring in the View and add the values to the links
Pass each querystring value from the Controller back into the View using ViewData
Is one better than the other? Are those the only options or is there a better way to do this?

i use a extension method for that:
public static string RouteLinkWithExtraValues(
this HtmlHelper htmlHelper,
string name,
object values)
{
var routeValues = new RouteValueDictionary(htmlHelper.ViewContext.RouteData.Values);
var extraValues = new RouteValueDictionary(values);
foreach (var val in extraValues)
{
if (!routeValues.ContainsKey(val.Key))
routeValues.Add(val.Key, val.Value);
else
routeValues[val.Key] = val.Value;
}
foreach (string key in htmlHelper.ViewContext.HttpContext.Request.Form)
{
routeValues[key] = htmlHelper.ViewContext.HttpContext.Request.Form[key];
}
foreach (string key in htmlHelper.ViewContext.HttpContext.Request.QueryString)
{
if (!routeValues.ContainsKey(key) && htmlHelper.ViewContext.HttpContext.Request.QueryString[key] != "")
routeValues[key] = htmlHelper.ViewContext.HttpContext.Request.QueryString[key];
}
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
return string.Format("{1}", urlHelper.RouteUrl(routeValues), name);
}

I would process the QueryString in the view (your option #1), instead of passing it in from the controller. This approach makes the view more self-contained, allowing you to convert it into a view control and re-use it across different views.
Note: Accessing the QueryString directly in the view may seem like a violation of the design principle of separating the Model and View, but in reality this data is a navigational concern which is related to the view, not really part of the model.

I would just keep the values in the Session that way the paging links only need to have;
/questions?page=2
/questions?page=3
The one reason why I would not us the QueryString is because I don't want the user to see the values that I am passing to the program. It makes it way too easy for them to go into the address bar and start changing the values to 'see what happens'. With this code all they could do is change the page number.

Here's how I done it in Asp.Net Core, first assign the query string parameters to ViewBags in your controller:
[HttpGet("/[controller]/[action]/{categoryId?}/{contractTypeId?}/{locationId?}")]
public IActionResult Index(Guid categoryId, int contractTypeId, Guid locationId)
{
ViewBag.CategoryId = categoryId;
ViewBag.ContractTypeId = contractTypeId;
ViewBag.LocationId = locationId;
...
}
Then pass the values to your links like so:
<a asp-action="Index" asp-controller="Jobs"
asp-route-categoryId="#teachingCategory.Id"
asp-route-contractTypeId="#ViewBag.ContractTypeId"
asp-route-locationId="#ViewBag.LocationId">
#teachingCategory.Description (#teachingCategory.Rank)
</a>
<a asp-action="Index" asp-controller="Jobs"
asp-route-categoryId="#ViewBag.CategoryId"
asp-route-contractTypeId="#typeOfEmployment.Id"
asp-route-locationId="#ViewBag.LocationId">
#typeOfEmployment.Name
</a>
<a asp-action="Index" asp-controller="Jobs"
asp-route-categoryId="#ViewBag.CategoryId"
asp-route-contractTypeId="#ViewBag.ContractTypeId"
asp-route-locationId="#item.Id">
#item.Id
</a>
Note that every link keep its own actual value and pass the rest of the route values through what we passed to ViewBag.

Related

Two ways of passing model back from view to controller

I'm new in MVC and have a question of principle about the way how the model is passed back from the view to the controller.
In the usual way the model-object comes from the controller, "spreads" its data into fields of the view and is then gone. The data will then be re-collected by a FormCollection-object passed back to the controller.
But there is also the other way where the model-object itself can be passed back to the controller as routedObject of e.g. an ActionLink, URL-Action or whatever. Then it's not necessary to spread and re-collect all the data.
In my work-place the other way is blocked, I get a warning about forbidden chars in the link-string. When investigating the issue I found that the other way seems mostly unknown.
For many reasons I think it's much better to pass the model-object back instead of the elaborate re-collecting of data.
So what is the reason for this curiosity please?
Update: Added View-Example
#model MvcApplication2.Models.TestClass
#{
#(Model.TestValue = 111);
}
<a href="#Url.Action("ValueBack", Model)">
<span>Test</span>
</a>
public void ValueBack(MvcApplication2.Models.TestClass testClass)
{
int x = testClass.TestValue;
}
In MVC we can get the values of the form in the controller by 3 ways.
Using the FormCollection Object.
Using the Parameters.
Strongly type model binding to view.
And I think it is not possible to send the model to the controller through the actionlink (as an ActionLink helper generates an anchor tag which when clicked sends a GET request to the server)
The Another Way :
Send the id of current model so that the controller action can fetch it back from the datastore from which it initially fetched it when rendering the View.
View:
#Ajax.ActionLink(
"Next",
"Step",
new {
StepId = 2,
id = Model.Id
},
new AjaxOptions { UpdateTargetId = "stepContainer" },
new { #class = "button" }
)
Controller:
public ActionResult Step(int StepId, int id)
{
var model = Repository.GetModel(id);
//Code
}

How can I return a view(model) and also pass a query string param to the view?

I have an MVC app where users fill in a 4-step form then go to a "confirm" screen. On the confirm screen, if they select to modify their info I use RedirectToAction to take them back to the first step view, and I pass a URL parameter "modify=true", which tells the controller to use the session object already created as opposed to creating a new object from the DB and displaying an empty form. But once they submit the form for step 1 I want to send them from my controller to the step 2 view along with the "modify=true" parameter. There doesn't seem to be a way to return a viewmodel to a view and also pass a query string parameter. How can I accomplish this?
I have considered adding a bool to the viewmodels to signify "inReview" but i use different viewmodels for each of these views and they're all pretty clean, it seems like this bool would muck things up a bit.
I have also considered adding the bool to viewbag or viewdata, but then i'd be using the submit button to pass that value and the "modify=true" parameter would drop off the URL, possibly confusing the user and definitely confusing the code.
Thanks
If you use the Html.BeginForm() helper (without parameters) it will automatically append existing query string parameters to the generated form action attribute. If you use some of the other overloads such as Html.BeginForm("SomeAction", "SomeController", FormMethod.Post) then you're gonna lose those parameters. This could be easily fixed by writing a custom helper that will take into account those parameters:
public static class HtmlHelpers
{
public static IDisposable BeginRequestForm(this HtmlHelper html, string action, string controller, FormMethod method)
{
var builder = new TagBuilder("form");
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
var routeValues = new RouteValueDictionary();
var query = html.ViewContext.HttpContext.Request.QueryString;
foreach (string key in query)
{
routeValues[key] = query[key];
}
builder.MergeAttribute("action", urlHelper.Action(action, controller, routeValues));
builder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
html.ViewContext.Writer.Write(builder.ToString(TagRenderMode.StartTag));
return new MvcForm(html.ViewContext);
}
}
and then use in your view (after bringing it into scope of course):
#using (Html.BeginRequestForm("SomeAction", "SomeController", FormMethod.Post))
{
...
}
You can either use ViewBag or your view model. You just need to pass the value somehow to the view:
ViewBag.modify = true;
return View(model);
Then in your view:
Html.BeginForm("MyAction", "MyController", new { modify = ViewBag.modify })

missunderstanding mvc default binding

I have multiselect jquery plagin (Choosen) and when I use it in 'Multiple Select' mode I expect in controller next values:
posted string = 'value1,value2...'
really have
posted string = 'value2'
only if I reffer directly to FormCollection I'll get expected values as below:
[HttpPost]
public ActionResult TagSearech(/*string tagSelect*/FormCollection c)
{
// only one value here
// string[] names = tagSelect.Split(',');
// as expected: value1,....
string expectedValue = c['tagSelect'];
return View();
}
I cant understand what might cause this behavior.
EDIT
Here is View:
#using (Html.BeginForm("TagSearech", "Tag"))
{
#Html.DropDownList("tagSelect", Model, new { #class = "chzn-select", data_placeholder = "tag names", multiple = "" })
<input type="submit"/>
}
MVC will attempt to bind the input data on the URL into the model. I haven't seen how Chosen.js posts the data back to the server, but essentially its coming in in the wrong format, so MVC binds the first element it sees to the string Model.
The FormsCollection retrieves all of the data that was posted in the URL, which is why all of your selected values can be seen there.
Did you try changing the incoming model from string to string[], and see if all of the items are bound to the array?

ASP.NET MVC Map String Url To A Route Value Object

I am creating a modular ASP.NET MVC application using areas. In short, I have created a greedy route that captures all routes beginning with {application}/{*catchAll}.
Here is the action:
// get /application/index
public ActionResult Index(string application, object catchAll)
{
// forward to partial request to return partial view
ViewData["partialRequest"] = new PartialRequest(catchAll);
// this gets called in the view page and uses a partial request class to return a partial view
}
Example:
The Url "/Application/Accounts/LogOn" will then cause the Index action to pass "/Accounts/LogOn" into the PartialRequest, but as a string value.
// partial request constructor
public PartialRequest(object routeValues)
{
RouteValueDictionary = new RouteValueDictionary(routeValues);
}
In this case, the route value dictionary will not return any values for the routeData, whereas if I specify a route in the Index Action:
ViewData["partialRequest"] = new PartialRequest(new { controller = "accounts", action = "logon" });
It works, and the routeData values contains a "controller" key and an "action" key; whereas before, the keys are empty, and therefore the rest of the class wont work.
So my question is, how can I convert the "/Accounts/LogOn" in the catchAll to "new { controller = "accounts", action = "logon" }"??
If this is not clear, I will explain more! :)
Matt
This is the "closest" I have got, but it obviously wont work for complex routes:
// split values into array
var routeParts = catchAll.ToString().Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
// feels like a hack
catchAll = new
{
controller = routeParts[0],
action = routeParts[1]
};
You need to know what part is what in the catchAll parameter. Then you need to parse it yourself (like you are doing in your example or use a regexp). There is no way for the framework to know what part is the controller name and what is the action name and so on, as you haven't specified that in your route.
Why do you want to do something like this? There is probably a better way.

ASP.NET MVC - Html.TextBox - Value not set via ViewData dictionary

I have a search box on a page (actually in a partial view though not sure that is relevant) with an Html.TextBox control.
<%= Html.TextBox("query", ViewData["query"], new { style = "width: 90%;" })%>
The action method takes "query" as a parameter, and I edit this value to clean up the string that is passed in:
public ActionResult SearchQuery(string query) {
ViewData["query"] = StringFunctions.ProperCasing(query.Replace("_", " "));
However, when it gets to the Html.TextBox the original query value (in this case with underscores) is preserved. I can see that the edited value is in the ViewData field, so for example, if:
query == "data_entry"
then, after being passed into the action method
ViewData["query"] == "data entry"
but the value, when it reaches the view, in the Html.TextBox is still "data_entry". It seems like there is a collision between the action method parameter "query" and the search box form parameter "query". Anyone know what is going on here or if there is another way to pass the value?
This action method is separate from the action that results from posting the search box data.
Html.Textbox helper looks for ModelState first (ASP.NET MVC source, InputExtensions.cs line 183, HtmlHelper.cs line 243). The simplest solution would be removing the ModelState for "query":
public ActionResult SearchQuery(string query)
{
ViewData["query"] = StringFunctions.ProperCasing(query.Replace("_", " "));
ModelState.Remove("query");
return View();
}
Dont know if this is the problem but my first thought is is to pass the view data back in the controller.
public ActionResult SearchQuery(string query)
{
ViewData["query"] = StringFunctions.ProperCasing(query.Replace("_", " "));
return view(ViewData):
}

Resources