MVC Hidden field via HTML Helper in Form Post issue - c#

I have an issue when using a hidden field in an MVC Form Post. When the hidden field is generated via a HTML Helper it won't preserve it's value during the postback. But when using a HTML tag, it works.
Unfortunately this one has taken me a whole day to work out this work around.
Here is what I'm doing... (excuse any spelling, re-typed code for SO):
View Model
public class SomeViewModel
{
public int MyProperty1 { get; set; }
public int MyProperty2 { get; set; }
public int MyProperty3 { get; set; }
}
Post method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MyActionMethod(SomeViewModel someViewModel, string command)
{
...
...
// someViewModel.MyProperty1
...
...
}
View
#using (Html.BeginForm("MyActionMethod", "SomeController", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.MyProperty1)
<div class="col-md-2">
<input type="hidden" value=#Model.MyProperty1 name="MyProperty1" />
<input type="submit" name="command" value="Button1" class="btn btn-primary" />
<input type="submit" name="command" value="Button2" class="btn btn-success" />
</div>
<div class="col-md-1"></div>
<div class="col-md-1">
<input type="submit" name="command" value="Button3" class="btn btn-danger" />
</div>
<div class="col-md-8"></div>
}
In the above View code, the HTML helper hidden field (#Html.HiddenFor(m => m.MyProperty1)) does NOT work. But the HTML tag hidden field (input type="hidden" value=#Model.MyProperty1 name="MyProperty1") DOES work. I only have one or the other enabled. Both shown here are for display purposes.
I'd prefer to use the HTML Helper syntax, but can live with the HTML tag.
Things to note:
The View is using multiple submit buttons.
The View is using a partial view. Currently no content in the partial view and nothing is being done with it.
I can't see how these would affect the issue. Thought I'd mention it, in case.
Question: Can anyone explain why the HTML Helper isn't working?
***** UPDATE *****
Thanks to Stephen Muecke for pointing out what needed to be included in my question. Moreover an extra thank you for guessing what I was actually doing but I couldn't articulate it.
I'm updating the View Model property in the ActionMethod(), and when the same View is re-rendered, the View Model property doesn't reflect the new value.
Rather it is keeping it's initial value, and not preserving the new value.

Although not obvious and I found it difficult to find many articles on this subject to clarify it for me in the past the default behaviour in ASP.NET MVC is the following:
If you are using HTML Helpers and you are rendering the same View in response to a POST it is assumed that you are responding to failed form validation.
Therefore the values before being set in the POST will always be rendered back in the view from ModelState.
You have a few options:
ModelState.Clear(); in your post. Not recommended as the framework has not been designed this way.
Use the Post-Redirect-Get pattern and only display validation failure, as the framework was designed (as #StephenMuecke mentions).
If you are not bothered about validation do not use HtmlHelpers
Use Request.Form values instead and remove the SomeViewModel someViewModel parameter. Wouldn't recommend this as you lose all benefits of model binding.
Use ModelState.Remove for the specific field, again not recommended.
The best article I found on this was article from Simon Ince in 2010:
ASP.NET MVC’s Html Helpers Render the Wrong Value!
Another one by Rick Strahl:
ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Related

How to properly display checkbox form controls in ASP.NET Core Views?

I have a model with a boolean property which will be shown as a checkbox in my view:
public class SomeModel
{
public bool SomeProperty { get; set; }
}
And I am creating the view like this:
<div class="form-check">
<input asp-for="TestProperty" value="true" class="form-check-input" />
<label class="form-check-label" asp-for="TestProperty"></label>
</div>
Below is the HTML output in the browser:
<div class="form-check">
<input value="true" class="form-check-input" type="checkbox" data-val="true" data-val-required="The TestProperty field is required." id="TestProperty" name="TestProperty">
<label class="form-check-label" for="TestProperty">TestProperty</label>
</div>
And I can see below hidden element is appended at the end of the form:
<input name="TestProperty" type="hidden" value="false">
When I submit the form this is the request body:
TestProperty: true
TestProperty: false
The value in the first line is the value in the check box element and the second line is the value of the hidden element that overrides the first line value when received in the controller. I noticed that when I click on the checkbox the hidden input's value does not change.
The first solution that comes to mind is to update the hidden element via JavaScript when the input's value is changed, which this is unnecessary. Is there any way that I can get rid of these hidden elements? or I am not displaying the form control properly. I am not sure.
UPDATE
The hidden input elements are added automatically by the razor view itself. Though I am using controllers/views and not razor views, I tested Enrico's solution and the result is the same. It still appends a hidden input element for the checkbox:
You don't need to manually add an hidden input field, or to interact with it via client side Javascript code. The way to go with ASP.NET core and Razor is using the tag helpers and let the actual HTML generation to be done by Razor itself.
This is an example using Razor Pages and a single boolen value in the page input model. I have used ASP.NET core 3.1 and the usual ASP.NET core web application template in Visual Studio 2019.
This is the page model:
public class TestFormModel : PageModel
{
private readonly ILogger _logger;
public TestFormModel(ILogger<TestFormModel> logger)
{
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
public IActionResult OnGet()
{
this.Input = new TestFormModel.InputModel
{
ShowUsername = false
};
return this.Page();
}
public IActionResult OnPost()
{
if (!this.ModelState.IsValid)
{
return this.Page();
}
_logger.LogInformation("The posted value is: {Value}", this.Input.ShowUsername);
return this.RedirectToPage("Index");
}
public class InputModel
{
[Display(Name = "Show User Name")]
public bool ShowUsername { get; set; }
}
}
This is the associated Razor view:
#page
#model StackoverflowTest.Pages.TestFormModel
#{
ViewData["Title"] = "How to build a form with a checkbox";
}
<h1>#ViewData["Title"]</h1>
<form asp-page="TestForm">
<div class="form-check">
<input class="form-check-input" asp-for="Input.ShowUsername">
<label class="form-check-label" asp-for="Input.ShowUsername"></label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
That's it. You do not need to manually add an hidden input in your form. You just have to use the proper tag helps and ASP.NET core will take care of generating the right form and form inputs for you.
UPDATE 2 August 2021
See this github repository for a working sample.
As a sidenote, please consider that the hidden input field is automatically added by ASP.NET core and it is required in order to post to the server the right value for Input.ShowUsername when the checkbox in the form is unchecked.
Basically in the generated HTML page source there are two form inputs for Input.ShowUsername. One of them is an HTML input having type="checkbox", the other one is an HTML input having type="hidden". Both of them have the same value for the name attribute (name="Input.ShowUsername"), while the value attribute is true for the checkbox input and false for the hidden input.
This is the HTML page source for the form (exactly the way it is generated by ASP.NET core 3.1):
<form action="/TestForm" method="post">
<div class="form-check">
<input class="form-check-input" type="checkbox" data-val="true" data-val-required="The Show User Name field is required." id="Input_ShowUsername" name="Input.ShowUsername" value="true">
<label class="form-check-label" for="Input_ShowUsername">Show User Name</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8AHG4SVs1BBFruMHNiza3QNKvHNf96d_oiz5J6n3eYuncnlFKqvL4x3G6jbAOmNfJjrwk_crqFN99kjZiBVoboFGd0VQIBlrvmp9fL6p8CMbiZ4f6Cf4rm3OpGZEZ_04UQQ5iuUi3nVjfIgfb5-vctw" />
<input name="Input.ShowUsername" type="hidden" value="false">
</form>
When the checkbox in the form is checked, the value true is posted to the server for the name Input.ShowUsername, when the checkbox is unchecked the value false is posted to the server for the name Input.ShowUsername: this works because the value of unchecked checkboxes is not posted to the server.
Adding an input with type="hidden" is a common pattern to post a default value for the checkbox when it is unchecked, see here for an explanation about the way HTML checkboxes work.
Again, all of this is automatically generated for you by ASP.NET core: you simply have to define the page input model and to use the proper Razor tag helpers.

Input data is not returning by passing through object

I am passing the input data from the .cshtml page to the action method.
Here is my .cshtml page:
#model passingdata
<form asp-controller="Home" method="post" asp-action="About" >
<button class="btn-danger" type="submit" value="Submit"></button>
<div class="form-group">
<label>
<input asp-for="date" placeholder="Date" class="col-md-8" />
</label>
<label>
Select from these Four Classes
<input type="radio" name="class" asp-for="classselect1" id="classselect1" value="classselect1" class="col-md-4" /> <p> #Model.classname1</p>
<input type="radio" name="class" asp-for="classselect2" id="classselect2" value="classselect2" class="col-md-4" /> <p> #Model.classname2</p>
<input type="radio" name="class" asp-for="classselect3" id="classselect3" value="classselect3" class="col-md-4" /> <p> #Model.classname3</p>
<input type="radio" name="class" asp-for="classselect4" id="classselect4" value="classselect4" class="col-md-4" /> <p> #Model.classname4</p>
</label>
</div>
</form>
And here is my controller code which is invoked when i Click on the button.
[HttpPost]
public IActionResult About(passingdata p)
{
ViewData["Message"] = "Your application description page.";
Teacher.classselect1 = p.classselect1;
Teacher.classselect2 = p.classselect2;
Teacher.classselect3 = p.classselect3;
Teacher.classselect4 = p.classselect4;
Teacher.date = p.date;
return View();
}
The input data like date and bool value from the radiobutton is not passing through the object of the class which contain these variables.
Please help me in this.
If i remember correctly, the Name attribute of each radiobutton is how .net MVC will map the values to your model.
In this case, all of your names are "class", which essentially means you have 1 form field named class with 4 options.
I would recommend using the html helper classes, because they will automatically create the proper html for you. The answer to this post should help: When using .net MVC RadioButtonFor(), how do you group so only one selection can be made?
If you dont want to use the helper just remember that when you submit a form, the data that is posted is based on the name of each form field. .Net does some magic in the background to serialize your Model for you, but essentially you are just submitting data in the format "?prop1=val1&prop2=val2".
Update
I figure maybe I should clarify a little better why what you are doing is not working how you expect.
When you post or put data via a form, it passes the input fields (text box, radio button, checkbox, etc...) as either querystring params or are part of the body. Radio buttons work a little differently than other input type. For a radio button, there are multiple input elements, but only one of them is valid. That is handled by using the name attribute. In your case, all of the names are "class", which means that the only thing being passed to the server is a single "?class={val}" (val is the value of which ever radio button is selected).
If your passingdata model had a property called "class", it would be populated. If your goal is to populated all 4 of the classselect properties with different values, you would need the name of each radio button to be different. But if there was only one radio button with each name, then each property could only have 1 value. You would need multiple RadioButtons with the same name to have multiple values (only one of which is selectable for each property).
Hopefully that clarifies what is wrong and gets you in the right direction.

Can't understand How to decide when to use Hidden() and when to use HiddenFor()

I am following a book and here is the code:
#using (Html.BeginForm("RemoveFromCart", "Cart"))
{
#Html.Hidden("ProductId", line.Product.ProductID)
#Html.HiddenFor(x => x.ReturnUrl)
<input class="btn btn-sm btn-warning" type="submit" value="Remove" />
}
And here is also his explanation for why he has used Hidden instead of HiddenFor
but still I can't understand the wiring behind it that he is talking about. Can you elaborate this a little more ?
public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl)
You need to remember that these helpers are just ways of generating HTML markup.
Example of the generated markup:
#Html.Hidden("ProductId", line.Product.ProductID)
Generates:
<input type="hidden" name="ProductId" value="5" />
#Html.HiddenFor(x => x.Product.ProductID)
Generates:
<input type="hidden" name="Product_ProductId" value="5" />
Your controller defines a parameter named productId. In order for model binding to work, the name value of the hidden input must match the argument name.
Product_ProductId will not match the defined argument productIdfor the RemoveFromCart Controller Action.
It is worth noting that model binding is case insensitive. So your hidden input value of ProductId will still bind to the RemoveFromCart parameter of productId.
in your RemoveFromCart(Cart cart, int productId, string returnURL) you have an explicit variable, specifically int productId. It would be expecting a field called "ProductId in the HTML in order to fill the value in. If you use the Html.HiddenFor helper, it generates the field with the full name of the variable, resulting in an HTML field called "Product_ProductID". The model binder would not be able to match the HTML with this field name to the correct parameter in your function call.
Use HiddenFor if your ViewModel property just has to be passed through view without any complicated processing.
The "Hidden" method is more custom way of data binding and is not necessarily related to your ViewModel but to a Form.
The "HiddenFor" method is just automated way of direct binding of ViewModel properties.

How to execute a method properly in Controller in MVC4

I have a simple MVC application that retrieves DB Server, database, username and password from the user to store in an XML file. I want to add a "Test Connection" button to the screen and have it execute a method on the controller called TestConnection. The problem is the TestConnection method resets all the information on the screen when clicked since the View is being returned with no model. (because a GET operation is occurring). Here is my code:
From the Controller (named FrameworkConfigurationController.cs)
public ActionResult TestConnection()
{
return View("Index");
}
[HttpPost]
public ActionResult TestConnection(FrameworkConfigurationViewModel viewModel)
{
// TODO: Test will occur here
viewModel.DbConnectionMessage = string.IsNullOrEmpty(viewModel.DatabaseName) ? "Connection unsuccessful" : "Connection successful";
return View("Index", viewModel);
}
From my View (FrameworkConfiguration/Index):
#model Framework.ViewModels.FrameworkConfigurationViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>FrameworkConfigurationViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.ServerName)
</div>
#* Edited for brevity *#
<button type="submit" onclick="location.href='#Url.Action("TestConnection")'">
Test Connection</button>
#Html.ValueFor(model => model.DbConnectionMessage)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to Dashboard", "Index", "Home")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Admittedly, I am new to MVC programming. I'm coming from a Silverlight/MVVM background so the concept is not foreign to me...the disconnected nature of web programming is. I've followed some tutorials out there, but none of them seem to cover this type of thing - every example is contrived. I know how to do this with webforms and code-behind, but I would like to accomplish this with MVC.
Is there some way to "force" the POST operation instead of GET? I was under the impression that
<button type="submit">
accomplished that. Perhaps the implementation of the onclick I have written isn't correct. I am sure this is HTML 101 stuff, but I can't seem to find a simple answer to this.
Thanks for any help you can offer,
Jason
Why not have the Test Connection button trigger an Ajax action to call the controller, and display the result?
That way you avoid submitting your entire page.
In case you're not familiar with the details, here's a good overview on getting started with Ajax and MVC 4
http://ofps.oreilly.com/titles/9781449320317/ch_AJAX.html

Fulfilling POST to DB from Razor MVC

I have never done a POST request via Razor and MVC4. I think i have the core methods and stuff down but i am having difficulty fulfilling an actual POST request.
Here is the Razor View page code...
#model UserJob
#Html.HiddenFor(Model => Model.UserCode)
#Html.DropDownList("jobCode")
<input type="submit" value="Add" class="btn btn-default" />
And the method which i want to fulfil the POST method is.....
[HttpPost]
public ActionResult AddSkill(UserJob model)
{
db.UserJobs.Add(model);
db.SaveChanges();
return RedirectToAction("Jobs", new { UserCode = model.UserCode });
}
You Razor view needs to have the form. Either use #Html.BeginForm(...) to enclose your inputs, or just write HTML form markup yourself.

Categories