Why does my viewmodel name have to be 'model'? - asp.net-mvc

I've been working with MVC for quite a while. I made a form to submit my data using an entity model and as per requirement had to add tags too so I updated the view and the actionmethod to use a viewmodel instead. Like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(PostwTagsVM post)
{
}
Surprisingly, the model was null.I couldn't find out why but then decided to rename the object as below:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(PostwTagsVM model)
{
}
Surprisingly, I get the data in the model now.
I know I can work like this but if i really needed to name by model object something else other than 'model'. Whats happening here?

It does not have to be named model.
If in the first case, your model is null, its because your PostwTagsVM model contains a property named post.
The parameter can be named whatever you want, except that it cannot be the same name as one of the properties in your model.
The reason is that your form would be sending back a name/value pair that is (say) post=someValue. The DefaultModelBinder looks for a matching name, sets the value of the property named Post to someValue, but then also finds a parameter named post and tries to set that to someValue, which fails (because you cannot do PostwTagsVM post = "someValue";), and the model becomes null.

Related

MVC - Inherited view model members [duplicate]

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.

Create and assign value back from View bag to Text box in MVC

I have to create a textbox from a viewbag property in MVC. I could do the mapping like #Html.TextBox("Comments", (string)ViewBag.Comments) but how do I read it back when the page is posted to the server. It is not filling the viewbag property back. I am very new to MVC so maybe don't understand the concept totally .
Thanks
Your ViewBag wont get updated from your view and that is not the way to get data from your form. Rather, you should either use strongly typed model binding to read your data from your Action Method or you can simply check for the key in your Forms data. I am showing you example for both:
Example 1: Strongly typed model binding.
[HttpPost]
public ActionResult MyAction(string comments)
{
// the Comment from the text box.
return View();
}
Example 2: Reading from Posted Data:
[HttpPost]
public ActionResult MyAction()
{
// the Comment from the text box.
string comments = Request.Form["comments"];
return View();
}
I hope, you will like to use the Example 1.
Anyway, the best practice would be to bind your View with a Model class and use HtmlHelper for generating the text box like :
Html.EditorFor(model => model.Comments)
Where your Model class contains a property named Comments.
And your action method should accept the same Model type as argument. Here is an example:
[HttpPost]
public ActionResult MyAction(MyModel model)
{
string comments = model.Comments;
}
And you should bind your View with the model of type MyModel.
I can understand that, as you are new to MVC, this may not make clear sense now, so, I would suggest you to check out some basic MVC tutorial. You can start from here : http://www.asp.net/mvc/tutorials

Can an ASP.NET MVC model class have a property called Model?

I've just spent quite some time tracking down a defect where a JSON model parameter to an MVC action method was always null. The cause is different to those raised in other related questions, hence the new question.
My action looks something like this:
[HttpPost]
public ActionResult SendDeviceDetails(DeviceModel model)
{
model.DoStuffHere();
}
And my model class looks like this:
public class DeviceModel
{
public string Manufacturer { get; set; } // e.g. "Asus"
public string Model { get; set; } // e.g. "Nexus 7"
// etc.
}
The model object is posted as a JSON string from a mobile device.
However, the controller action always throws a NullReferenceException because model is always null. I spent some time verifying that the data is sent correctly from the client end before starting to strip down my model class and build it back up again. (In reality it has many more properties than I've shown here.)
What I found was this: if the model class has a property called Model, you will always get a null reference passed to your action method. If I rename that property (e.g. to ModelName), everything works perfectly.
Is this really as insane as it seems to me? Am I missing some good reason for this restriction or is it just a flat-out defect in MVC? And is there any way to work around it if I really want to have a property named Model?
The problem was caused by the property name (DeviceModel.Model) matching the action parameter name (model). Renaming either of them solves the problem. So actually it's OK to have a Model property in your model class, as long as the parameter you pass it as is not called model.
Crazy!

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!

Example of an ASP.NET MVC post model?

I was watching the HaHaa presentation on ASP.NET MVC from MIX and they mentioned using a Post Model where I guess they were saying you could use a model that was ONLY for posting. I have tried looking for examples for this. Am I not understanding what they are saying? Does anyone have an example of how this might work in a strongly typed view where the view model and post model are not of the same type?
Below is ScottGu's example expanded a bit. As #SLaks explained, when the POST is received, MVC will try to create a new MyPostName object and match its properties with the from fields. It will also update the ModelState property with the results of the matching and validation.
When the action returns the view, it has to provide a model for it as well. However, the view doesn't have to use that same model. In fact, the view can be strongly typed with a different model, that contains the expanded data, for example it can have navigation properties bound to external keys in the DB table; and if that's the case, the logic to map from the POST model to the view model will be contained in the POST action.
public class MyGetModel
{
string FullName;
List<MyGetModel> SuggestedFriends;
}
public class MyPostModel
{
string FirstName;
string LastName;
}
//GET: /Customer/Create
public ActionResult Create()
{
MyGetModel myName = new MyGetModel();
myName.FullName = "John Doe"; // Or fetch it from the DB
myName.SuggestedFriends = new List<MyGetModel>; // For example - from people select name where name != myName.FullName
Model = myName;
return View();
}
//POST: /Customer/Create
[HttpPost]
public ActionResult Create(MyPostModel newName)
{
MyGetModel name = new MyGetModel();
name.FullName = newName.FirstName + "" + newName.LastName; // Or validate and update the DB
return View("Create", name);
}
The POST model would only be used to pass the data into your action method.
The model that the POST action sends to its view doesn't need to be related to the model that it received (and usually will not be).
Similarly, the model that the initial GET action (that shows the form in the first place) passes to its view (which submits to the POST action) doesn't need to be related to the model that the POST action takes (although it usually will be the same model)
As long as it has properties that match your input parameters, you can use any model you want for the parameter to the POST action.

Resources