MVC - Inherited view model members [duplicate] - asp.net-mvc

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:
My Post call does not return the correct Model type.
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.
instead of the correct derived object that I passed in from the Get
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.
a way 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
When you use EditorFor it sets an ID and name 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 correct id.
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")
public ActionResult Post(RestaurantViewModel viewModelPost)
{
if (!string.IsNullOrEmpty(Request.Form["Unit"]))
// it's a bread form
to make this easier, you could provide a hidden field with the type
if (Request.Form["Type"] == typeof(Bread).Name)
{
var bread = new Bread { Unit = Request.Form["Unit"] }
Add bread to the action so it's bound
public ActionResult Post(RestaurantViewModel viewModelPost, Bread bread)
but then, obviously, it won't work for milk.
So could extend this using an ActionNameSelector to select the correct action
public ActionResult PostBread(RestaurantViewModel viewModelPost, Bread bread)
public ActionResult PostMilk(RestaurantViewModel viewModelPost, Milk milk)
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class FoodSelectorAttribute : ActionNameSelectorAttribute
{
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
... check if provided parameters contains bread/milk
(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:
public class RestaurantViewModel<T>
where T: Food
{
}
public ActionResult Post(RestaurantViewModel<Bread> viewModelPost)
public ActionResult Post(RestaurantViewModel<Milk> viewModelPost)
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 on Food 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 to Bread then the type of your property must be Bread. That's the only way it will work.

Related

ModelBinding a generic type in MVC3

In my views I'm using a generic type for the Model, ItemModel<T>.
This allows me to have a basetype on my model and it works fine. Within ItemModel<T> I attach the actual entity of T to a property called 'Item'.
Let's say I'm loading a User item: in my view I would like to do something like this:
<%: Html.TextBoxFor(Model => Model.Item.NickName,
new { id="NickName", name="NickName" })%>
Because the entity is added via the Item property, this generates the input tag's name attribute as 'Item.NickName'. When posting, MVC can no longer relate this to the User.NickName property of the user object in my controller
public ActionResult Login(User user, string redirectUrl)
and therefore nothing gets loaded into my User object. Is there a way to fix this? I've been looking into writing a custom bindingmodel or valueprovider, but that looks like alot of work for something this simple. Plus, I'm really not sure if that's that way to go.
All help is greatly appreciated!
change method of your actionresult from
public ActionResult Login(User user, string redirectUrl)
To
public ActionResult Login(User Item, string redirectUrl)
this way modelbiner will be able to locate properties of User object prefixed with Item
you can create your helper methods to create that textbox
public static MVCString MyTextBox<T>(this HtmlHelper html,T _item,String PropertyName)
{
TagBuilder myTag = new TagBuilder("input");
myTag.Attributes("type")="Text";
myTag.Attributes("id")=PropertyName;
myTag.Attributes("name")=PropertyName;
myTag.Attributes("value")=typeof(_item).GetProperty(PropertyName).GetValue(_item,null).toString();
Return MvcHtmlString.Create(selectTag.ToString())
}

Postback values getting lost!

I have a controller with typical create methods (one for GET, one for POST). the POST takes a strongly-typed parameter:
[HttpPost] public ActionResult Create(Quiz entity)
however, when the callback is made, the properties of my entity are null... if I redefine it like this:
[HttpPost] public ActionResult Create(Quiz entity, FormCollection form)
I can see that the values are there e.g. form["Component"] contains "1". I've not had this problem in the past and I can't figure out why this class would be different.
thoughts anyone?
The easiest way to get the default model binder to instantiate Quiz for you on postback is to use the Html form helpers in you view. So, for example, if your Quiz class looked like this:
public class Quiz
{
public int Id { get; set; }
public string Name { get; set; }
}
The following code in your view would ensure the values are present on postback:
#Html.HiddenFor(mod => mod.Id)
#Html.TextBoxFor(mod => mod.Name)
Keep in mind that values which need to be posted back but not shown in the view (like identifiers) need to be added to the view with Html.HiddenFor.
Here's a more comprehensive list of Html form helper functions.
I FIGURED IT OUT!!
so, in my model (see comments on #ataddeini's thread below) you can see I have a Component... to represent components I used a couple of listboxes, the second (Components) dependent on the contents of the first (Products). In generating the second list I used
#Html.DropDownListFor(x => x.Component, ...)
which (as shown in one of the above links) generates a form field called "Component"... and therein lies the problem. What I needed to have done is bind it to the the Id of the component instead!
#Html.DropDowListFor(x => x.Component.Id, ...)
hurray!

mvc.net how to edit member list variables

Given the following model which has a name, url, and an arbitrary list of keywords (I want the user to add a series of keywords) ...
public class Picture
{
public Picture()
{
keywords = new List<string>();
}
public string name {get;set:}
public string url {get;set;}
public List<string> keywords{get;set;}
}
... and the following action in my controller ...
[HttpPost]
public ActionResult Edit(FormCollection fc)
{
if (ModelState.IsValid)
{
// do stuff
}
return View(ModelManager.Picture);
}
In the FormCollection I have the following field
fc["keywords"] = "keyword1,keyword2,keyword3"
And I then create a Picture object based on the form collection.
However, I would prefer to use a strongly-typed action such as
[HttpPost]
public ActionResult Edit(Picture p)
But in this approach, my p.keywords property is always empty. Is there some way to help the framework recreate my p.keywords property before it hits my controller's action method?
I thought an Editor Template might work here, but I don't think there is a way to model bind a nested IEnumerable view model member. Your fastest bet may be handling it directly with FormCollection and some string parsing magic. Otherwise, if you have to strongly-type this, maybe a custom model binder like this could help if you can control your keyword element id's:
public class PictureKeywordBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext,
string modelName, Type modelType,
ModelStateDictionary modelState)
{
Picture picture = new Picture();
//set name, url, other paramaters here
foreach(var item in Request.Form.Keys)
{
if (item.StartsWith("keyword"))
{
picture.keywords.Add(Request.Form[item]);
}
}
//add any errors to model here
return picture;
}
}
Maybe the keyword id's could be setup in a partial view passed the sub model from your parent view:
<% Html.RenderPartial("PictureKeywords", Model.keywords);
Are your keywords seperate text boxes? If so, create an inputs like this and they will be populated by the model binder.
<input name="keywords[0]" type="text">
<input name="keywords[1]" type="text">
<input name="keywords[2]" type="text">
The way I got around this, is to use a hidden input to store the csv string of items, in your case, keywords.
I then hooked into the form submit event (using jQuery) and appended the inputs to form the csv string, which is then stored in the hidden input. This hidden input was strongly typed to a property on my model.
It's a little clunky, but if you have a dynamic number of possible keywords then this works quite well (except if JS is disabled of course)
In what way you are expecting the user to add more keywords? In the form comma separated values(CSV) or by dynamically adding textboxes?
Based on your requirement, i have two solutions with me.

My custom ASP.NET MVC entity binding: is it a good solution?

Suppose I want to allow to select our entity (from a dropdown, etc) on a page, let's say Product. As a result I may receive this:
public ActionResult SelectedAction(Guid productId)
{
}
But, I want to use model binders power, so instead I write model binder to get my product from repository and instead use
public ActionResult SelectedAction(Product product)
{
if (ModelState.IsValid) {} else {}
}
My model binder will set model state to false if product is invalid.
Now, there're problems with this approach:
It's not always easy to use strongly-typed methods like Html.ActionLink(c => c.SelectedAction(id)) since we need to pass Product, not id.
It's not good to use entities as controller parameters, anyway.
If model state is invalid, and I want to redirect back and show error, I can't preserve selected product! Because bound product is not set and my id is not there. I'd like to do RedirectToAction(c => c.Redisplay(product)) but of course this is not possible.
Now, seems like I'm back to use "Guid productId" as parameter... However, there's one solution that I'd like to present and discuss.
public class EntityViewModel<T> where T : BaseEntity
{
public EntityViewModel(Guid id)
{
this.Id = id;
}
public static implicit operator EntityViewModel<T>(T entity)
{
return new EntityViewModel<T>(entity.Id);
}
public override string ToString()
{
return Id.ToString();
}
public Guid Id { get; set; }
public T Instance { get; set; }
}
Now, if I use
public ActionResult SelectedAction(EntityViewModel<Product> product)
{
if (ModelState.IsValid) {} else {}
}
all the problems are solved:
I can pass EntityViewModel with only Id set if I have only Id.
I don't use entity as parameter. Moreover, I
can use EntityViewModel as property inside another ViewModel.
I can pass EntityViewModel back to RedirectToController and it will keep its Id value, which will be
redisplayed to user along with the validation messages (thanks to MVCContrib and ModelStateToTempData / PassParametersDuringRedirect).
The model binder will get Instance from the repository and will set model state errors like "Not found in database" and so on. And I can use things like ActionLink(c => c.Action(Model.MyProductViewModelProperty)).
The question is, are there any drawbacks here? I can't see anything bad but I'm still new to MVC and may miss some important things. Maybe there're better and approved ways? Maybe this is why everybody uses entity IDs as input parameters and properties?
Overall that looks like a good appoach to me...
As an alternative, you could use POCO for your viewmodel then I think all 3 problems would be solved automatically. Have you seen the Automapper project that allows an Entity to DTO approach? This would give you more flexibility by separating you ViewModel from your EntityModel, but really depends on the complexity of you application you are building.
MVC's ViewDataExtensions might also be useful instead of creating custom containers to hold various viewmodel objects as you mention in number 2.
MVCContrib's ModelStateToTempData should work for any serializable object (must be serializable for any out of process sessionstate providers eg. SQL, Velocity etc.), so you could use that even without wrapping your entity classes couldn't you?

MVC Custom ViewModel and auto binding

I have a custom ViewModel defined as :
public class SampleFormViewModel
{
public SampleFormViewModel(SelectList companies, Widget widget)
{
Companies = companies;
Widget = widget;
}
public SelectList Companies { get; private set; }
public Widget Widget { get; private set; }
}
In my Edit POST handler I have the following entry:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(SampleFormViewModel model)
{
Edit form is set up as:
Inherits="System.Web.Mvc.ViewPage<Sample.Web.Models.SampleFormViewModel>"
And it just blows up, not sure what’s going on, has the following error:
No parameterless constructor defined for this object.
Certain I’m missing something really obvious here. Some background, the GET works perfectly and display the dropdown from the SelectList as expected.
I’m guessing the auto-binding back to the custom view model is what is failing but not sure what to do about it.
You need to have a parameterless constructor and I believe that the properties need to have public setters. The default binder creates the object using a constructor that takes no parameters, then uses reflection on the public properties to set values from the form/query parameters.
public class SampleFormViewModel
{
public SampleFormViewModel() { }
public SelectList Companies { get; set; }
public Widget Widget { get; set; }
}
I suspect, though, that what you really want to do is not get the view model, but the underlying Widget model and select list value on form post. I don't think the binder will be able to reconstruct a SelectList on post since it only has the selected value in the parameters.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit( int CompanyID, Widget widget )
{
}
MVC requires, on strongly typed views, that the view can create the class used on that view. This means a constructor without any parameters. And this makes sense. Folks new to MVC will see similar "huh?" issues when they forget/fail to make parameters public and all such related errors that popup when the view attempts to put itself together (as opposed to a compiler error).
But what is "interesting" in this class of parameterless constructor problems is when a property of your class also does NOT have a parameter-free constructor. I guess this is the pessimistic approach?
Having spent some learning time on the SelectList class - a class specific to MVC - I wanted to hopefully help some folks save a few minutes/hours.
This really important tool/class for dropdown list creation, has the following constructors:
public SelectList(IEnumerable items);
public SelectList(IEnumerable items, object selectedValue);
public SelectList(IEnumerable items, string dataValueField, string dataTextField);
public SelectList(IEnumerable items, string dataValueField, string dataTextField, object selectedValue);
..and therefore, if these are properties on your class (the one used for the view), MVC will give you the elusive "No parameterless constructor" error.
BUT, if you create something like a helper class, cut-n-paste the exact code from your original class, and then make that helper class a parameter (NOT a get/set) on your original class; you're good to go.
And in this manner, you can use a single view for gets and posts. Which is more beautiful :)
Personnally, I'd have either created the compiler to recognize the associations and requirements of strong typed views, or let the dropdown (or other "customer" of the SelectList) just fail to work rather then wonder if there's a specific level of recursive checking on paramerterless constructors.
Thankfully, the current version seems to only be top-level. Feels like a hack and I hope it's by design.
HTH.

Resources