0

OnGet() populates UnapprovedApplications with data from AspNetUsers. The user ids are correct at this time.

c4d69cbe-436e-4d74-bc7d-c4a99a8cbf34
e184da25-687b-4e51-9f1d-c3a93a732ec1

OnPostAsync(), however, returns the user ids as

20b6af04-ecb5-49c0-b4e4-bdb172bd19c7
5a61fbce-4c69-4a15-be66-725c5ab4b884

Why is the data in UnapprovedApplications changing? How do I keep these data from changing?

A model ReviewApplicationModel is constructed as so

[BindProperty]
        public IList<ApplicationUser> UnapprovedApplications { get; set; }

        public void OnGet(string returnUrl = null)
        {
            ReturnUrl = returnUrl;
            UnapprovedApplications = new List<ApplicationUser>();
            foreach (var user in userManager.Users.Where(x => !x.Approved))
            {
                System.Diagnostics.Debug.WriteLine(user.Id);
                UnapprovedApplications.Add(user);
            }
        }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            foreach (var user in UnapprovedApplications)
                System.Diagnostics.Debug.WriteLine(user.Id);
            return RedirectToPage();
        }

with a view of

@page
@model ReviewApplicationsModel
@{
    ViewData["Title"] = "Review Applications";
    ViewData["ActivePage"] = ManageNavPages.ReviewApplications;
}

<h4>@ViewData["Title"]</h4>
<div class="row">
    <div class="col-md-6">
        <form asp-route-returnUrl="@Model.ReturnUrl" method="post">
            <button type="submit" class="btn btn-primary">Approve Selected Applications</button>
            <table class="table">
                <thead>
                    <tr>
                        <th>
                            @Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().Id)
                        </th>
                        <th>
                            @Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().Organization)
                        </th>
                        <th>
                            @Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().FEIEIN)
                        </th>
                        <th>
                            @Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().State)
                        </th>
                        <th>
                            @Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().PrincipalCity)
                        </th>
                        <th>
                            @Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().PhoneNumber)
                        </th>
                        <th>
                            Approved
                        </th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    @for (var i = 0; i < Model.UnapprovedApplications.Count(); i++)
                    {
                        <tr>
                            <td>@Model.UnapprovedApplications[i].Id</td>
                            <td>@Model.UnapprovedApplications[i].Organization</td>
                            <td>@Model.UnapprovedApplications[i].FEIEIN)</td>
                            <td>@Model.UnapprovedApplications[i].State</td>
                            <td>@Model.UnapprovedApplications[i].PrincipalCity</td>
                            <td>@Model.UnapprovedApplications[i].PhoneNumber</td>
                            <td>
                                <input asp-for="@Model.UnapprovedApplications[i].Approved" class="form-control" />
                                <span asp-validation-for="@Model.UnapprovedApplications[i].Approved" class="text-danger"></span>
                            </td>
                        </tr>
                    }
                </tbody>
            </table>
        </form>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}
JPocoata
  • 634
  • 1
  • 11
  • 24
  • Please use Developer Tools in Chrome or Firefox to show us the payload being POSTed to the server. – mjwills Mar 27 '19 at 22:16
  • UnapprovedApplications[0].Approved: true __RequestVerificationToken: CfDJ8O-HFbOstWtHoJ33--d61DaNVv4NbYn8Sdo0l8dua4MXKn0AHsRjYs8I4tBwPAzbiEz0BX3_pw1KSrf_3DHR1Bp22bYg3G0ZMvyc2uv7sQCkxscx5dpKM_bKEnjje5vnhPFzGfIQAJ8vWtC_LJTSSBr4rLNA_qVWz3cn4jRKOEwtYJTKArMrGx-d4lEJ1IdW3Q UnapprovedApplications[0].Approved: false UnapprovedApplications[1].Approved: false should it not be posting true and false? – Steven Confessore Mar 27 '19 at 22:22
  • HTTP is stateless - why would `UnapprovedApplications` in one request `GET`, be the same in your `POST`? You're iterating over different things. I don't think you're even `POST`ing it back, etc (seeing in your code and as you can see in your comment on the payload). – EdSF Mar 27 '19 at 23:35
  • maybe I need to take some time to really understand HTTP. everything was working fine until I decided I wanted to work with collections. could you please provide a link to a tutorial on working with collections in aspnetcore? – Steven Confessore Mar 27 '19 at 23:41
  • wait @EdSF. I don't understand what you are telling me. I `GET` the users, put it into a collection and then the view displays all of the data... how can the `POST` not know the data when it came from the same model? all of the other pages that use `InputModel` have no issues. it is only when attempting to use a collection that this problem arises. also, it is impossible to use `foreach` in the view with a collection. for some reason I have to use a `for` loop. I know I'm making an error here and I don't want to say there is a bug in the language. I hope someone can answer this properly. – Steven Confessore Mar 28 '19 at 00:04
  • What are you sending in your `POST`? You provided your payload in a comment, there's no "model" there as well as in your code. You're only `POST`ing **two** pieces of data. Why would your controller "know"? – EdSF Mar 28 '19 at 17:15
  • check out my answer to the thread for some information provided by @Forty-Two. these are razor pages in dotnetcore mvc. the controller “knows” because the same page model used to `GET` the information is also the same page model to `POST` the information. I have provided a solution below as well – Steven Confessore Mar 28 '19 at 17:23

