Why is Request.Form["myfield"] empty unless property is available? - c#

In a RazorPage, I submit a form but in the model OnPost(), Request.Form["myfield"] is empty.
<form method="post">
<input id="myfield"/>
<button type="submit">Submit</button>
</form>
But if I add a class level property in the model:
public string myfield { get; set; }
and update the form to use a TagHelper:
<form method="post">
<input asp-for="myfield"/>
<button type="submit">Submit</button>
</form>
Request.Form["myfield"] is populated when it hits the breakpoint in OnPost(). But the actual property is null in OnPost().
Does anyone understand what is going on? Meaning:
Why doesn't Request.Form["myfield"] populate with the class level property?
Why doesn't the property populate?
-- EDIT --
The first part of this because I'm using id instead of name.
Still not sure about the 2nd part on the asp-for and model property.

Razor pages are not like Web Forms where those Form fields are automatically populated. They are more like a simplified MVC controller.
You can specify that the all of the form fields are passed in by adding it as a parameter to your post method:
public async Task<IActionResult> OnPostAsync(IFormCollection data) {
var myField = data["myField"];
...
}
Using Bindable properties is the preferred way to persist data between the client and server.

Related

How to resolve hidden form field auto generated from #Html.CheckBoxFor

Whenever i create a boolean property in my ASP.NET MVC model and try to create a checkbox using #Html.CheckBoxFor i get a hidden form field autogenerated. I know it is happening for a good reason but whenever i submit my form and get that value using form collection it returns 2 values when it is in checked state. It submits value like this - 'true,false'. Now when i get the values using form collection and do bool.Parse() it throws an error because it cannot parse 'true,false' together. Is there any way around to get rid of hidden form field or should i try something while processing the request ??
In Model
[Display(Name ="Is Enabled")]
public bool IsEnabled { get; set; }
In Controller
public ActionResult Request(FormCollection collection)
{
bool valueIsEnabled=bool.Parse(collection["IsEnabled"])
}
In View
#Html.CheckBoxFor(m => m.IsEnabled, new {
#class = "custom-input"
})
When I click Checked
Expected Result - true
Actual Result - true,false
When I don't click checked
Expected Result - false
Actual Result - false
Ok I did some research and found that the CheckBox helper generates an additional hidden field with the same name as the checkbox (you can see it by browsing the generated source code):
<input checked="checked" id="Visible" name="Visible" type="checkbox" value="true" />
<input name="Visible" type="hidden" value="false" />
So both values are sent to the controller action when you submit the form. Here's a comment directly from the ASP.NET MVC source code explaining the reasoning behind this additional hidden field:
if (inputType == InputType.CheckBox) {
// Render an additional <input type="hidden".../> for checkboxes. This
// addresses scenarios where unchecked checkboxes are not sent in the request.
// Sending a hidden input makes it possible to know that the checkbox was present
// on the page when the request was submitted.
...

Bind variable number of tags with asp-for

I have a view where an user can trigger an action that will insert input tags in the page:
The 'Add more' button is tied to a jquery click event.
HTML:
<div id="details">
<label>More details</label>
<input placeholder="Foo" asp-for="">
<input placeholder="Bar" asp-for="">
</div>
<button id="add-details" type="button">Add more</button>
JS:
$("#add-details").click(function(){
$("#details").append(`<input placeholder="Foo" asp-for="">
<input placeholder="Bar" asp-for="">`)
});
Now, I want to bind the <input> tags to a C# Model, in order to retrieve their values in a Controller. But there could be any amount of these tags.
The most convenient way to retrieve them would be as a Dictionary<T>, but I don't know what to put in the asp-for attributes.
So how should my Model look like? What should I put in my asp-for attributes?
Although this answer is not about TagHelpers (which you are using), please read it to give you a background of what you need to do and why.
In your case you will want to add input tags so their names are array-like with indices. For example, below:
<input placeholder="Foo" asp-for="[0].Foo">
<input placeholder="Foo" asp-for="[1].Foo">
will map to a collection and the first item's (index 0) will be the contents of the input tag with asp-for="[0].Foo" and the second item's will be the contents of the input tag with asp-for="[1].Foo".
You controller action method should accept a collection. For example, if you were adding students, then the action method may look like this:
[HttpPost]
public ActionResult Index(List<Student> students) { ... }
Finally, please refer the Expression names and Collections of the docs for more info.

MVC Post A Child model to a Controller

I have a parent view model (Let's call it ParentViewModel) which has a list of children view models (Let's call them ChildViewModel). Each child view model can be edited independently and I have a separate form which I display in a loop. This works brilliantly but I cannot work out how to post just the child model and ignore the parent.
This is my form:
#model ParentViewModel
...
#foreach (var child in Model.Children)
{
#using (Html.BeginForm("_EditChild", "Admin", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.EditorFor(model => child.Content, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => child.Content, "", new {#class = "text-danger"})
</div>
<div class="form-group">
<div class="col-md-12">
<input type="submit" value="Create" class="btn btn-default new-post" />
</div>
</div>
}
}
And this is the signature of my controller. It is expecting a type ChildViewModel which exists in ParentViewModel as a list.
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult _EditPost([Bind(Include = "")] ChildViewModel childViewModel)
{
}
The form works and submits BUT ChildViewModel is null when it reaches the submit controller. This is certainly because binding between the Form Post and the Action is not happening.
I am afraid it is not possible to post the child model only , since the page can define one and only one model that is the parent model you have defined .
But you can solve your problem simply by posting the parent model and extracting the child model in the controller .
It is possible, just not intended by ASP.NET MVC. All you would have to do is remove the parent prefix from the name of your submitted inputs on the client side. Your input would probably look something like:
<input name="Children[0].SomeProperty" ../>
If your AdminController._EditChild action expects a ChildViewModel, then you'd just have to use javascript to rename the input to:
<input name="SomeProperty" ../>
and the model binder should build the ChildViewModel. Alternatively, you might also be able to solve it by creating a custom ValueProvider or ModelBinder that maps the inputs to a ChildViewModel even though it has the wrong prefix...although that would seem like an uglier hack to me than changing the input names. On that note, I would probably also update the IDs with javascript when updating the names just to keep them in sync, even though only the name is used for binding.
Note also, if you're not looping but simply want to submit a single child ViewModel of your model, you can just assign it to a variable:
#var childVM = Model.ChildProp;
#Html.HiddenFor(m => childVM.ID)
Notice m is disregarded in the property expression of HiddenFor. I think last time I did this the variable name had to match the action's parameter name so you would submit this to:
public ActionResult SomeAction(ChildViewModel childVM){ ... }
I'm currently trying to understand why this technique can't be combined with looping.

