3

There are a few similar questions on here that I have checked but none seem to answer my issue, so here's hoping someone can help me out.

I have a form in a view, and a partial view I'm using like a subform. The Partial view is used to display an iList of items. (Screenshot below to show how this appears).

In the partial view, each item has a checkbox which the user can check to delete it. If I check the checkbox for the first item, the first item is removed from the list in code, but when the model is passed back to the View, the wrong item (the checked item) is the one that comes back.

So in the example below, if I check the first item (No Answer Delay = 18) and submit, that same item stays on the page whilst the other one (No Answer Delay = 10) disappears. If I then reload all the data, the correct item (No Answer Delay = 10) appears.

I have checked in the method that the correct data is being passed back, but the wrong item remains on the page. If I then refresh the page, the correct item appears. Note, the method has been sanitised a bit but the correct item does get removed them the database.

One other thing to note, this is a plugin to a 3rd party product so I cannot run it unless I publish to the other product, making debugging tricky.

The code for the main view is

@using(Html.BeginForm("SaveCallFeatures", "CallFeatures", FormMethod.Post, new { id = "CallFeatures", name = "CallFeatures" }))
{
    @Html.AntiForgeryToken()

    <div>
        <h2>Call Features</h2>

        <div class="form-panel">
            <h4>Telephone Call Features</h4>

            <div>
                @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                @Html.LabelFor(model => model.phoneNumber, htmlAttributes: new { @class = "label" })
                @Html.EditorFor(model => model.phoneNumber, new { htmlAttributes = new { @class = "form-control", @readonly = "readonly" } })
                @Html.ValidationMessageFor(model => model.phoneNumber, "", new { @class = "text-danger" })
            </div>

            <div>
                @Html.LabelFor(model => model.password, htmlAttributes: new { @class = "label" })
                @Html.EditorFor(model => model.password, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.password, "", new { @class = "text-danger" })
            </div>

            <div>
                @Html.LabelFor(model => model.hideOutgoingCallerID, htmlAttributes: new { @class = "label" })
                @Html.CheckBoxFor(model => model.hideOutgoingCallerID, new { htmlAttributes = new { @class = "form-control" } })
            </div>

            <div>
                @Html.LabelFor(model => model.callWaiting, htmlAttributes: new { @class = "label" })
                @Html.CheckBoxFor(model => model.callWaiting, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div id="ForwardRules">
            @Html.Partial("_ForwardingRules")
        </div>

    </div> //form

    @Html.TextArea("Test")

    <div id="form-buttons" class="col-md-offset-4 col-md-6">
        <input type="button" value="Save" id="save-button" class="btn btn-primary" />
    </div>


<script type="text/javascript">
    $("#update-button").on('click', function () {
        GetFwdRules();
    });
</script>




function GetFwdRules() {
        $.ajax
        ({
            url: '@Url.Action("GetFwdRules", "CallFeatures", new { boid = Model.CompanyId })',
            method: 'GET',
            data: $("#CallFeatures").serialize(),
            cache: false,
            success: function (returnData) {
                $("#ForwardRules").html(returnData);
                $("#Test").html(returnData);
                alert('GetFwdRules');
            },
            failure: function () {
                alert('GetFwdRules Failure');
            }
        });
    }

The code the the Partial View is

@model XXXXX.Models.CallFeaturesModel

<div class="form-panel">
<h4>Active Forwarding Rules</h4>

    @for(int i = 0; i < Model.FwdRules.Count; i++)
    {
        <div>
            @Html.HiddenFor(model => Model.FwdRules[i].ForwardingRuleID)
        </div>
        <div>
            @Html.LabelFor(model => Model.FwdRules[i].Condition)
            @Html.TextBoxFor(model => Model.FwdRules[i].Condition, new { htmlAttributes = new { @class = "form-control", @readonly = "readonly" } })
        </div>
        <div>
            @Html.LabelFor(model => Model.FwdRules[i].Destination)
            @Html.TextBoxFor(model => Model.FwdRules[i].Destination, new { htmlAttributes = new { @class = "form-control", @readonly = "readonly" } })
        </div>
        <div>
            @Html.LabelFor(model => Model.FwdRules[i].NoAnswerDelay)
            @Html.TextBoxFor(model => Model.FwdRules[i].NoAnswerDelay)
            @Html.DescriptionFor(model => model.FwdRules[i].NoAnswerDelay)
        </div>
        <div>
            @Html.LabelFor(model => Model.FwdRules[i].ToDelete)
            @Html.CheckBoxFor(model => Model.FwdRules[i].ToDelete)
        </div>
        <br />
    }

This is the method

[HttpGet]
public ActionResult GetFwdRules(CallFeaturesModel CFModel)
{
  // Refresh the list to include on those where the ToDelete variable == false (checkbox is unchecked)
  CFModel.FwdRules = CFModel.FwdRules.Where(x => x.ToDelete == false).ToList();
  return PartialView("_ForwardingRules", CFModel);
}   

And this is the model

 public class CallFeaturesModel : UIPluginBaseModel
 {
   [Display(Name = "Phone Number")]
   public string phoneNumber { get; set; }

   [Display(Name = "Password")]
   public string password { get; set; }

   [Display(Name = "Hide Outgoing Caller ID")]
   public bool hideOutgoingCallerID { get; set; }

   [Display(Name = "Call Waiting")]
   public bool callWaiting { get; set; }

   [Display(Name = "Message")]
   public string Message { get; set; }

   public IList<ActiveForwardRule> FwdRules { get; set; }
 }

public class ActiveForwardRule
{
  [Display(Name = "Rule ID")]
   public string ForwardingRuleID { get; set; }

   [Display(Name = "Condition")]
   public string Condition { get; set; }

   [Display(Name = "Destination")]
   public string Destination { get; set; }

   [Display(Name = "No Answer Delay", Description = " seconds (approx. 6 seconds for each ring cycle)")]
   public int NoAnswerDelay { get; set; }

   [Display(Name = "Delete")]
   public bool ToDelete { get; set; }
 }

Here's a screenshot of the example. Looks like I'm not allowed to embed an image yet.

Screenshot

Hoping someone can point out where I am going wrong.

Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
Dugggie
  • 356
  • 5
  • 17
  • Can you share the Controller method used for submitting? The controller method you shared looks like it just refreshes the list when the "update-button" is pushed which you didn't describe as being part of the process. – Hopeless Jan 30 '19 at 06:05
  • If you ever get weird results when using something as a result of a post, I would also use ModelState.Clear() to see if it is old data that is being displayed. It might help you track down the issue – Slicksim Jan 30 '19 at 10:14
  • Hi @Hopeless, the method I have included (basically created a method to handle the button click - which is how I am doing the submit anyway) is a sanitised version of the one I am using as there is some work-related stuff in the original that I can't share. – Dugggie Jan 30 '19 at 10:31
  • Thanks @Slicksim, I tried adding that in before returning the PartialView but it had no effect. – Dugggie Jan 30 '19 at 12:34
  • @Slicksim, forget my last comment, this fixed it, awesome, thank you. If you can add this as a solution I can accept it. – Dugggie Jan 30 '19 at 12:41

1 Answers1

7

When posting data and then re-displaying data in the same request, the ModelState will be populated with the data from the original post.

This can lead to situations where items that should have been deleted still show or a form being pre-filled when it should now be blank.

Adding:

ModelState.Clear()

before re-displaying the data will clear down the model state and prevent the tag helpers from populating themselves from the original post request

Slicksim
  • 7,054
  • 28
  • 32
  • Debugging this felt like a fever dream. Thank you for the solution. – Lukas Nov 10 '21 at 22:04
  • This solves the problem and causes it to behave as I, and apparently many other devs, expect. However it may not be good practice and contrary to how the framework intends. See https://stackoverflow.com/questions/1775170/asp-net-mvc-modelstate-clear. (IMHO MVC is a terrible framework.) – Mmm Nov 30 '22 at 18:58