0

I have a view that is displaying records on a table with checkboxes for the user to select and update. However, if I have 3 records in the table, Data[0], Data[1], Data[2] and I select only the checkbox for Data[0] and POST it to the controller, then when the screen is returned Data[1] checkbox now has the state of Data[0] that was submitted (true). However the database shows the checkbox value is 0 (false) for Data[1]. My only guess is something with the model binding or I would need to persist the checkbox states some how.

Model

public class DrinkingWaterModel 
{
    //contains all properties of the FPDrinkingWater Entity 
    public List<FPDrinkingWater> Data { get; set; } 

    public AlertModel SuccessAlert { get; set; }

    public AlertModel FailureAlert { get; set; }
}

GET Method

public async Task<ActionResult> UnverifiedDrinkingWaterLog(AlertModel 
successAlert, AlertModel failureAlert)
    {
        //get unverified data from the db
        var data = (from s in await Manager.Store.GetAllAsync<FPDrinkingWater>()
                   where s.Verified.Equals(false)
                   select s).ToList();

        //fill the model
        DrinkingWaterModel model = new DrinkingWaterModel
        {
            SuccessAlert = successAlert,
            FailureAlert = failureAlert,
            Data = data
        };

        return PartialView("_UnverifiedFPDrinkingWaterTable", model);
    }

The View

@model MyApplication.Areas.FP.Models.DrinkingWaterModel
@{
Layout = null;
}
<div>
    @Html.AntiForgeryToken()
    <table id="UnverifiedDrinkingWaterTable" class="table table-hover">
        <thead>
            <tr>
                <th>@Html.LabelFor(m => m.Data.FirstOrDefault().SID)</th>
                <th>@Html.LabelFor(m => m.Data.FirstOrDefault().Location)</th>
                <th>@Html.LabelFor(m => m.Data.FirstOrDefault().Replicate)</th>
                <th>@Html.LabelFor(m => m.Data.FirstOrDefault().CollectionDate)</th>
                <th>@Html.LabelFor(m => m.Data.FirstOrDefault().CollectionTime)</th>
                <th>@Html.LabelFor(m => m.Data.FirstOrDefault().Collectors)</th>
                <th>@Html.LabelFor(m => m.Data.FirstOrDefault().Clorinated)</th>
                <th>@Html.LabelFor(m => m.Data.FirstOrDefault().Comments)</th>
                <th>@Html.LabelFor(m => m.Data.FirstOrDefault().Verified)</th>
            </tr>
        </thead>

        <tbody>
            @for (int i = 0; i < Model.Data.Count(); i++)
            {
                <tr>
                    @Html.HiddenFor(m => m.Data[i].Id)
                    @Html.HiddenFor(m => m.Data[i].SID)
                    <td>@Html.DisplayFor(m => m.Data[i].SID)</td>
                    @Html.HiddenFor(m => m.Data[i].Location)
                    <td>@Html.DisplayFor(m => m.Data[i].Location)</td>
                    @Html.HiddenFor(m => m.Data[i].Replicate)
                    <td>@Html.DisplayFor(m => m.Data[i].Replicate)</td>
                    @Html.HiddenFor(m => m.Data[i].CollectionDate)
                    <td>@Html.DisplayFor(m => m.Data[i].CollectionDate)</td>
                    @Html.HiddenFor(m => m.Data[i].CollectionTime)
                    <td>@Html.DisplayFor(m => m.Data[i].CollectionTime)</td>
                    @Html.HiddenFor(m => m.Data[i].Collectors)
                    <td>@Html.DisplayFor(m => m.Data[i].Collectors)</td>
                    @Html.HiddenFor(m => m.Data[i].Clorinated)
                    <td>@Html.DisplayFor(m => m.Data[i].Clorinated)</td>
                    @Html.HiddenFor(m => m.Data[i].Comments)
                    <td>@Html.DisplayFor(m => m.Data[i].Comments)</td>
                    <td>@Html.EditorFor(v => v.Data[i].Verified) </td>
                </tr>
            }

            @if (Model.Data.Count() == 0)
            {
                <tr>
                    <td colspan="@Html.ColumnCount(9)"><em>No Drinking Water data to verify.</em></td>
                </tr>
            }

        </tbody>
    </table>

