8

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);
}
awrigley
  • 13,481
  • 10
  • 83
  • 129
  • Possible duplicate of [ASP.Net MVC Html.HiddenFor with wrong value](http://stackoverflow.com/questions/4710447/asp-net-mvc-html-hiddenfor-with-wrong-value) – KyleMit Nov 23 '16 at 21:11

2 Answers2

17

@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.

awrigley
  • 13,481
  • 10
  • 83
  • 129
  • 2
    +1 Wow, this bit me too, and I thought I had no chance of figuring it out. @Model.Id printed out the proper value, but @Html.TextBoxFor(model => model.Id) printed out the last posted value. – Matt Hamsmith Jan 27 '12 at 16:25
0

Try explicitly setting the outputcache duration to 0 on your controller action.

I think the browser isn't supposed to cache POSTs but it seems to still do that sometimes.

BNL
  • 7,085
  • 4
  • 27
  • 32
  • I have tested with Fiddler, and can see that the caching has happened on the server, not the browser. For the hell of it, I put the outputcache attribute on the Action, but as expected no difference. – awrigley Sep 13 '11 at 15:39
  • What if you append a random value to the action? Does it still cache? I assume you looked at the requests / responses in fiddler to verify that the correct request is being sent and that the response data is incorrect (ie its not a problem updating the dom). – BNL Sep 13 '11 at 15:47
  • Yes, checked that. Have used a datestamp. I even added the datestamp to the Transaction.Comment field, and printed that out. The new values get printed out, but the values passed using Html.EditorFor and Html.DropDownListFor still show the old values. – awrigley Sep 13 '11 at 16:11
  • if you read the question, you will see that I am printing out the values of the Model. They update OK, it is the values generated by Html.EditorFor and Html.DropDownListFor that are misbehaving... – awrigley Sep 13 '11 at 16:13