46

This is a follow on from the following question:

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

There is a detailed description of the problem over there. However, I have now managed to narrow down the problem, that seems to be with the Html.EditorFor helpers, hence the new question.

The issue:

I post data to the server using $.ajax, then return the html of the partial view that holds the input controls. The problem is that, despite passing a newly created object to the Partial Views model, the various @Html.EditorFor and @Html.DropDownListFor helpers return the OLD DATA!.

I can prove that the model has correctly passed in a new object to the helpers, by printing the value out beside the Html helper. Ie:

@Html.EditorFor(model => model.Transaction.TransactionDate) 
@Model.Transaction.TransactionDate.ToString()

As the following image shows, the @Html.EditorFor is returning the wrong data:

Cached response...

[Note that the value beside the Comentario text box is a date time, because I was testing replacing the default values with a value that would change with each post, ie, a DateTime.]

If I replace the @Html.EditorFor for TransactionDate with a plain old @Html.TextBox():

@Html.TextBox("Transaction_TransactionDate", Model.Transaction.TransactionDate)

Then it renders the correct TransactionDate value for a new Transaction object, ie, DateTime.MinValue (01/01/0001...).

Therefore...

The problem is with the @Html.EditorFor helpers. The problem also happens with TextBoxFor and DropDownListFor.

The problem being that these helpers seem to cache the old value.

What am I doing wrong??!

EDIT:

I have just tried debugging in the custom Editor template for dates, and in there, ViewData.TemplateInfo.FormattedModelValue shows the correct value, ie, "01/01/0001". However, once it gets to Fiddler, the response is showing the old date, eg, "01/09/2011" in the image above.

As a result, I just think that there is some caching going on here, but I have none set up, so nothing makes any sense.

Community
  • 1
  • 1
awrigley
  • 13,481
  • 10
  • 83
  • 129
  • Note, this behavior is also occurring in MVC 4. – Eat at Joes Dec 04 '12 at 19:40
  • 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
  • This happens to me too on a 'RenderPartial' call with a different model that share property name, only for a DateTime value. Darin Dimitrov solution fixed it. I used Html.ViewData.ModelState.Remove("Date"); on the cshtml file right after rendering it. Thank you. – Chesare Oct 06 '22 at 20:04

5 Answers5

110

There is no caching involved here. It's just how HTML helper work. They first look at the ModelState when binding their values and then in the model. So if you intend to modify any of the POSTed values inside your controller action make sure you remove them from the model state first:

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

    return PartialView("_Create", viewModel);
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • I have to vote for your answer, since my updated answer is mainly a link to another recent answer of yours. – counsellorben Sep 16 '11 at 19:49
  • 1
    @Darin Dimitrov: Thanks, I am much enlightened, which is always the best outcome. I had got as far as realizing that the problem was with ModelState, but had been looking for errors, not realizing that it held the old values. What worked was ModelState.Clear();, so counsellorben gets an upvote. I couldn't get the remove bit to work. – awrigley Sep 17 '11 at 06:21
  • nice darin never knew this hmmmmmmm – davethecoder Sep 17 '11 at 08:00
  • 3
    Darin, but why it looks into ModelState? If I define model with values I set then I want typed view to use model I pass and only it. Why did whey do it? What is the reason? Now I always have to do this ModelState.Clear(); return View(model); It is a security issue. If I did not clear ModelState hacker can modify my view through GET params. – Cherven Jun 05 '12 at 16:12
  • That's by design. The reason is to persist information through the postback. A hacker cannot modify anything through GET parameters if you use a correct view model as input action parameter. Only properties of the input view model would modify the view state. – Darin Dimitrov Jun 07 '12 at 17:22
  • @DarinDimitrov Thanks for you answer, this issue was really driving me mad! when I us Model.Property value is correct, when I use x => x.Property, value is old. However I am uncomfortable with the extra code I need to add to fix this issue, could you please explain more the reasons behind this design or guide me to some resources that explains the issue more, may be I can modify my code to overcome this behavior. – Sisyphus Apr 07 '18 at 11:02
  • Thank god for SO. I would have wasted ...hours... if I hadn't found this. – Martin Hansen Lennox Jan 17 '19 at 00:30
  • Thank you for this answer! I have been pulling my hair out for a whole day trying to figure this out. Assumed it was caching it somewhere, I was wrong – Ben D Aug 12 '20 at 08:08
24

Even if you do not specify caching, it sometimes can occur. For my controllers which handle AJAX and JSON requests, I decorate them as follows:

[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]

This specifically declares no caching should occur.

UPDATE

Based on an answer Darin Dimitrov gave here, try adding the following line to your controller action:

ModelState.Clear();
Community
  • 1
  • 1
counsellorben
  • 10,924
  • 3
  • 40
  • 38
1

This was unexpected behavior for me, and although I understand the reason why it's necessary to give ModelState precedence, I needed a way to remove that entry so that the value from Model is used instead.

Here are a couple methods I came up with to assist with this. The RemoveStateFor method will take a ModelStateDictionary, a Model, and an expression for the desired property, and remove it.

HiddenForModel can be used in your View to create a hidden input field using only the value from the Model, by first removing its ModelState entry. (This could easily be expanded for the other helper extension methods).

/// <summary>
/// Returns a hidden input field for the specified property. The corresponding value will first be removed from
/// the ModelState to ensure that the current Model value is shown.
/// </summary>
public static MvcHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> helper,
    Expression<Func<TModel, TProperty>> expression)
{
    RemoveStateFor(helper.ViewData.ModelState, helper.ViewData.Model, expression);
    return helper.HiddenFor(expression);
}

/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model. Call this when changing
/// Model values on the server after a postback, to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this ModelStateDictionary modelState, TModel model,
    Expression<Func<TModel, TProperty>> expression)
{
    var key = ExpressionHelper.GetExpressionText(expression);

    modelState.Remove(key);
}

Call from a controller like this:

ModelState.RemoveStateFor(model, m => m.MySubProperty.MySubValue);

or from a view like this:

@Html.HiddenForModel(m => m.MySubProperty.MySubValue)

It uses System.Web.Mvc.ExpressionHelper to get the name of the ModelState property. This is especially useful when you have "Nested" models since the key name isn't obvious.

Tobias J
  • 19,813
  • 8
  • 81
  • 66
1

i have never seen this but basically if you are using ajax to request this data, you need to set nochache: i am assuming you using jQuery.ajax here so will show the code:

$.ajax({
    url: "somecontroller/someAction,
    cache: false, // this is key to make sure JQUERY does not cache your request
    success: function( data ) {  
         alert( data );
    }
});

just a stab in the dark, i assume you have probably already covered this already. have you tried to create a new model first and then populate that new instance of the model with your data, and then send this to your view!

Finally not sure what DB server your using but have you check to see that DB results are not cached and that you are not just requesting SQL results from the DB cache... i dont use MsSQL but i hear that it has outputCaching until something is change on the DB server itself?? anyway just a few thoughts

davethecoder
  • 3,856
  • 4
  • 35
  • 66
0

Make sure you're not doing this:

@Html.EditorFor(model => model.Transaction.TransactionDate.Date)

I did this, and the model never got the value back. It worked perfectly once I remove the .Date.

famousgarkin
  • 13,687
  • 5
  • 58
  • 74