Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 3 + $.ajax - response seems to be caching output from partial view

I must be missing something, silly, but here is the problem.

I have a Create action on the Transactions controller. The Create.cshtml uses jQuery to post the form to the server using a call to $.ajax. Debugging shows that everything arrives on the server as expected. I use the form data to update a record: this works fine too. I then return a partial view, passing a model to the view with default data. I can debug and verify that the model is passing nulls and 0s, ie, the default data for my model.

Problem is, what gets sent back to the browser in the response is the old data...!

I can see no reason why. Hopefully, you can...

Note: I am not using any form of output cache.

EDIT 1:

The caching is not happening in the browser. The reason I say that is that I can see in Firebug the response of the call to the AjaxCreate Action. I can also see this in Fiddler.

EDIT 2:

If you look at the code for the Partial View, you will see that each dropdownlist or textbox has the value of @Model.Transaction.[Property] printed out beside it. This, bizarrely, shows the correct value, ie, the defaults for my Transaction object, but the dropdownlists and text boxes stick with the values that were posted to the server rather than the default values for the property each one is supposed to render.

EDIT 3:

I have included the following image, so you can see the values printed to the right of each control that are being passed in. And yet the controls reflect the old data posted to the server in the previous $.ajax call. (The comment shows a date time at the moment of creating the view model, that way I could see things updating).

EDIT 4:

I have found that replacing @Html.EditorFor(...) (see view code below) with @Html.TextBox helpers removes the problem. So, what seems to be happening is that the EditorFor helpers are causing the problem. Why? I have no idea, but will post another, more specific question.

Caching Problem

Code and markup as follows:

jQuery:

$(document).ready(function () {

    $('input[name="nextRecord"]').live('click', function () {
        var theForm = $(this).closest('form');
        if ((theForm).valid()) {
            var buttonText = $(this).val();
            var action = "/input/Transactions/AjaxCreate/";
            if (buttonText === "Reset") {
                clearForm(theForm);
            }
            else {
                var targetElement = $('#CreateDiv');
                var _data = theForm.serialize() + '&nextRecord=' + $(this).val();

                $.ajax({
                    url: action,
                    data: _data,
                    cache: 'false',
                    type: 'POST',
                    dataType: 'html',
                    success: function (html) {
                        $(targetElement).html(html);
                        createDatePickers(targetElement);
                        jQuery.validator.unobtrusive.parse(targetElement);
                    }
                });
            }
        }
        return false;
    });
});

Partial View:

@model FlatAdmin.Domain.ViewModels.TransactionViewModel

@* This partial view defines form fields that will appear when creating and editing entities *@

<div class="editor-label">
    Fecha
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Transaction.TransactionDate, new { @class = "date-picker" })
    @Html.ValidationMessageFor(model => model.Transaction.TransactionDate) @Model.Transaction.TransactionDate.ToString()
</div>

<div class="editor-label">
    Origen:
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Transaction.IdFrom, ((IEnumerable<FlatAdmin.Domain.Entities.Account>)Model.FromAccounts).Select(option => new SelectListItem
{
        Text = (option == null ? "None" : option.AccountName), 
        Value = option.AccountId.ToString(),
        Selected = (Model != null) && (option.AccountId == Model.Transaction.IdFrom)
    }), "Choose...")
    @Html.ValidationMessageFor(model => model.Transaction.IdFrom)@Model.Transaction.IdFrom
</div>

<div class="editor-label">
    Destino:
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Transaction.IdTo, ((IEnumerable<FlatAdmin.Domain.Entities.Account>)Model.ToAccounts).Select(option => new SelectListItem
{
    Text = (option == null ? "None" : option.AccountName),
    Value = option.AccountId.ToString(),
    Selected = (Model != null) && (option.AccountId == Model.Transaction.IdTo)
}), "Choose...")
    @Html.ValidationMessageFor(model => model.Transaction.IdTo)@Model.Transaction.IdTo
</div>
<div class="editor-label">
    Monto
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Transaction.IdCurrency, ((IEnumerable<FlatAdmin.Domain.Entities.Currency>)Model.AllCurrencies).Select(option => new SelectListItem
{
    Text = (option == null ? "None" : option.CurrencyName),
    Value = option.CurrencyId.ToString(),
    Selected = (Model != null) && (option.CurrencyId == Model.Transaction.IdCurrency)
})) 
    @Html.EditorFor(model => model.Transaction.Amount)
    @Html.ValidationMessageFor(model => model.Transaction.Amount) @Model.Transaction.Amount
</div>

<div class="editor-label">
    Comentario
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Transaction.Comment)
    @Html.ValidationMessageFor(model => model.Transaction.Comment)@Model.Transaction.Comment
</div>

View:

@model FlatAdmin.Domain.ViewModels.TransactionViewModel
@using FlatAdmin.Domain.Entities

@{
    ViewBag.Title = "Nueva Transaccion";
}

<h2>@ViewBag.Title</h2>
<div>
    @Html.ActionLink("<< Lista de Transacciones", "Index")
</div>
<br />

<div id="InputPanel">
    @using (Html.BeginForm()) {
        @Html.ValidationSummary(true)
        <fieldset>
            <legend>Elegir Actividad</legend>
            <div class="editor-field">
                @Html.DropDownListFor(model => model.Transaction.IdCostCentre, ((IEnumerable<FlatAdmin.Domain.Entities.CostCentre>)Model.AllCostCentres).Select(option => new SelectListItem
           {
               Text = (option == null ? "None" : option.Name),
               Value = option.CostCentreId.ToString(),
               Selected = (Model != null) && (option.CostCentreId == Model.Transaction.IdFrom)
           }), "Actividades...")
            </div>
        </fieldset>
        <fieldset>
            <legend>Transaccion</legend>
            <div id="CreateDiv">
                @Html.Partial("_Create", Model)
            </div>
            <p>
                <input type="submit" name="nextRecord" value="Proxima Transaccion >>" />
            </p>
            <p>
                ...o sino, para guardar y volver a la lista de transacciones:<br /><input type="submit" value="Guardar" />
            </p>

        </fieldset>
    }
</div>

Controller Action:

[HttpPost]
public virtual ActionResult AjaxCreate(Transaction transaction)
{
    if (ModelState.IsValid)
    {
        service.InsertOrUpdate(transaction);
        service.Save();
    }
    service.ChosenCostCentreId = transaction.IdCostCentre;
    TransactionViewModel viewModel = new TransactionViewModel();
    viewModel.Transaction =  new Transaction();
    viewModel.CostCentre = service.ChosenCostCentre;
    viewModel.AllCostCentres = service.AllCostCentres;
    viewModel.AllCurrencies = service.AllCurrencies;
    viewModel.FromAccounts = service.FromAccounts;
    viewModel.ToAccounts = service.ToAccounts;

    return PartialView("_Create", viewModel);
}
like image 282
awrigley Avatar asked Sep 13 '11 15:09

awrigley


1 Answers

@Darin Dimitrov came up with the answer in a related thread.

Essentially, the HtmlHelpers such as Html.EditorFor, Html.TextBoxFor, etc, check first in the ModelState for existing values, and ONLY then in the Model.

As a result, I needed a call to:

ModelState.Clear();

Ignorance is so painful.

like image 107
awrigley Avatar answered Nov 13 '22 07:11

awrigley