2

There are already several good discussions about how model properties set in the form post controller won't show up in view because of model state. To take the discussion a step further I have an example where items in the VIEW are updated but the model is not, again due to Model state. That's right, what your user sees will NOT match the model properties that are returned in the post!

say you have a model with a list

public class FruitBasket {
    public string OwnersName { get; set; }
    public IEnumerable<Fruit> Fruits { get; set; }
    public string FruitColor { get; set; }
    public FruitBasket() {
        this.OwnersName = "Bob";
        this.FruitColor = "Red";
    }
}
public class Fruit {
    public string Name { get; set; }
    public string Color { get; set; }
}

And a data access class has methods that retrieves a list of fruits based on color

Data Access code

public IEnumerable<Fruit> GetFruits(string color) {
    if (color == "Red") {
        List<Fruit> fruits = new List<Fruit> {
            new Fruit { Name = "Apple", Color = "Red" },
            new Fruit { Name = "Grape", Color = "Red" }
        };
        return fruits;
    } else {
        List<Fruit> fruits = new List<Fruit> {
            new Fruit { Name = "Banana", Color = "Yellow"},
            new Fruit { Name = "Apple", Color = "Red" },
            new Fruit { Name = "Grape", Color = "Red" }
        };
        return fruits;
    }
}

Controller (where db is the data access class)

public ActionResult FruitBasket() {
    FruitBasket basket = new FruitBasket();
    basket.Fruits = db.GetFruits("Red");
    return View(basket);
}
[HttpPost]
public ActionResult FruitBasket(FruitBasket basket) {
    basket.Fruits = db.GetFruits("All");
    return View(basket);
}

View

@model GEWeb.Models.FruitBasket
<!DOCTYPE html>
<html>
<head>
    <title>FruitBasket</title>
</head>
<body>
@using (Html.BeginForm()) {
    <p>@Html.DisplayFor(model => Model.OwnersName)</p>
    <table>
        @Html.EditorFor(model => model.Fruits)
    </table>
    <input type="submit" value="filter" />
}
</body>
</html>

Editor Template (the template for editorfor is in views/shared/EditorTemplates) the hiddenfor helpers are included so that the model will be populated with list data on post back

@model GEWeb.Models.Fruit
<tr>
    <td>
        @Html.DisplayFor(model => model.Name)
        @Html.HiddenFor(model => model.Name)
    </td>
    <td>
        @Html.DisplayFor(model => model.Color)
        @Html.HiddenFor(model => model.Color)
    </td>
    <td>
        @Html.CheckBoxFor(model => model.Remove)
    </td>
</tr>

Scenario - The user requests the page and sees the list of red fruits. - The user clicks the filter button, the post controller loads all fruits, the user sees a list of all three fruits - The user clicks the filter button a second time. Now inspect the basket properties as passed to the post controller, the list will not match what the user saw.

Now imagine that you wanted to use that checkbox to select fruits to remove from the basket. You could pass the basket.fruits model to a method in your data access class which would delete the fruits and then you could have your conroller reload the basket.fruits list. But it will not work because the list returned to the controller does not match what the user is seeing.

This "feature" can be subverted by putting the following into the Post controller

ModelState.Clear();

But realy, I shouldn't have to. Views and controllers should pass the model back and forth accurately. ModelState is an attempt to persist View properties between page loads and that doesn't seem to be in keeping with the MVC phylosophy where state is managed by the database, and not the view!

4

0 回答 0