Html.HiddenFor sporadically not getting set w/ model information - asp.net-mvc

We're having an issue with Html.HiddenFor in MVC3 occasionally not getting bound properly. We can't reproduce it at all, but we're seeing nullrefs come through in our logging and it's driving us absolutely nuts.
We have the following model and controller structure:
public class DummyController
{
[HttpGet]
public ActionResult ReturnAPage(int NumericID)
{
//NumericID should never be 0 or negative, but let's check to make sure
if (NumericID < 1)
{
return RedirectToAction("TracyJordanStabbingRobot");
}
return View("DummyView", new DummyViewModel(NumericID));
}
[HttpPost]
public ActionResult TakePageSubmission(DummyViewModel model)
{
//AnObject relies on having a non-zero ID
ComplexObject AnObject = new ComplexObject(model.NumericID);
AnObject.UseMe();
}
}
public class DummyViewModel
{
public DummyViewModel() {}
public DummyViewModel(int ID)
{
NumericID = ID;
}
public int NumericID { get; set; }
}
... and the following view structure:
DummyView.cshtml
#model DummyViewModel
<html>
<head></head>
<body>
<p>THIS IS A VIEW!</p>
<form id="DummyViewForm" action="/RouteTo/TakePageSubmission" method="post">
#Html.Partial("_PartialDummyView", Model)
<input type="submit" value="Submit This!" />
</form>
</body>
</html>
_PartialDummyView.cshtml
#model DummyViewModel
<p>Heard you like views...</p>
#Html.HiddenFor(model => model.NumericID)
Considering we're checking for less-than-zero values in the initial controller action, it stands to reason that #Html.HiddenFor(model => model.NumericID) should never have a less-than-zero value.
That being said, when we get to using AnObject in the TakePageSubmission action, we're getting null reference errors.
When we dug into logging the model.NumericID value, we're seeing it come through as zero, which shouldn't be possible considering the DummyView can only be accessed with a non-zero value.
We're a little stumped and since we can't reliably reproduce the issue, we have no idea what could possibly be causing it. Has anyone run into something like this before?
Edit: We are doing ModelState validation on the form post, but we're not checking to see if the NumericID coming through is 0. When we did check for that, the model came through as invalid, which just proves that the HiddenFor is getting set improperly. Furthermore, the route to the page actually includes the NumericID, so for example, we've seen this happen on:
http://our.site.com/RouteToReturnAPage/1736/
...where the parameter for the action is clearly set, the model is constructed correctly, but for some unknown reason the HiddenFor NumericID value is 0. It's really baffling.

Your default 0 value bindings are from MVC View'ing to the same page after post, thinking it is reloading the same view due to an error during the post. The correct binding will occur on a load/action call to a different Action call.
There is a hack workaround, to ModelState.Clear(); before you reload the View.
Also, not using the Helpers to create the hidden fields at all, something like:
<input type="hidden" value="#Model.NumericID" id="NumericID" name="NumericID" />
Reference:
http://blogs.msdn.com/b/simonince/archive/2010/05/05/asp-net-mvc-s-html-helpers-render-the-wrong-value.aspx

First you are missing default constructor in your model. Without it applicaiton throws exception when binding.
You can reproduce the error by editing the hidden field on client side. So user can change id to 0 or any other value. If you aren't running you application on distributed enviroment then use TempData to pass the id between actions. This way you will keep id safe from data tampering.
TempData["NumericID"] = NumericID;

Related

mvc5 Additional information: 'object' does not contain a definition for 'Action'

