My Post call does not return the correct Model type. It always use the baseObject instead of the correct derived object that I passed in from the Get
RestaurantViewModel.cs
public class RestaurantViewModel{
public Food BaseFoodObject{get;set;}
}
Food.cs
public class Food{
public string Price{get;set;)
}
Bread.cs -- Inherit from Food
public class Bread:Food{
public int Unit{get;set;}
}
Milk.cs -- Inherit from Food
public class Milk:Food{
public string Brand{get;set}
}
Editor For Template for Bread. Display the unit and allow user to edit
Index.html
@Model RestaurantViewModel
@using(Html.BeginForm("SaveFood", "Food"))
{
@Html.EditorFor(m=>m.BaseFoodObject)
<input type="submit" value="Process"/>
}
Bread.cshtml
@Model Bread
<div>
@Html.TextboxFor(bread=>bread.Unit)
</div>
FoodController.cs
public ActionResult Index(){
Bread bread = new Bread(){
Price = "$10",
Unit = 1
}
RestaurantViewModel viewModel = new RestaurantViewModel(){
BaseFoodObject = bread
}
return View(viewModel);
}
public ActionResult Post(RestaurantViewModel viewModelPost)
{
// When I inspect the viewModelPost, there is no attribute for unit
}
Final Result: 1. The display looks correct. EditorFor is smart enough to pick the correct editor template and display the value correctly 2. The Save does not work. The Unit attribute of Bread Object does not get passed in with the RestaurantViewModel. The reason for that is the RestaurantViewModel used the Food object instead of Bread
I hope there is away to modify the EditorFor and tell it to use the Model in the View or the Object Type that I passed in when I display it.
Thanks
Update 1: I solved this problem by using the custom binder and using a factory to decide which object I really want. This helps construct the correct Model which I want
MVC is stateless. A couple of references.
There's a couple of statements in your question that conflict with this, and how MVC binding works eg:
Possibly just terminology, but your Post call does not 'return a model type' - it goes into the model that's defined in the post action, in this case
RestaurantViewModel
.because it is stateless, it knows nothing about the model you passed in from the
get
... absolutely nothing.The final html rendered via the getaction+view.cshtml+model is not linked to the postaction. You could just as easily take the rendered html, save it, reboot your PC, reload the rendered html and it will work exactly the same way.
When you use
EditorFor
it sets anID
andname
attribute based on the model it was bound to, so it already does this, but perhaps you are not binding to the model you want to bind to to get the correctid
.So, to the question, if, in 'normal' C# code you were to instantiate a new instance of
RestaurantViewModel
, what would you expect the type of BaseFoodObject to be?This is what the ModelBinder is doing - it's creating a new
RestaurantViewModel
.As your post action method's signature does not include anything to do with
Bread
- all the bread properties are ignored.Some options:
Check for the food properties after binding and read them manually (probably the quickest+easiest but not very "mvc-ish")
to make this easier, you could provide a hidden field with the type
Add bread to the action so it's bound
but then, obviously, it won't work for milk.
So could extend this using an
ActionNameSelector
to select the correct action(related link but not a solution to this specific case)
Another option might be to change the Restaurant type to a generic, but would require a few more changes (and ideally use of interfaces), and more details (provided here as an idea, rather than a solution)
The basics would be:
but I've not confirmed if the default ModelBinder would work in this case.
The problem comes with the post. Once you post, all you have is a set of posted data and a parameter of type,
RestaurantViewModel
. The modelbinder sets all the appropriate fields onFood
because that's all it knows. Everything else is discarded. There's nothing that can be done about this. If you need to post fields related toBread
then the type of your property must beBread
. That's the only way it will work.