MVC get a form control data- attribute value in a post ActionResult

i'm trying to recreate something i did back in winforms days with html data- attributes in mvc.
you could set an attribute on a form control using this:
txtTest.Attributes.Add("data-myattribute", "my value");
and then read it back once the form had been posted using:
txtTest.Attributes["data-myattribute"]
adding the attributes in mvc is a breeze:
#Html.TextBoxFor(m => m.FirstName, new { data_myattribute = "my value" })
just can't figure out how to read them in the action result once the form has been posted?!
been searching around and whilst i can find loadsa posts on how to set data- attribute values and read them in javascript, i can't find anything that'll tell me how to get them back in the code...
anyone out there know the magic answer?!
Data attributes are not included in the data that's posted with the form, so there is no way to read them in your controller action. Try using a hidden field instead:
<input type="hidden" name="FirstNameAttribute" value="my value" />
This'll bind back to a model property:
public string FirstNameAttribute { get; set; }

Fill #Html.Hidden with div value

Let's say I have a div like this:
<div id ="carmodel"></div>
This div gets populated using Javascript 5 seconds after pageload, so it will look like this:
<div id ="carmodel">Audi A4</div>
Now I need to send the text inside the "Carmodel" div to the controller when a form is submitted.
#using (Html.BeginForm("Method", "Controller"))
{
#Html.TextBox("textbox1", "")
#Html.TextBox("textbox2", "")
<input type="submit" value="Subtmit"/>
}
I thought about using a hidden field and posting it together, but I don't know how to populate the hidden field with the text in "Carmodel" div:
#Html.Hidden("carmodelhiden", "");
It seems as if I need to use JQuery to retrieve the text like this:
var value = $("#carmodel").text();
But how do I then send this to the controller?
I also thought about using .ajax to post it but not sure how to handle the value in the controller once it's there, since the ajax method in the controller would be separate from where the form gets posted.
Anybody have any advice?
You need to set the value of the hidden input:
$('#carModelHidden').val(...);
Make sure to put the hidden value between the form using statement. Also, use a strongly typed view to bind with a property in your model.
#using (Html.BeginForm("Method", "Controller"))
{
<div id ="carmodel">
#Html.HiddenFor(model => model.SomeProperty)
</div>
}
#section scripts{
<script>
// Hook into some event and set:
$('#carModelHidden').val(...);
</script>
}

Categories

Resources