简体   繁体   中英

ASP.NET Core 2.0 Model returns null for items not used in a form

Context

ASP.NET Core 2.0 MVC project

Issue

Using a View Model something like this:

public class UpdateReturnIssueViewModel
{
    public IssueTrackerIssue Issue { get; set; }

    public bool CloseIssue { get; set; }
}

Where IssueTrackerIssue is something like this:

public class IssueTrackerIssue
{
        [Required]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int ID { get; set; }

        [StringLength(19)]
        public string OrderNumber { get; set; }

        [Range(1, 50)]
        public int Quantity { get; set; }

        [StringLength(200)]
        public string ReturnCustomerComment { get; set; }
}

I fetch data for my View like this:

    [HttpGet]
    public IActionResult UpdateReturnIssue(int ID)
    {
        // populate the view model in some way

        return View(this.updateReturnIssueViewModel);
    }

In my View I only use (ie define input asp-for tags for) IssueTrackerIssue.OrderNumber and CloseIssue.

This gets Posted to the controller thus:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult UpdateReturnIssue(UpdateReturnIssueViewModel viewModel)
{
    if (this.ModelState.IsValid)
    {
        // Some other processing
    }
}

On examining viewModel in the controller I see that everything is null except the properties that were used in View.

Question

How can I get the full contents of the View Model passed from controller to View and back to Controller intact? Put another way, how can I fully round trip a View Model where some parts of that View Model aren't utilised by the View?

There turned out to be two issues to overcome here.

1) As @JoshMein and others kindly pointed out values have to be used on the form else they are Posted back to the controller as null. Using Hidden values overcomes this.

2) My View was a Master - Detail type View and the Detail collection was always returning null ie an empty collection. I now realise that this was due to the way I'd coded the iteration of the Detail collection in the View. I was using

            @foreach (IssueTrackerIssueComment comment in Model.Issue.Comments)
            {
                <tr>
                    <td><input asp-for="@comment.ID" type="hidden" /></td>
                    <td><input asp-for="@comment.DateCreated" type="hidden" /></td>
                    <td><input asp-for="@comment.LastUpdated" type="hidden" /></td>
                    <td>
                        <input asp-for="@comment.DateCreated" class="form-control" type="date" asp-format="{0:yyyy-MM-dd}" />
                    </td>
                    <td>
                        <input asp-for="@comment.Narrative" class="form-control" />
                    </td>
                    <td>
                        <button asp-action="DeleteReturnIssueComment" asp-route-id=@comment.ID class="btn btn-primary">
                            <span class="glyphicon glyphicon-trash" />
                        </button>
                    </td>
                </tr>
            }

ASP.NET clearly wasn't able to figure out that these @comment items were bound to the Model so it wasn't passing them back to the Controller.

So I changed the Detail Collection from being ICollection<> to IList<> and used @for instead of @foreach, hence:

            @for (int i = 0; i < Model.Issue.Comments.Count; i++)
            {
                <tr>
                    <td><input asp-for="@Model.Issue.Comments[i].ID" type="hidden" /></td>
                    <td><input asp-for="@Model.Issue.Comments[i].DateCreated" type="hidden" /></td>
                    <td><input asp-for="@Model.Issue.Comments[i].LastUpdated" type="hidden" /></td>
                    <td>
                        <input asp-for="@Model.Issue.Comments[i].DateCreated" class="form-control" type="date" asp-format="{0:yyyy-MM-dd}" />
                    </td>
                    <td>
                        <input asp-for="@Model.Issue.Comments[i].Narrative" class="form-control" />
                    </td>
                    <td>
                        <button asp-action="DeleteReturnIssueComment" asp-route-id=@Model.Issue.Comments[i].ID class="btn btn-primary">
                            <span class="glyphicon glyphicon-trash" />
                        </button>
                    </td>
                </tr>
            }

Now that @Model is being used to reference these items instead of @comment everything binds nicely and I can see Comments in the controller.

I do not believe there is a way, to pass the values without them being in the form. You can use hidden fields to store the data to be passed back to the server. Here is the razor syntax to create the a hidden field for an existing model field in the form.

@Html.HiddenFor(x => x.HiddenFieldName)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM