Aspnetcore: Razor Pages BindProperty Value Not Set on Post

Created on 11 Jan 2020  路  7Comments  路  Source: dotnet/aspnetcore

I'm creating a form using one razor page (I know this may not be a good idea, but I'm trying to do some [not so] rapid prototyping. Basically, the first step is that the user enters a bunch of data, and the next step is that the user will upload some files. My page model looks like this:

    public class CreateModel : PageModel
    {
        private readonly DefaultDbContext _context;

        public CreateModel(DefaultDbContext context)
        {
            _context = context;
        }

        public async Task<IActionResult> OnGet(Guid id)
        {
            FileUpload = false;
            return Page();
        }

        [BindProperty]
        public bool FileUpload { get; set; } // Stored in a hidden field in my cshtml


        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see https://aka.ms/RazorPagesCRUD.
        public async Task<IActionResult> OnPostAsync()
        {
            FileUpload = true; // Hidden field is always false  
            return Page();
        }
    }

This is what my form looks like:

    <form method="post" enctype="multipart/form-data">
        <input type="hidden" asp-for="FileUpload" />

        @if (!Model.FileUpload)
        {
            // Do some stuff
        }
        else
        {
            <div class="form-group">
                <label asp-for="Upload" class="control-label"></label>
                <input type="file" asp-for="Upload" multiple />
                <span asp-validation-for="Upload" class="text-danger"></span>
            </div>
        }
        <div class="form-group">
            @if (Model.FileUpload)
            {
                <input type="submit" value="Finished" class="btn btn-primary" />
            }
            else
            {
                <input type="submit" value="Next" class="btn btn-primary" />
            }
        </div>
    </form>

When I click submit the first time, I would expect FileUploadto be true, which it is when I step through the .cshtml page in the debugger. The problem is that when the page is sent back to the browser, the value is always false:

<input type="hidden" data-val="true" data-val-required="The FileUpload field is required." id="FileUpload" name="FileUpload" value="False" />

Either I'm doing something wrong, or this is a bug.

By Design Resolved area-mvc question

Most helpful comment

ModelState is used to re-populate value (if not overridden) in order to not disrupt the user.

Take an example where a user provides a bad value for a boolean property (JohnDoe) and they then submit a form. MVC validates the input, indicates that it's a bad value and then the field is re-populates the field with what the user original typed so an error message of "JohnDoe" is an invalid input makes sense instead of us populating it with the model value (false by default).

This behavior definitely gets a little more confusing when you start using hidden input fields though; reason being the user isn't directly typing into the hidden input field and you're programmatically changing the data being sent over the wire. For other input types whatever the user types round tripping makes a lot more sense 馃槃. We had to balance do we special case hidden input types here or keep consistent behavior with outher input types? We choose the latter.

Hopefully this sheds some light on the behavior. Going to mark this as by design and close for now. Feel free to re-open if you feel i've misunderstood your question.

All 7 comments

Thanks for contacting us, @zeus82.
It looks like your sample would work, if you modify the hidden field tag helper as follows:

<input type="hidden" asp-for="FileUpload" value="@Model.FileUpload" />

@NTaylorMullen I'm not sure whether asp-for should have been enough here or not? Can you confirm that the proposed approach is correct or not? Thanks!

I tried that, but it didn't work.

I also created a repo that highlights this problem. See the Index page.

When I first load index and view page source I see this:

<div class="text-center">
    <form method="post" enctype="multipart/form-data">
        <input type="hidden" data-val="true" data-val-required="The Test1 field is required." id="Test1" name="Test1" value="False" />
        <input type="hidden" data-val="true" data-val-required="The Test2 field is required." id="Test2" name="Test2" value="False" />

            <b>Test1 Is False</b>
        <div class="form-group">
            <input type="submit" value="Next" class="btn btn-primary" />
        </div>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8H5Wtnrq-8hNnDZjQMWq8gpuJ3P2vgkcW6ysQlI7S0RmpGBwuyFw3jTR0F8Fg0lFKgFu0-6_KWaxk5mheWBh8Fnunfn8FbNCIJrivZwqEHbFPYiyomZC8w6b9onxcO9_yyADZBtXMVIxXWNQg8GAfZ8" /></form>
</div>

When I click next, and view page source I see this

<div class="text-center">
    <form method="post" enctype="multipart/form-data">
        <input type="hidden" data-val="true" data-val-required="The Test1 field is required." id="Test1" name="Test1" value="False" />
        <input type="hidden" data-val="true" data-val-required="The Test2 field is required." id="Test2" name="Test2" value="False" />

            <b>Test1 Is True</b>
        <div class="form-group">
            <input type="submit" value="Next" class="btn btn-primary" />
        </div>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8H5Wtnrq-8hNnDZjQMWq8goQWjz0PBYcYTTym6-mQob6LSo312ELB6eyvAPCZ3I23e8TCpow79IGIDsU-Vs7l5wYKaymnaXR8UvT9Ke3CmhdZsFLXo_5bpYTp6jKRA1vOXt4ctOjUz7h8zZ9ZyJmUuM" /></form>
</div>

Notice that the hidden input is false, but the text say 'True'. This suggests that in the rendering on the server the value is correct, but the rendering of the asp-for value is still false.

I've submitted a PR with my earlier proposal and I can confirm that it works:
image

As for why asp-for is not satisfactory, I'll let @NTaylorMullen to respond.

@mkArtak Thanks - Your PR does fix the issue, but it still seems like there is a problem with the way aps-for works in this scenario. I'm going to hold off on completing the PR until there is more clarification on what asp-for is supposed to do in this scenario.

I answered something similar on Stack Overflow a while back. Does that help? I was never crazy about the solution, but I think the explanation should help.

asp-for doesn't affect the value attribute at all if it's set explicitly, as in the PR solution.

@serpent5, adding ModelState.Remove(nameof(Test1)); to the Post action also fixes the problem, but this really isn't very intuitive, especially since when your debugging, the value you see in the .cshtml is the correct value, Is there a reason the default behavior of ModelState is to not update when a value changes?

ModelState is used to re-populate value (if not overridden) in order to not disrupt the user.

Take an example where a user provides a bad value for a boolean property (JohnDoe) and they then submit a form. MVC validates the input, indicates that it's a bad value and then the field is re-populates the field with what the user original typed so an error message of "JohnDoe" is an invalid input makes sense instead of us populating it with the model value (false by default).

This behavior definitely gets a little more confusing when you start using hidden input fields though; reason being the user isn't directly typing into the hidden input field and you're programmatically changing the data being sent over the wire. For other input types whatever the user types round tripping makes a lot more sense 馃槃. We had to balance do we special case hidden input types here or keep consistent behavior with outher input types? We choose the latter.

Hopefully this sheds some light on the behavior. Going to mark this as by design and close for now. Feel free to re-open if you feel i've misunderstood your question.

Was this page helpful?
0 / 5 - 0 ratings