Search code examples
asp.net-corepartial-viewsasp.net-core-3.0mvc-editor-templates

EditorTemplate asp-for="Property" vs value="@Model.Property"


I am using .NET Core 3.0 to build a web application which contains an EditorTemplate for a list of values. This EditorTemplate is inside a PartialView. A PartialView is returned from my ActionMethod whenever I add or remove a record from the list.

The confusion is, whenever I remove a record from top/middle of the list, the EditorTemplate is messed up. Please check the images below.

Question 1: Why is the happening and how to resolve it?

Question 2: Why is value different when I use <input type="text" asp-for="FilterId" class="form-control" /> & <input type="text" value="@Model.FilterId" />

Before delete EditorTemplate before delete

After delete EditorTemplate after delete

AdvancedFilterVM EditorTemplate

@model SSAQ.Web.ViewModels.AdvancedFilterVM

<tr>
    <td>
        <input type="text" asp-for="FilterId" class="form-control" />
    </td>
    <td>
        @Html.TextBoxFor(m => m.FilterId, new { @class="form-control" })
    </td>
    <td>
        <input type="text" value="@Model.FilterId" class="form-control" />
    </td>
    <td style="text-align:center;vertical-align:middle;">
        <a href="#" class="deleteFilter" data-filterid="@Model.FilterId"><i style="font-size:20px;" class="fa fa-times"></i></a>
    </td>
</tr>

_SurveyFilter PartialView

@model SSAQ.Web.ViewModels.SurveySubmissionViewModel
@if (Model.AdvancedFilter?.Any() == true)
{
<table class="table" id="tblFilter">
    <thead>
        <tr>
            <th>Using asp-for=FilterId</th>
            <th>Using @@Html.TextBoxFor(m => m.FilterId)</th>
            <th>Using value=@@Model.FilterId</th>
            <th>&nbsp;</th>
        </tr>
    </thead>
    <tbody id="tblFilterSurvey">
        @Html.EditorFor(m => m.AdvancedFilter)
    </tbody>
</table>
}

JQuery to delete row

$('body').on('click', '.deleteFilter', function (e) {
    e.preventDefault();
    var filterId = $(this).data('filterid');
    var formData = $("#formFilter").serializeArray();
    formData.push({ name: 'filterId', value: filterId });
    $.ajax({
        type: 'POST',
        context: this,
        url: '@Url.Content("~/Survey/DeleteFilterRow")',
        data: formData,
        success: function (response) {
            $('#filterTableDiv').html(response);
        }
    });
});

DeleteFilterRow action method

public IActionResult DeleteFilterRow(SurveySubmissionViewModel surveySubmissionVM, Guid filterIdToRemove)
{
    surveySubmissionVM.AdvancedFilter.Remove(surveySubmissionVM.AdvancedFilter.Single(m => m.FilterId == filterIdToRemove));
    return PartialView("_SurveyFilter", surveySubmissionVM);
}

Solution

  • I could reproduce the same issue. I thought it was caused by cache. But it is not. It really refreshes my knowledge: EditorFor renders the template using ModelState prior to Model.

    Quick fix:

    Clear the ModelState before rendering:

    ModelState.Clear();
    return PartialView("_SurveyFilter", surveySubmissionVM);
    

    Further tech info:

    Quoted from MSDN Blog:

    ASP.NET MVC assumes ... Therefore, the Html Helpers actually check in ModelState for the value to display in a field before they look in the Model. This enables them to redisplay erroneous data that was entered by the user, and a matching error message if needed.

    There're also some threads online talking about this behavior online:

    1. https://forums.asp.net/t/1527149.aspx?A+Bug+EditorFor+and+DisplayFor+don+t+display+same+value+EditorFor+out+of+date
    2. How does Html.EditorFor process changes to the model
    3. https://blogs.msdn.microsoft.com/simonince/2010/05/05/asp-net-mvcs-html-helpers-render-the-wrong-value/
    4. https://exceptionnotfound.net/modelstate-values-override-model-values-in-htmlhelpers/

    (Although they're all about ASP.NET, the theory applies for ASP.NET Core too)