-1

I'm working on refactoring a MVC project to clean up the controllers by using action filters to perform PRG (Post-Redirect-Get) when ModelState is not valid, and storing ModelState in TempData during the redirect.

I have a view model that looks something like this:

public class GroupMember
{
  public string UserId { get; set; }
  public bool SendNotifications { get; set; }
}

public class Group
{
  public int Id { get; set; }
  public string Name { get; set; }
  // ...
  public List<GroupMember> Members { get; set; }
}

Controller actions:

[HttpGet]
[ImportModelState]
public ActionResult Create()
{
  var model = new Group();
  return View(model);
}

[HttpPost]
[ValidateAntiForgeryToken]
[ValidateModelState]
public ActionResult Create(
  [Bind(Exclude = "Id")]
  Group model)
{
  // call service to save
  // ...
}

The problem I'm having is with getting the collection Group.Members to display correctly after a redirect. ModelState is loaded 100% correctly, and works fine for the properties like Group.Name, where the called to Html.EditorFor(...) loads the data from ModelState instead of using the (empty) model.

I have an editor template for GroupMember (which works fine in all but the redirect scenario) and am displaying the collection using Html.EditorFor(model => model.Members), but apparently MVC does not check ModelState in this case, so it always displays an empty list.

Is there a clean way to fix this? I think I should be able to store the number of members in a hidden field and check by hand to see if it has a value in ModelState, but that seems like a clumsy workaround.

Edit: The workaround I've come up with is to add a property NumMembers to Group, and insert the following into the Razor view:

@{
  ModelState state;
  var numMembers = ViewData.ModelState.TryGetValue("NumMembers", out state)
    ? int.Parse(state.Value.AttemptedValue)
    : Model.Members.Count;

  if (numMembers != Model.Members.Count)
  {
    Model.Members = Enumerable.Repeat(new GroupMember(), numMembers).ToList();
  }
}
@Html.HiddenFor(model => model.NumMembers)
@Html.EditorFor(model => model.Members)

My javascript code which fixes the item indices for proper model binding updates the NumMembers hidden field before a post. Still hoping for a cleaner solution.

Merad
  • 749
  • 1
  • 6
  • 14
  • are you using it correctly? http://cpratt.co/html-editorfor-and-htmlattributes/ http://www.pearson-and-steel.co.uk/how-to-use-the-html-editorfor-method/ – MaxOvrdrv Dec 22 '15 at 17:48
  • @MaxOvrdrv those articles have literally nothing to do with my question – Merad Dec 22 '15 at 17:58
  • they both do: just making sure you are using the correct helper here... a LOT of people are not, and it's causing a LOT of problems. Here's a perfect example of that which seems, to me, to be related to your problem. http://stackoverflow.com/questions/7414351/mvc-3-html-editorfor-seems-to-cache-old-values-after-ajax-call – MaxOvrdrv Dec 22 '15 at 18:05
  • That's rather the opposite of my problem. I *want* the "cached" values from model state, and they work fine on everything except the collection – Merad Dec 22 '15 at 18:25
  • then you don't have a choice but to use either hidden fields or some other method to properly persist the data across... it is indeed a bit of a clumsy workaround, and a cleaner version is to cache information at the service level or through some other means, but the most "state-driven" and "efficient" workaround is through hidden fields. I also wouldn't rely on the other parts that you are saying are working correctly to do so in every situation... tomorrow, those could stop working as well. – MaxOvrdrv Dec 22 '15 at 18:32
  • I'm not sure that you understand the question. Using TempData to persist ModelState during PRG has been around about as long as MVC, it isn't some hack I just made up. The data is all there, the problem is getting it to display properly. – Merad Dec 22 '15 at 20:00
  • Using `TempData` to try and follow a RPG pattern when `ModelState` is invalid is a hack (and refreshing the browser will cause your app to fail). MVC is designed around returning the view when `ModelState` is invalid (not redirecting). If your collection is empty then `EditorFor()` will not attempt to generate any html so the code that checks `ModelState` for the `AttemptedValue` is never even hit. –  Jan 17 '16 at 08:47
  • @StephenMuecke, I suppose you're entitled to your opinion that it's a hack... but it's been recommended as a MVC 'best practice' for years. The rest of your comment is flat-out incorrect: refreshing the page does not not cause a failure of any kind, and the workaround code I added to the OP is ugly but has been working fine since I made that edit. – Merad Jan 29 '16 at 20:20
  • Are you serious. `TempData` only lasts one request (unless you use `.Peek()` or `.Keep()`) so refreshing the browser means its no longer available. And where on earth did you get the idea that _but it's been recommended as a MVC 'best practice' for years_ –  Jan 29 '16 at 23:53

1 Answers1

0

There are no simple workarounds. You should use the hidden field.

MaxOvrdrv
  • 1,780
  • 17
  • 32