@if (verify)
{
<button type="submit" class="btn btn-primary" data-loading-text="Verifying...">Verify</button>
}
</div>
<script>
$(document).ready(function () {
        makeDataTable('UnverifiedDrinkingWaterTable')
    });
    $('#RefreshDrinkingWater').click();



</script>

The POST method

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyDrinkingWater([System.Web.Http.FromBody] DrinkingWaterModel model)
    {
        var successAlert = new AlertModel();
        var failureAlert = new AlertModel();

        if (ModelState.IsValid)
        {
            List<string> successes = new List<string>();
            List<string> failures = new List<string>();


            foreach (var verifiable in model.Data)
            {
                if (verifiable.Verified != false)
                {
                    verifiable.Verified = true;
                    verifiable.VerifiedDate = DateTime.Now;
                    verifiable.VerifiedBy = User.Identity.Name;  
                    var result = await Manager.VerifyAsync(verifiable);
                }
             }
        }
        else
        {
            InvalidState(failureAlert);
        }
        //return the GET method to update and refresh the table
        return await UnverifiedDrinkingWaterLog(successAlert, failureAlert);
    }
Braden
  • 135
  • 1
  • 11

1 Answers1

1

The value isn't updating because it's being read by ModelState and not the model. Check out some related questions to get a better idea of why this feature exists. The short version is that typically you only return the exact same page from a POST if there is an error that needs to be corrected. By reading from the ModelState you give the user the benefit of keeping their previous input.

Two options to resolve this problem:

  1. Call ModelState.Clear(); in your HttpPost action before returning the view. Personally I would not recommend this, but it is an option.
  2. If the ModelState is valid, follow the Post -> Redirect -> Get pattern and call RedirectToAction to your original HttpGet method. If the ModelState is not valid, then do what you're doing so you can show an error message and keep the user input.
Justin Helgerson
  • 24,900
  • 17
  • 97
  • 124
  • How would the `RedirectToAction` work with the two overloads? – Braden Aug 25 '18 at 14:45
  • You can pass objects into the `RedirectToAction` method. Personally I only use 1 model per action, but using multiple objects should work fine. Internally it will build a route dictionary. – Justin Helgerson Aug 25 '18 at 15:31
  • The reason for two models is, say I select 10 records to verify...2 may fail on the update. Granted...there's definitely a better way of handling this, as I'm finding out. But I have a deadline coming up that I need to have something functioning before I consider a total re-write. – Braden Aug 25 '18 at 16:38
  • Yeah so you could just pass two objects in as route data in the redirect. Or you can clear the `ModelState` like I mentioned in 1 above. – Justin Helgerson Aug 25 '18 at 17:19
  • So I've been trying to use the route dictionary and when it reaches the GET method, the SuccessAlert and FailureAlert are both null even though the data is present in RedirectToAction before reaching the GET method. Almost like RedirectToAction looses track of the data. – Braden Aug 27 '18 at 16:34
  • What does the request look like? – Justin Helgerson Aug 27 '18 at 17:05
  • `return RedirectToAction("UnverifiedDrinkingWaterLog", new { successAlert = successAlert, failureAlert = failureAlert});` This is what I replaced after the `return` with. When debugging, successAlert does contain the success string, but when it reaches the GET method, it's null. – Braden Aug 27 '18 at 17:58
  • It is unmodified. – Braden Aug 27 '18 at 19:49
  • An HTTP redirect includes a 302 (or 301) status code and a location URL. The browser then makes a request to that URL. What is the *URL* being requested that results in the model binding not working? – Justin Helgerson Aug 27 '18 at 19:57
  • It doesn't change. Upon submitting a record to Verify the URL doesn't change. (localhost:56668/FP/DrinkingWater#UnverifiedDrinkingWatersContent) this is the before submit and after submit. The purpose of this Alert Model is to display to the user alerts upon completion of tasks. Kind of like Toast notifications. – Braden Aug 27 '18 at 20:04