I get:
'object' does not contain a definition for 'Action'
excepiton in my "_ExternalLoginsListPartial" view but I don't understand why because in Login view I call:
#Html.Partial("_ExternalLoginsListPartial", new { Action = "ExternalLogin", ReturnUrl = ViewBag.ReturnUrl })
And when I look into the Model in debugger it definaltely contains "Action".
Can anyone help me understand that?
Actually my site was running but today I started to edit "ManageUserViewModel" so that I can store some user specific settings in it. After that I always get this exception although I already reverted my changes...
The code below makes my website run again:
//string action = Model.Action;
//string returnUrl = Model.ReturnUrl;
string action = "ExternalLogin";
string returnUrl = "/myTime/en/Manage";
using (Html.BeginForm(action, "Account", new { ReturnUrl = returnUrl }))
{
#Html.AntiForgeryToken()
<div id="socialLoginList">
<p>
#foreach (AuthenticationDescription p in loginProviders)
{
<button type="submit" class="btn btn-default" id="#p.AuthenticationType" name="provider" value="#p.AuthenticationType" title="Log in using your #p.Caption account">#p.AuthenticationType</button>
}
</p>
</div>
}
UPDATE:
I'm able to reproducte the problem. As mentioned above I tried to change "ManageUserViewModel" so that the user can set some settings. Since I only use Google login I removed the password stuff for the model. To reproduce the exception comment out everything in ManageUserViewModel (make it an empty class).
Then comment out everything in Manage:
//
// POST: /Account/Manage
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Manage(ManageUserViewModel model)
{
// If we got this far, something failed, redisplay form
return View(model);
}
and then comment in:
app.UseGoogleAuthentication();
in StartupAuth.cs
And you get the exception when you click the google login button.
You can use the default MVC5 template and just do the steps described above to reproduce this...
I'm not sure if it is the wrong place to let my user store his settings. However, the screenshot below is definately giving me wrong information...
UPDATE2:
You don't have to edit Manage function in AccountController. It is enough to make “ManageUserViewModel” empty.
Cheers,
Stefan
The fact that it exists in the debugger is meaningless. The debugger exposes the object and all it's properties without knowing or caring about it's type. The problem you're having in your view is that you don't have a model definition, and because of that, your "model" is an object. The Object type truly does not have a property or method named Action, so you get the error.
The best solution is to simply specify your model as the actual type you're working with. Then you get intellisense and all the other goodness that comes from being strongly-typed. The alternative, is to cast Model to dynamic, but that's really nasty.
I encountered this exact error on this exact line because I had enabled Twitter as an authorization source with bogus key and secret values.
When I commented that section out in StartupAuth.cs, the application worked as expected.

Why won't my controller just hand back the model unchanged?

I have had this problem before and it annoys the hell out of my but I just cannot for the life of me remember what the problem was.
I have a controller...
public ActionResult SubmitApplication(ApplicationModel model)
{
return View("Index", model);
}
And the view
#model ApplicationModel
#using (Html.BeginRouteForm(null, null, FormMethod.Post, new { action = "/apply/SubmitApplication" }))
#Html.TextBoxFor(model => model.Email)
<input type="submit" text="submit"/>
}
I stick a break on the action method and the email entered is being passed to the action method no problem. BUT.....No matter what happens the controller clears the email down at some point I end up with an empty email box when its delivered to the page. But I want to preserve it so the user doesn't HAVE to enter ALL of the information again. What am I missing here?
Thanks
EDIT:
Sorry guys I had a faulty watermarking tool that was masking the response, however there is a still a strange problem.....When I do....
[HttpPost]
public ActionResult Apply(ApplicationModel model)
{
return View("Index",new ApplicationModel(){FullName = "newValue"});
}
Instead of getting the "newValue" on the page, I still get the original posted value. Can anybody explain this? Is is to do with model state?
The view helpers check, if there is a matching item in the model state and use this value if it exists. This value is in most cases the value the user typed before posting. If you want to force the view helpers to use your passed model you should clear the model state.

umbraco mvc surface controller, can't return view from HttpPost Action

