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

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):
}

Related

Partialview result returns null

Partial view does not return any data.When i check with debug tool on PartialView page(_ContentBlock.cshtml), model seem to null.
Controller
public ActionResult Module()
{
int RouteDataValue = default(int);
if (RouteData.Values["id"] != null)
{
RouteDataValue = int.Parse(RouteData.Values["id"].ToString());
}
using (Models.ContentModel db = new Models.ContentModel())
{
var Query = from n in db.PageModule
join m in db.Module on n.ModuleId equals m.ModuleId
where n.PageId == RouteDataValue
select m.PhysicalPath;
return PartialView(Query.Single()); //returns PartialView such as ~/Modules/Content/_ContentBlock.cshtml
}
}
public PartialViewResult ContentBlock()
{
using (Models.ContentModel db = new Models.ContentModel())
{
return PartialView("~/Modules/Content/_ContentBlock.cshtml", db.ContentBlock.Where(n => n.PageId == 2).Single());
}
}
Page.cshtml
#Html.Action("Module")
_ContentBlock.cshtml
#model IEnumerable<Models.ContentBlock>
#foreach (var item in Model)
{
#Html.DisplayFor(n => item.Content)
}
You seem to have used the Html.Partial helper instead of Html.Action. So you were basically only rendering the partial without ever hitting the controller action that is supposed to populate and the model to the partial.
Your page Page.cshtml is calling the partial view action Module using:
#Html.Action("Module")
The action called Module is being executed. In that action, your query results in a path to your view, such as:
"~/Modules/Content/_ContentBlock.cshtml"
That action is returning the single result of that query using:
return PartialView(Query.Single());
What this is doing is passing the name of the view to the PartialView method to return which view is going to be used to display data from the action. In addition, no model data is included in this return.
That's where your problem is. When you return the path to the partial view, you are simply telling the MVC system what view to use to display the data from Module. It won't actually call another partial view. That's not how it works. So your model is null because, you didn't pass any data in your PartialView(...) call.
You have another action called ContentBlock. But that action is not being called because nothing is calling it.
Edit:
Another problem you have is that _ContentBlock.cshtml uses a model of IEnumerable<ContentBlock>, but you're only passing it a .Single() from your ContentBlock action.

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?

How to modify posted form data within controller action before sending to view?

I want to render the same view after a successful action (rather than use RedirectToAction), but I need to modify the model data that is rendered to that view. The following is a contrived example that demonstrates two methods that that do not work:
[AcceptVerbs("POST")]
public ActionResult EditProduct(int id, [Bind(Include="UnitPrice, ProductName")]Product product) {
NORTHWNDEntities entities = new NORTHWNDEntities();
if (ModelState.IsValid) {
var dbProduct = entities.ProductSet.First(p => p.ProductID == id);
dbProduct.ProductName = product.ProductName;
dbProduct.UnitPrice = product.UnitPrice;
entities.SaveChanges();
}
/* Neither of these work */
product.ProductName = "This has no effect";
ViewData["ProductName"] = "This has no effect either";
return View(product);
}
Does anyone know what the correct method is for accomplishing this?
After researching this further, I have an explanation why the following code has no effect in the Action:
product.ProductName = "This has no effect";
ViewData["ProductName"] = "This has no effect either";
My View uses HTML Helpers:
<% Html.EditorFor(x => x.ProductName);
HTML Helpers uses the following order precedence when attempting lookup of the key:
ViewData.ModelState dictionary entry
Model property (if a strongly typed view. This property is a shortcut to View.ViewData.Model)
ViewData dictionary entry
For HTTP Post Actions, ModelState is always populated, so modifying the Model (product.ProductName) or ViewData directly (ViewData["ProductName"]) has no effect.
If you do need to modify ModelState directly, the syntax to do so is:
ModelState.SetModelValue("ProductName", new ValueProviderResult("Your new value", "", CultureInfo.InvariantCulture));
Or, to clear the ModelState value:
ModelState.SetModelValue("ProductName", null);
You can create an extension method to simplify the syntax:
public static class ModelStateDictionaryExtensions {
public static void SetModelValue(this ModelStateDictionary modelState, string key, object rawValue) {
modelState.SetModelValue(key, new ValueProviderResult(rawValue, String.Empty, CultureInfo.InvariantCulture));
}
}
Then you can simply write:
ModelState.SetModelValue("ProductName", "Your new value");
For more details, see Consumption of Data in MVC2 Views.
The values are stored in ModelState.
This should do what you want:
ModelState.SetModelValue("ProductName", "The new value");
I wouldn't suggest doing that though... the correct method would be to follow the PRG (Post/Redirect/Get) pattern.
HTHs,
Charles
EDIT: Updated to reflect the better was of setting the ModelState value as found by #Gary
This will trigger the model to re-evaluate under simple conditions:
ModelState.Clear();
model.Property = "new value";
TryValidateModel(model);
Perform ModelState.Clear() before you change the model.
...
ModelState.Clear()
dbProduct.ProductName = product.ProductName;
dbProduct.UnitPrice = product.UnitPrice;
...

How do you persist querystring values in 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.

Resources