Can someone please help me understand this. I have the following code:
The controller
public ActionResult Stuff(PersonModel model)
{
model.Address = "Some Address";
return PartialView("_Registration", model);
}
The View
#Ajax.BeginForm("Stuff", new AjaxOptions(){ HttpMethod="POST", UpdateTargetId="the_form", InsertionMode=InsertionMode.Replace, OnSuccess="Stuff" })
{
<div id="the_form">
#{Html.RenderPartial("_Registration", new TypeScriptTest.Models.PersonModel());}
</div>
<input type="submit" value="Get Addreess" />
}
The Partial View
#model TypeScriptTest.Models.PersonModel
<table>
<tr>
<td>
Name:
</td>
<td>
#Html.TextBoxFor(c => c.Name)
</td>
</tr>
<tr>
<td>
Address:
</td>
<td>
#Html.TextBoxFor(c => c.Address)
</td>
</tr>
</table>
The data is posted with whatever I type in the two fields. But then I replace the Address property value with "Some Address" and return a PartialView I would expect to get a new view that replaces the old. This probably happens, but the view that replaces the old does not include the new "Some Address" value. It just looks exactly like the view I posted.
I have a workaround, but I want to understand what is happening here. My workaround is as follows:
public ActionResult Stuff(PersonModel model)
{
model.Address = "Some Address";
var v = PartialView("_Registration");
var view = v.ViewEngineCollection.OfType<System.Web.Mvc.RazorViewEngine>().Single().FindPartialView(new ControllerContext(HttpContext, RouteData, this), "_Registration", false);
var result = new PartialViewResult() { View = view.View };
result.ViewData.Model = model;
return result;
}
Here I can bypass cache using false as the last parameter in FindPartialView. Setting this parameter to true results in the same problem as the one in discussion.
Why is this happening and what is the correct way to get around this problem? My workaround works fine, but I would like to understand the problem.
In case you are still interested, this is because the values submitted from the form are stored in the ModelState and when rendering the HtmlHelpers, any value in the ModelState takes precedence over your current model object values.
If you want to change values submitted by the user then you could either clear the modelState or clear the entry for the single property that you want to change. (Take care when clearing the model state as that will also clear the errors for any submitted value, at least you may want to make sure the ModelState is valid before manually modifying it)
I guess that with your work around you will end up having a clean ModelState without the submitted values.
You might also find useful the answers to this similar question: Asp.net MVC ModelState.Clear
Related
I try to pass some hidden data to my controller by using the hiddenFor, I know the value I want gets to the view, but after submiting the form the value stays null when it arrives in the controller. The data in EditorFor is passed correctly to the controller.
// View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
// Some working editorFor fields. Data from these gets successfully received
// The name is correctly displayed in the paragraph
<p>#Model.name</p>
// This data is not received in the controller
#Html.HiddenFor(x => x.name)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
// Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product, HttpPostedFileBase image)
{
product.name = "a name";
return View(product);
}
I also tried using a normal named hidden, but this also didn't return a value.
Someone an idea what I missed?
You can pass the hidden fields automatically, if you have a form, using for example the razor helper
#using (Html.BeginForm("CreateTable", "Home", FormMethod.Post, null){ #HiddenFor(i => i.PropertyName) }
and the hidden fields must be inside of form, otherwise you will "lost" them.
Update following your updated question: Try remove the HiddenField and change <p>#Model.name</p>
to
#Html.LabelFor(i => i.Name)
I did focus on the incorrect thing, the problem was that I changed the model in the controller after the postback. But this only changes the model en does not changes the ModelState, which the form data uses.
//This is updated after model changes.
<p>#Model.name</p>
//For this you need to update the ModelState
#Html.HiddenFor(x => x.name)
In the controller you need to use ModelState.Remove(property name). (Or clear the complete ModelState)
//After removal of the property the ModelState will update to the new model value.
product.name = "a name";
ModelState.Remove("name");
return View(product);
In this article it's explained, https://weblog.west-wind.com/posts/2012/Apr/20/ASPNET-MVC-Postbacks-and-HtmlHelper-Controls-ignoring-Model-Changes.
I have 3 tables:
RateProfile
RateProfileID
ProfileName
Rate
RateID
RateProfileID
PanelID
Other stuff to update
Panel
PanelID
PanelName
I have models for each of these. I have an edit page using the RateProfile model. I display the information for RateProfile and also all of the Rates associated with it. This works fine and I can update it fine. However, I also added a dropdown so that I can filter Rates by PanelID. I need it to post back on change so that it can display the filtered rates.
I'm using
#Html.DropDownList("PanelID", (SelectList)ViewData["PanelDropDown"], new { onchange = "$('#RateForm').submit()" })
for my dropdownlist. Whenever it posts back to my HttpPost Edit method though, it seems to be missing all information about the Rates navigation property. It's weird because I thought it would do exactly what the input/submit button that I have in the form does (which actually passes the entire model back to my HttpPost Edit action and does what I want it to do). The panelID is properly being passed to my HttpPost Edit method and on to the next view, but when I try to query the Model.Rates navigation property is null (only when the post comes from the dropdown. Everything works fine when the post comes from my submit input).
Get Edit:
public ActionResult Edit(int id, int panelID = 1)
{
RateProfile rateprofile = db.RateProfiles.Single(r => r.RateProfileID == id);
var panels = db.Panels;
ViewData["PanelDropDown"] = new SelectList(panels, "PanelID", "PanelName", panelID);
ViewBag.PanelID = panelID;
return View(rateprofile);
}
HttpPost Edit:
[HttpPost]
public ActionResult Edit(RateProfile rateprofile, int panelID)
{
var panels = db.Panels;
ViewData["PanelDropDown"] = new SelectList(panels, "PanelID", "PanelName", panelID);
ViewBag.PanelID = panelID;
if (ModelState.IsValid)
{
db.Entry(rateprofile).State = EntityState.Modified;
foreach (Rate dimerate in rateprofile.Rates)
{
db.Entry(dimerate).State = EntityState.Modified;
}
db.SaveChanges();
return View(rateprofile);
}
return View(rateprofile);
}
View:
#model PDR.Models.RateProfile
#using (Html.BeginForm(null,null,FormMethod.Post, new {id="RateForm"}))
{
<div>
#Html.Label("Panel")
#Html.DropDownList("PanelID", (SelectList)ViewData["PanelDropDown"], new { onchange = "$('#RateForm').submit()" })
</div>
#{var rates= Model.Rates.Where(a => a.PanelID == ViewBag.PanelID).OrderBy(a => a.minCount).ToList();}
#for (int i = 0; i < rates.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(modelItem => rates[i].RateProfileID)
#Html.HiddenFor(modelItem => rates[i].RateID)
#Html.HiddenFor(modelItem => rates[i].PanelID)
#Html.EditorFor(modelItem => rates[i].minCount)
#Html.ValidationMessageFor(model => rates[i].minCount)
</td>
<td>
#Html.EditorFor(modelItem => rates[i].maxCount)
#Html.ValidationMessageFor(model => rates[i].maxCount)
</td>
<td>
#Html.EditorFor(modelItem => rates[i].Amount)
#Html.ValidationMessageFor(model => rates[i].Amount)
</td>
</tr>
}
<input type="submit" value="Save" />
}
To summarize my problem, the below query in my view only works when the post comes from the submit button and not when it comes from my dropdownlist.
#{var rates= Model.Rates.Where(a => a.PanelID == ViewBag.PanelID).OrderBy(a => a.minCount).ToList();}
How does it look in the rendered page? You may have two elements being rendered with the same name/id:
#Html.DropDownList("PanelID", (SelectList)ViewData["PanelDropDown"], new { onchange = "$('#RateForm').submit()"
and
#Html.HiddenFor(modelItem => rates[i].PanelID)
I suggest you use Firefox/Firebug to examine the actual request that it is being made. I can't imagine that the form will post differently if triggered via the submit vs. the button, but I suppose it's possible.
On a larger note, I would make a couple of comments about your design.
I would use ViewBag properties for your selections rather than key-based access to ViewData. This will be much more readable in both your controller and your view.
Consider having a separate action that populates the information filtered by the dropdown list. Render that action in your view based on the PanelID and call that action via AJAX to get the new HTML rather than doing a full form post.
Avoid applying your handlers in your markup. Rather, add an optional Scripts section to your master page and, when needed, add scripts to your page that apply your behaviors through that section. This allows you to control via the master where view-specific scripts are placed in the document and keeps your behaviors separate from your document structure, a best practice for readability.
I found the problem. It looks like because I was only looping through a subset of my navigation property (Rates filtered by panelID), the model that was returned from the view only had that subset of navigation properties available to it. After saving the changes, I just redefined my model (called the record from the database again) and it looks good now.
Ie. There were supposed to be 140 records in my navigation property, filtering by panelID == 1 narrowed it down to 28 records. For some reason the entity framework decided that it didn't feel like maintaining the relationship to the other 112 records, so when I changed the dropdown to filter by panelID == 2, the only records available all had panelID == 1 and returned null.
I have a textarea that represents a description field. The descriptions have commas so when trying to split the field's descriptions the data is not parsed correctly. How can I get each row's description correctly.
var DescList = FormValues["Item.Description"].Split(',').Select(item => item).ToList<string>();
//will not work for obvious reasons. Comma delimited FormCollection has commas to identify separate row data.
It seems like Microsoft designed the FormsCollection without the textarea control in mind. A text area with commas will not work when trying to access each value. What is interesting is that the _entriestables property has it in the perfect format but they chose to make it a private property. Very frustrating.
`
Here is the important part of my viewmodel.
public class TenantViewModel
{
public Tenant Tenant { get; set; }
public Site Site { get; set; }
}
My view is populated like this:
if (Model != null && Model.Tenant != null && Model.Tenant.Site != null && Model.Tenant.Site.Count() > 0)
{<div class="detailsbox_view">
<table id="tblTenantSites">
<tr>
<th>#Html.LabelFor(item => item.Site.Title)</th>
<th>#Html.LabelFor(item => item.Site.Description)</th>
</tr>
#foreach (var Item in Model.Tenant.Sites)
{
<tr>
#Html.HiddenFor(modelItem => Item.SiteId)
<td>
#Html.EditorFor(modelItem => Item.Title)
</td>
<td>
#Html.TextAreaFor(modelItem => Item.Description, new {#width="400" })
</td>
</tr> }
</table>
As you see this site table is a child of Tenant object. This child record does not get automatically updated using this method but the Tenant data does automatically get updated. This is the reason I tried the FormColelction instead.
Is there something I am missing to make this work?
try with this useful function
ValueProviderResult Match=FormCollection.GetValue("ValueProvider");
When you have multiple fields with the same name attribute, they'll come back into your FormCollection as an array. So upon posting a view like this:
<form action="/Home/MyAction">
<textarea id="row_one_description" name="description">
First row's description
</textarea>
<textarea id="row_two_description" name="description">
Second row's description
</textarea>
<input type="submit" value="Submit" />
</form>
you could do something like this in your action
[HttpPost]
public ActionResult MyAction(FormCollection collection)
{
var descriptionArray = collection["description"];
string firstRowDescription = descriptionArray[0];
string secondRowDescription = descriptionArray[1];
}
I must note that this is not the recommended way of dealing with posted data. You should instead be building your view using data from a view model and using strongly typed html helpers to render your controls. That way when you post, your action can take the ViewModel as a parameter. Its properties will be automatically bound and you will have a nice object to play with.
[HttpPost]
public ActionResult MyAction(MyViewModel viewModel)
{
foreach (var row in viewModel.Rows)
{
string description = row.Description;
}
}
EDIT
I'm still assuming a lot about your ViewModel but perhaps try this:
<table id="tblTenantSites">
<tr>
<th>#Html.LabelFor(model => model.Site.Title)</th>
<th>#Html.LabelFor(model => model.Site.Description)</th>
</tr>
#for (var i = i < Model.Tenants.Sites.Count(); i++) {
<tr>
#Html.HiddenFor(model => model.Tenants.Sites[i].SiteId)
<td>
#Html.EditorFor(model => model.Tenants.Sites[i].Title)
</td>
<td>
#Html.TextAreaFor(model => model.Tenants.Sites[i].Description, new { #width="400" } )
</td>
</tr>
}
</table>
You could also try ,
string Match=FormCollection.GetValue("ValueProvider").AttemptedValue;
We have a form and depending upon the circumstances we want to switch between an add operation and an update operation from the same form. Below is a cut down version of our form.
Effectively the "Order number" textbox is disabled and can never be edited in this form. Now, the scenario is a bit like this:
The first time the user lands on this form, the "Order number" text box is blank.
The user enters a customer name and submits the form.
At this point in the controller action, we get the max value of order number in the database and increment the order number by 1 . We then add that new record in the database.
If that operation is successful, we update the current form and the "Order Number" textbox should now be updated with the order number created in the previous step AND also what should happen is that we are now in Edit mode.
Say the user then updates the "Customer name" and submits the form, the record in the database should be updated in this instance.
Now for some code:
The View:
<%: Html.TextBox("OrderNumber", Model.OrderNumber == 0 ? "" : Model.OrderNumber.ToString(), new { #disabled = "true" })%>
The controller:
public ActionResult Index()
{
var customerOrderModel = new CustomerOrderModel();
return View(customerOrderModel);
}
public ActionResult Add(CustomerOrderModel customerOrderModel, FormCollection values)
{
// We write the logic for either the add or update.
return this.View("Index", customerOrderModel);
}
I have removed the code from the Add action because from putting breakpoints we know that the "// We write the logic for either the add or update." is not the problem.
Now where we are having trouble is this. We can add the new entry in the table following which the "Order Number" field gets updated and is displayed correctly. However, after we change the customer name and try to update, the customerOrderModel passed into the "Add" action shows that the order number being passed is 0(which is our default in the system and which is used to determine if we are performing an add or update operation).
So the question is why is our textbox getting updated, which would seem to indicate that our model is getting updated, but then when we try to submit, the correct model doesn't get passed in? Moreover, why is it that the Index action doesn't get hit after the "Add" action is completed? What do we have to do to get things to work the way we want them to?
Model
namespace Demo.Models
{
public class Order
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
}
public class OrderDb:DbContext
{
public DbSet<Order> Orders { get; set; }
}
}
View
#model Demo.Models.Order
<form action="/" method="post">
<table>
<tr>
<td>#Html.LabelFor(m=>m.OrderId)</td>
<td>
#Html.EditorFor(m => m.OrderId)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m=>m.CustomerName)
</td>
<td>
#Html.EditorFor(m=>m.CustomerName)
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" name="submit" value="submit" />
</td>
</tr>
</table>
</form>
Action
public ActionResult Index(Order o)
{
if (o.CustomerName != null)
{
using (OrderDb db = new OrderDb())
{
db.Entry(o).State = o.OrderId == 0 ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
ModelState.Clear();
}
}
return View(o);
}
This is because HtmlHelpers look to ModelState for values first and then uses the values you explicitly use.
So when you add the entity you get ["Id"]=0 inside your model state.
So solve you have to clear your ModelState with .Clear() after a successful add.
I have a simple for with at text field where the user enters the address of a RSS feed. I wont act if the field is blank, this is my markup:
<%=Html.ValidationSummary() %>
<table>
<tr>
<td>
Feed Url:
</td>
<td>
<%=Html.TextBox("url", null, new {#style="width:300px"}) %>
</td>
</tr></table>
My controller is also very simple:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddFeed(FormCollection collection)
{
string url = collection.Get("url");
string roles = collection.Get("Roles");
if (string.IsNullOrEmpty(url))
{
ModelState.AddModelError("url", "Please provide a propre feed url");
}
if (string.IsNullOrEmpty(roles))
{
ModelState.AddModelError("Roles", "Please select a valid role");
}
if (ModelState.IsValid)
{
Session["url"] = url;
Session["Roles"] = roles;
return RedirectToAction("ValidateFeed");
}
else
{
return View();
}
}
When it fails it reloads the view and makes an exception in the line where it renders my textbox, saying that there has been a null pointer exception. This really botheres me, it should be so simple... but still im struggling
/H4mm3r
Edit Please disregard the Roles element its a drop down i have, but removed it from markup for simplicity
You probably can't use null when using the HTML helper. Try this in your form instead:
<%= Html.TextBox("url", String.Empty, new {#style="width:300px"}) %>
I haven't tested that, but that's the first thing I would try, given that error message.
I think you cant use the ModelSate like that if you didn't do something like:
UpdateModel(collection);
or
TryUpdateModel(collection);
or by Parameter Binding
public ActionResult AddFeed(YourModelType collection)
Because the Modelstate is not associated with any Model.
As the MSDN reference says:
ModelState Class
Encapsulates the state of model
binding to a property of an
action-method argument, or to the
argument itself.