Overview of the problem:
I've created a Surface controller with an action that is called using #Html.Action(...).
The #Html.Action call is done within a Macro partial view and the macro is included within the content of a page using the rich text editor.
(I'm new to this so if i'm going about things the wrong way then please let me know.)
The Surface controller has a GET and a POST action but it's the get action called within the macro partial.
Get action renders fine, entering no data into the form will invalidate the model state (which is what i'm currently testing).
submitting the form (with no entered data) means i can step into my POST action, ModelState.IsValid is set to false and CurrentUmbracoPage() is returned.
All fine... No Exceptions encountered when debugging...
It's at this point that the error text "Error loading Partial View script" appears on the page.
All I'm trying to do is return the same page with the validation messages showing.
Details:
Umbraco v6.0.5
The Controller I'm currently working on is used to reset a user's password. I also have a login conroller that is getting around this issue by using RedirectToCurrentUmbracoPage().
to access the page that contains the macro i use the address http://{testhost}/Reset-Password
the error text returned reads: Error loading Partial View script (file: ~/Views/MacroPartials/ResetPassword.cshtml)
code is within a seperate solution and views and bin directories are copied accross.
nuget package UmbracoCMS.Scaffolding is used.
Controller code:
public class ResetPasswordSurfaceController : SurfaceController {
[ChildActionOnly]
[HttpGet]
public ActionResult Reset(string token, string email) {
// Validation Code Omited
var user = Membership.GetUser(username);
return PartialView("Reset", new ResetPasswordSurfaceModel { UserID = user.ProviderUserKey.AsInt() });
}
[HttpPost]
public ActionResult PostReset(ResetPasswordSurfaceModel model) {
if (ModelState.IsValid) {
//Password reset code omited
return RedirectToCurrentUmbracoPage();
}
//works but only partial view content is rendered
// return PartialView("Reset",model);
return CurrentUmbracoPage();
}
}
View - ~\Views\ResetPasswordSurface\Reset.cshtml:
#model UmbracoExt.Models.ResetPasswordSurfaceModel
#using (Html.BeginUmbracoForm("PostReset", "ResetPasswordSurface")) {
#Html.EditorForModel()
<input type="submit" value="Submit" />
}
Macro Partial View - ~\Views\MacroPartials\ResetPassword.cshtml:
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#Html.Action("Reset", "ResetPasswordSurface")
Any help is appreciated.
Edit:
Removing the [HttpGet] attribute from the Reset Action has revealed that after the PostReset action is called the Reset action is also called.
Renaming PostReset to Reset and re-adding the httpget attribute to the original Reset Action results in the post action being called twice.
the second time it is called causes the exception:
Can only use UmbracoPageResult in the context of an Http POST when using a SurfaceController form
I have reverted the changes so i'm back at Reset ([HttpGet]) being called after the PostReset action.
So the problem still stands. How can i get around this issue?
I need to return the result from the PostReset Action.
This is how I solved this problem:
I created extension method for model:
public static class ExtensionMethods
{
public static void MapModel<T>(this WebViewPage<T> page) where T : class
{
var models = page.ViewContext.TempData.Where(item => item.Value is T);
if (models.Any())
{
page.ViewData.Model = (T)models.First().Value;
page.ViewContext.TempData.Remove(models.First().Key);
}
}
}
Controller code:
[HttpPost]
public ActionResult Index(MyModel model)
{
TempData.Add("MyModel", model);
return RedirectToCurrentUmbracoPage();
}
Partial view code:
#using UmbracoTest.Extension
#using UmbracoTest.Models
#model MyModel
#{
this.MapModel<MyModel>();
}
#using (Html.BeginUmbracoForm("Index", "Home", FormMethod.Post))
{
<div>
#Html.TextBox("Text", Model.Text )
</div>
<input type="submit" name="submit" value="Submit" />
}
The Answers were given to me here
All credit goes to Shannon Deminick
The post action does not return anything for the response (that bit was new to me).
After the post when the Reset action is run the second time, since the modelstate is maintained, by passing a newly instantiated model, this model will inherit the model state of the model processed in the POST action (PostReset).
During the second time the Reset action was called, the validation logic meant it never gets to the point where it returns the partial view.
i temporarily bypassed the validation logic and sure enough the model validation messages were displayed.
I fixed this error by resolving a naming conflict:
Make sure that the GET and POST methods are named differently
Make sure the controller name doesn't conflict with any document types

HTML.CheckBox Behaviour