1 Answers1

0

This will be a running monologue in the pursuit of a solution to this thread.

I have discovered that IdentityUser has a default constructor that generates a Guid. What is happening is the model data is missing on the client side and the different user ids are different because the model data is being generated as if they were new users.

I found this information posted by @Forty-Two Pass an entire model on form submission

The model will be passed to the controller in its entirety, but the values of properties that are not bound by input or hidden fields will be lost.

You have to either bind the properties in the form on the client-side, or re-fetch the entity on the server-side.

You seem to be asking for something like @Html.HiddenFor(m => m.Model), and that is not possible. Sorry

One thing to keep in mind, if you have tons of hidden fields, you may be sending more data to the view than you really need. Consider employing view models

I will implement a ViewModel of the entire aggregated User object in an attempt to retain data retrieved via GET so that the edited data may be saved via POST as originally intended.

okay, so actually I just bound the necessary data in the form and everything works as intended.

here is a solution

[AllowAnonymous]
    public class ReviewApplicationsModel : PageModel
    {
        readonly UserManager<ApplicationUser> userManager;

        public ReviewApplicationsModel(UserManager<ApplicationUser> userManager)
        {
            this.userManager = userManager;
        }

        public string ReturnUrl { get; set; }

        [BindProperty]
        public ObservableCollection<ApplicationUser> UnapprovedApplications { get; set; }

        public void OnGet(string returnUrl = null)
        {
            ReturnUrl = returnUrl;
            UnapprovedApplications = new ObservableCollection<ApplicationUser>();
            foreach (var user in userManager.Users.Where(x => !x.Approved))
                UnapprovedApplications.Add(user);
        }

        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            ReturnUrl = returnUrl;
            if (!ModelState.IsValid)
                return Page();
            foreach (var user in UnapprovedApplications.Where(x => x.Approved))
            {
                var tmp = await userManager.FindByIdAsync(user.Id);
                tmp.Approved = true;
                await userManager.UpdateAsync(tmp);
            }
            return RedirectToPage();
        }
    }
@page
@model ReviewApplicationsModel
@{
    ViewData["Title"] = "Review Applications";
    ViewData["ActivePage"] = ManageNavPages.ReviewApplications;
}

<h4>@ViewData["Title"]</h4>
<div class="row">
    <form method="post">
        <div class="col-md-auto">
            <button type="submit" class="btn btn-primary">Approve Selected Applications</button>
            <table class="table">
                <thead>
                    <tr>
                        <th hidden>@Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().Id)</th>
                        <th>@Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().Organization)</th>
                        <th>@Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().FEIEIN)</th>
                        <th>@Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().State)</th>
                        <th>@Html.DisplayNameFor(x => x.UnapprovedApplications.FirstOrDefault().Approved)</th>
                    </tr>
                </thead>
                <tbody>
                    @for (var i = 0; i < Model.UnapprovedApplications.Count(); i++)
                    {
                    <tr>
                        <td hidden><input asp-for="UnapprovedApplications[i].Id" class="form-control" hidden /></td>
                        <td><input asp-for="UnapprovedApplications[i].Organization" class="form-control" disabled /></td>
                        <td><input asp-for="UnapprovedApplications[i].FEIEIN" class="form-control" disabled /></td>
                        <td><input asp-for="UnapprovedApplications[i].State" class="form-control" disabled /></td>
                        <td><input asp-for="UnapprovedApplications[i].Approved" class="form-control" /></td>
                    </tr>
                    }
                </tbody>
            </table>
        </div>
    </form>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

there has to be a more flexible way to do this and I hope to discover it one day but this works for right now