Search code examples
c#asp.net-coremodel-view-controller

Primary key for model is taken from route, instead from the view model


I have the following action methods in controller:

[HttpGet("Duplicate/{id:int}")]
public async Task<IActionResult> DuplicateAsync(int id)
{
    return await EditAsync(id, true);
}

[HttpGet("Edit/{id:int?}")]
public async Task<IActionResult> EditAsync(int id = 0, bool copy = false)
{
    MyViewModel? model = null;
    ....
    ....
    if (copy) model.ID = 0;
    return View("Edit", model);
}

And in the view, I am adding a hidden field with the model.ID value, this way:

@Html.HiddenFor(m => m.ID)

However, strangely, the hidden field generated, when calling https://server/MyController/Duplicate/14, is set with the id value passed in the route, instead of 0, which is set by the if (copy) model.ID = 0 instruction.

How can I avoid that behaviour?

Should I specify some of these attributes in the ID property?

> [FromQuery] - Gets values from the query string.
> [FromRoute] - Gets values from route data.
> [FromForm] - Gets values from posted form fields.
> [FromBody] - Gets values from the request body.
> [FromHeader] - Gets values from HTTP headers.

Should it be [FromForm] in my case ? I tried it but it did not work. Is there another way?

Most curious thing is that the model defines the ID field in uppercase, and in the route it is defined in lowercase.


Solution

  • Html helpers such as HiddenFor will always first use the posted/got value and after that the value in the model, that’s why the value is not set as you expected. You can manually generate the hidden field to avoid this.

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

    My cshtml

    <div>
        <a asp-action="Do" asp-route-id="@Model?.Id">Do</a> |
    </div>
    <div>
        <h4>Directly Pass</h4>
        <dl class="row1">
                @Html.HiddenFor(model => model.Id)
        </dl>
    </div>
         …
        <form name="form1">
            <input type="hidden" asp-for="Id" value="@Model.Id"/>
        </form>
         …
    

    Html generated

    enter image description here

    As you can see is the html, the value of model.id is passed, but not selected by hiddenfor. Directly generating the html will work.

    The id defined in route is binded, which means controller, not model will get the value. You still need to bind the value with model in your logic. There is no necessary connection between how you define the variable name in model and in route.