I am using a model where a check box is there . where i am posting the form i always get more than one boolean value, Code is as follow
//controller code
// GET: /Home/
// GET: /Home/Test
public ActionResult HomeTest()
{
HomeTest ht = new HomeTest();
return View(ht);
}
[AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
public ActionResult HomeTest(FormCollection collection, HomeTest model)
{
string str = collection["test"].ToString();
return View(model);
}
//View Html
<%= Html.CheckBox("test") %>
I am getting following value in str while debugging "true,false". Am i do anything wrong?
This is how Html.CheckBox was designed!
It always renders
<input name="test" type="checkbox" value="true" />
followed by a
<input name="test" type="hidden" value="false" />.
When you submit the form with the checkbox checked, you'll get test=true,false and when the checkbox is unchecked, you get test=false.
This is how it supports binding to a boolean value, because the ,false part is ignored. However, if you're binding to something else (such as string), that's when you'll see the "true,false" value.
If you want different behavior, you'll have to roll-your-own Html.CheckBoxAlt method. Here's a similar question, along with some code, that I wrote a while back: Passing checkbox selection through to an action
MVC does things this way so it can tell the difference between "There is no checkbox called 'test'" and "The checkbox called 'test' is unchecked." Since HTML doesn't provide any built-in way to tell the difference, MVC makes it always send a "false" value, which will get overridden by a "true" value if you check the box.
The easiest solution is to make better use of MVC's approach. Rather than using the FormCollection, just use a parameter that the model binder can bind to:
public ActionResult HomeTest(bool test, HomeTest model)

Html.TextBoxFor does not show updated value in POST action

In my view I have
<%:Html.LabelFor(model => model.IPAddress)%>
<div class="editor-field">
<%:Html.TextBoxFor(model => model.IPAddress)%>
<%:Html.ValidationMessageFor(model => model.IPAddress)%>
</div>
In my controller(post method), I have this
[HttpPost]
public ActionResult Manipulation(MyModel model){
//I change modele here
if(something)
model.IPAddress="100.100.100.100";
return View(model);
}
So, my question is:
When I change model, the TextBoxFor does not change his value.
TextBoxFor get his value when I get from get method to the post, and later I cannot change value of TextBoxFor.
I debug, and my model have new value, but TextBoxFor does not show new value.
Can you help me?
Try:
ModelState.Clear();
return View(model);
If not result! return a JSON Result and then update by javascript
Mr Grok had a similar problem this site. He had already found the ModelState.Clear() solution, but was wanting an explanation of why it worked. The highest ranked answer on the linked site proposed that the behavior of the html helper is a bug, for which ModelState.Clear() is a workaround. However, bradwils at this site says that the behavior is by design, and gives the following explanation:
The reason we use the posted value for editors rather than the model value is that the model may not be able to contain the value that the user typed. Imagine in your "int" editor the user had typed "dog". You want to display an error message which says "dog is not valid", and leave "dog" in the editor field. However, your model is an int: there's no way it can store "dog". So we keep the old value.
If you don't want the old values in the editor, clear out the Model State. That's where the old value is stored and pulled from the HTML helpers.
Despite the fact that it’s by design, this is very unexpected behavior for the developer, and it is unfortunate that interaction with the ModelState is required for a common programming need.
Furthermore, clearing the entire ModelState might cause unexpected issues in other areas (I think with respect to validation on unrelated model fields). So many thanks to Peter Gluck (responding in a comment within Mr Grok’s page) for proposing the more limited ModelState.Remove(“key”), and to Toby J for developing a more convenient method that works when you’re not sure what the key should be if the model property is nested. I also like Toby’s method because it doesn’t depend on a string as input.
That method, with minor changes, follows:
/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model. Call this when changing
/// Model values on the server after a postback, to prevent ModelState entries from taking precedence.
/// </summary>
/// <param name="model">The viewmodel that was passed in from a view, and which will be returned to a view</param>
/// <param name="propertyFetcher">A lambda expression that selects a property from the viewmodel in which to clear the ModelState information</param>
/// <remarks>
/// Code from Tobi J at https://stackoverflow.com/questions/1775170/asp-net-mvc-modelstate-clear
/// Also see comments by Peter Gluck, Metro Smurf and Proviste
/// Finally, see Bradwils http://forums.asp.net/p/1527149/3687407.aspx.
/// </remarks>
public static void RemoveStateFor<TModel, TProperty>(
this ModelStateDictionary modelState,
TModel model,
Expression<Func<TModel, TProperty>> propertyFetcher
) {
var key = ExpressionHelper.GetExpressionText(propertyFetcher);
modelState.Remove(key);
}
Instead of using model binding, id suggest using a tryupdate call.
[HttpPost]
public ActionResult Manipulation(FormCollection formCollection)
{
MyModel model = new MyModel();
if(TryUpdate(Model))
{
enter code here
}
if(somthing)
model.IPAddress="100.100.100.100";
return View(model);
}
Check out my answer to another post for the general structure i use. Its never failed me before and i believe it covers all bases when updating models from user input.
asp.net mvc controller post best practices
Here's another work around that I found.
Instead of
#Html.TextBoxFor(m=>m.PropertyName)
do this
#{
var myModel = Model;
}
#Html.TextBoxFor(m=>myModel.PropertyName)
This could be useful if you don't want to override the default behavior for every input.

Resources