0

I'm following this standard pattern for using Ajax to reload a partial view. However, when the partial view is reloaded, the controls in the view have different IDs. They lose the name of the parent model. This means that when the form is posted back, model binding won't work.

So in the example below, when the page is first loaded, the checkbox id is "PenguinEnclosure_IsEnoughPenguins" but after the partial is reloaded, the checkbox id is "IsEnoughPenguins" The ID must be "PenguinEnclosure_IsEnoughPenguins" for model binding to correctly bind this to the PenguinEnclosure property of the VM.

Model:

public class ZooViewModel
{
    public string Name { get; set; }
    public PenguinEnclosureVM PenguinEnclosure { get; set; }
}

public class PenguinEnclosureVM
{
    public int PenguinCount { get; set; }

    [Display(Name = "Is that enough penguins for you?")]
    public bool IsEnoughPenguins { get; set; }
}

Controller:

public ActionResult Index()
{
    var vm = new ZooViewModel
    {
        Name = "Chester Zoo",
        PenguinEnclosure = new PenguinEnclosureVM { PenguinCount = 7 }
    };

    return View(vm);
}

public ActionResult UpdatePenguinEnclosure(int penguinFactor)
{
    return PartialView("DisplayTemplates/PenguinEnclosureVM", new PenguinEnclosureVM { PenguinCount = penguinFactor * 10 });
}

View:

@model PartialProblem.Models.ZooViewModel

@Scripts.Render("~/bundles/jquery")

<p>
    Welcome to @Model.Name !
</p>
<p>
    <div id="penguin">
        @Html.DisplayFor(m => m.PenguinEnclosure)
    </div>
</p>

<button id="refresh">Refresh</button>

<script>
    $(document).ready(function () {
        $("#refresh").on("click", function () {
            $.ajax({
                url: "/Home/UpdatePenguinEnclosure",
                type: "GET",
                data: { penguinFactor: 42 }
            })
            .done(function (partialViewResult) {
                $("#penguin").html(partialViewResult);
            });
        });
    });
</script>

Partial View:

@model PartialProblem.Models.PenguinEnclosureVM

<p>
    We have @Model.PenguinCount penguins
</p>
<p>
    @Html.LabelFor(m => m.IsEnoughPenguins)
    @Html.CheckBoxFor(m => m.IsEnoughPenguins)
</p>
MrBlueSky
  • 728
  • 6
  • 20
  • Your model in partial view is different from the one you have on page load in Index method – Reyan Chougle Mar 14 '19 at 12:44
  • Actually, I forgot to include the code for the partial view! Thanks, Reyan. – MrBlueSky Mar 14 '19 at 12:47
  • Dunno if I'm being silly and not seeing it but how is the partial getting rendered when you first load the page? The DisplayFor? But what it looks like is in your ajax response, you're only returning a standalone object of `PenguinEnclosureVM `, where as whatever is rendering the partial view initially, is being passed a `ZooViewModel`, and from there passing the partial the `PenguinEnclosureVM`. – Topher Mar 14 '19 at 13:00
  • The partial view is now in the question above. DisplayFor will look for a partial view of the same name as the model passed to it. So the above partial view is Views\Home\DisplayTemplates\PenguinEnclosureVM.cshtml. – MrBlueSky Mar 14 '19 at 13:50
  • The DisplayFor will not look for a partial view; it will just load your Model... If you set a breakpoint in your partial view, you will not reach it in the first load; you reach it only when you click on the refresh button. – AMINCHAR Mar 14 '19 at 13:56
  • Now my question, what do you want to have in the both cases, PenguinEnclosure_IsEnoughPenguins or IsEnoughPenguins? – AMINCHAR Mar 14 '19 at 13:57
  • The partial view definitely does render when the page is first loaded - I've set my breakpoint and seen it hit . The control ID must be PenguinEnclosure_IsEnoughPenguins for model binding to work in the form post (not shown). I've clarified this in an edit to the question. Thanks. – MrBlueSky Mar 14 '19 at 14:17
  • You are right, it should load it, because you are using DisplayTemplates... Please se my response bellow – AMINCHAR Mar 14 '19 at 14:35

2 Answers2

1

An approach I have used is to set the "ViewData.TemplateInfo.HtmlFieldPrefix" property in the action that responds to your ajax call (UpdatePenguinEnclosure). This tells Razor to prefix your controls names and/or Ids.

You can choose whether to hard code the HtmlFieldPrefix, or pass it to the action in the ajax call. I tend to do the latter. For example, add a hidden input on the page with its value:

<input type="hidden" id="FieldPrefix" value="@ViewData.TemplateInfo.HtmlFieldPrefix" />

Access this in your ajax call:

            $.ajax({
                url: "/Home/UpdatePenguinEnclosure",
                type: "GET",
                data: { penguinFactor: 42, fieldPrefix: $("#FieldPrefix").val() }
            })

Then in your action:

public ActionResult UpdatePenguinEnclosure(int penguinFactor, string fieldPrefix)
{
    ViewData.TemplateInfo.HtmlFieldPrefix = fieldPrefix;
    return PartialView("DisplayTemplates/PenguinEnclosureVM", new PenguinEnclosureVM { PenguinCount = penguinFactor * 10 });
}
Phil
  • 125
  • 1
  • 7
0

Try this: Controller:

    public ActionResult UpdatePenguinEnclosure(int penguinFactor)
    {
        PenguinEnclosureVM pg = new PenguinEnclosureVM { PenguinCount = penguinFactor * 10 };
        return PartialView("DisplayTemplates/UpdatePenguinEnclosure", new ZooViewModel { PenguinEnclosure = pg });
    }

Your Partial:

@model PartialProblem.Models.ZooViewModel

<p>
    We have @Model.PenguinEnclosure.PenguinCount penguins
</p>
<p>

    @Html.LabelFor(m => m.PenguinEnclosure.IsEnoughPenguins)
    @Html.CheckBoxFor(m => m.PenguinEnclosure.IsEnoughPenguins)
</p>

I Think this will do the trick

AMINCHAR
  • 225
  • 1
  • 11
  • I think this might work, but it means the partial view can't be reused. The partial view is dependent on the type of the parent model - ZooViewModel. So it cannot be reused with a different parent. – MrBlueSky Mar 14 '19 at 15:06