Asp.Net MVC - Model Binding to Model or ViewModel
Firstly the names of you data model do not make sense. A property named BedroomsList
suggests a collection of Bedrooms
yet the property is a string
. Start by naming you properties to describe what they are so others can make sense of your code.
public class BuyModel
{
public string PurchaseDate { get; set; }
public string Bedrooms { get; set; }
public string Stories { get; set; }
public string SquareFootage { get; set; }
public string PreferredCityLocations { get; set; }
public string AdditionalInfo { get; set; }
}
And the corresponding view model needs to contain those properties plus the SelectList
properties.
public class BuyVM
{
public string PurchaseDate { get; set; }
public string Bedrooms { get; set; }
public string Stories { get; set; }
[Required]
public string SquareFootage { get; set; }
[Required]
public string PreferredCityLocations { get; set; }
public string AdditionalInfo { get; set; }
public SelectList PurchaseDateList { get; set; }
public SelectList BedroomsList { get; set; }
public SelectList StoriesList { get; set; }
}
Next remove your GetBuyVM()
method and replace it with a method in the controller that populates the select lists so that you can also call that method if ModelState
is invalid and you need to return the view and change the POST method to parameter to your view model (your view is based on BuyVM
so you must post back to BuyVM
, not BuyModel
)
public ActionResult Buy()
{
BuyVM model = new BuyVM(); // initalise an instance of the view model
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Buy(BuyVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
Initialize your data model and map the view model properties to it
BuyModel dataModel = new BuyModel()
{
PurchaseDate = model.PurchaseDate,
Bedrooms = model.Bedrooms,
....
};
// save the data model
return View("Success");
}
private ConfigureViewModel(BuyVM model)
{
model.PurchaseDateList = new SelectList(new[] { "Immediately", "1 to 3 months", "4 to 6 months", "More than 6 months" });
model.BedroomsList = new SelectList(new[] { "1", "2", "3", "4", "5+" });
model.StoriesList = new SelectList(new[] { "1", "2", "Does not matter" });
}
And finally in the view, bind to your property (PurchaseDate
, not PurchaseDateList
)
@Html.DropDownListFor(m => m.PurchaseDate, Model.PurchaseDateList)
@Html.DropDownListFor(m => m.Bedrooms, Model.BedroomsList)
Adi Sekar
Updated on July 14, 2022Comments
-
Adi Sekar almost 2 years
I have a controller, which returns a view passing in a view model, which has properties required for display of the view (Drop-down select item lists etc).
But when I post it to the server, I have a different model class, which has the selected value of those dropdowns. In my HttpPost controller action, I check the (ModelState.IsValid), before doing any processing, but when it is false, I 'return View(model)' back.
But since the view is bound to the ViewModel, and my Post action, is accepting the actual model, I get an error 'The model item passed into the dictionary is of type 'Model', but this dictionary requires a model item of type 'ViewModel', when I submit the form, and the validation error to show up on the view.
How do I solve this? What is the best practice for using strongly typed views, passing in the view model, but when submitting to a different model?
Code:
public ActionResult Buy() { BuyVM buyVM = GetBuyVM(); return View(buyVM); } [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Buy(BuyModel model) { if (ModelState.IsValid) { // Do Procesing return View("Success"); } return View(model); } public class BuyVM { public SelectList PurchaseDateList { get; set; } public SelectList BedroomsList { get; set; } public SelectList StoriesList { get; set; } [Required] public string SquareFootage { get; set; } [Required] public string PreferredCityLocations { get; set; } public string AdditionalInfo { get; set; } } public class BuyModel { public string PurchaseDateList { get; set; } public string BedroomsList { get; set; } public string StoriesList { get; set; } public string SquareFootage { get; set; } public string PreferredCityLocations { get; set; } public string AdditionalInfo { get; set; } } private static BuyVM GetBuyVM() { BuyVM buyVM = new BuyVM(); buyVM.PurchaseDateList = new SelectList(new[] { "Immediately", "1 to 3 months", "4 to 6 months", "More than 6 months" }); buyVM.BedroomsList = new SelectList(new[] { "1", "2", "3", "4", "5+" }); buyVM.StoriesList = new SelectList(new[] { "1", "2", "Does not matter" }); return buyVM; }
Buy.cshtml
@model Models.BuyVM // html @Html.DropDownListFor(m => m.PurchaseDateList, Model.PurchaseDateList, new { @class = "form-control" }) @Html.DropDownListFor(m => m.BedroomsList, Model.BedroomsList, new { @class = "form-control" })
So when I return the View(model) back, in the HTTPPost if there were validation errors (JQueryVal), I am trying to display the validation errors, if I pass the model back to the view. But I have this type mismatch.
-
A_Sk over 8 yearspost your code please.without code can't help.
-
Steve Greene over 8 yearsWhen it fails, you need to rebuild the same model in your get before calling the view back, so you need to reset items not bound to that model like lookup lists, etc.
-
Adi Sekar over 8 yearsAdded Code @user3540365 & Steve !
-
Adi Sekar over 8 yearsHi Steve - Would rebuilding the view, have my validation errors intact? Required field and other validations, if I rebuild the VM and pass it?
-
Admin over 8 yearsYour POST method needs be
public async Task<ActionResult> Buy(BuyVM model)
(notBuyModel
) but youBuyVM
view model appears be be missing a lot of properties - you haveSelectList
's for a number of properties by no corresponding property to bind to. -
Admin over 8 yearsIt also impossible to understand what your really trying to do here. Your data model contains properties named
xxxList
suggesting a collection yet they are all typeofstring
and you also have a method namedGetBuyVM()
suggesting you populate theSelectList
's in that method which is also the wrong approach. You need to show that method and explain what the propertiesPurchaseDateList
,BedroomsList
andStoriesList
really are in the data model. -
Adi Sekar over 8 yearsI am trying to keep my VM separate from my Model. My VM contains the select List for Dropdowns, and they get populated by GetBuyVM, and when posting to server, I use the Buy Model to get the selected values from the dropdowns. Would you recommend having the selected values (string type of DD value) also be in the VM?
-
Adi Sekar over 8 yearsWhat I have works okay, for client side validation, but my server side validation still has this issue.
-
Admin over 8 yearsHaving a view model (as opposed to using you data model) is best practice but you don't seem to be understanding the binding process. Your view model needs a property to bind to in addition to the SelectLists. But your data model just does not make sense so hard to show you the right way to do this. You have a property named
BedroomsList
which suggests a collection of Bedrooms, yet its type ofstring
(notList<Bedroom>
orList<string>
). You need to explain just what those properties really are before I can give you the answer. And also show yourGetBuyVM()
method -
Adi Sekar over 8 yearsHI Stephen, I have added the code for the GetBuyVM() and view html helper for the dropdowns. So the VM has a SelectList of PurchaseDateList, but the selected value of this during submit of form, gets bound to string PurchaseDateList in BuyModel, since the naming is consistent. I wanted to make the initial Get which includes populating of Dropdown's separate from the values posting back (Selected value), so thereby having a separate VM and the model.
-
Admin over 8 years@AdiSekar, To notify a user, start the comment as I have done here :). I'll post an answer a bit later showing you how to do this correctly.
-
-
Adi Sekar over 8 yearsWould rebuilding the view, have my validation errors intact? Required field and other validations, if I rebuild the VM and pass it?
-
Adi Sekar over 8 yearsI tried this, and I get this error 'No parameterless constructor defined for this object.', before it hits my breakpoint at the Post action. The BuyVM has all the select items to be bound to the Dropdowns in the View, would this get the selected values, in the POST action?
-
Platte Gruber over 8 yearsValidation errors will stay intact either way. As Steve Greene mentions below, it still would be better to pass the ViewModel into Post. When you do, you can do all the processing you need to on the model by using ViewModel.modelName. You will still have to rebuild the dropdownlist when the model is invalid, however.
-
Adi Sekar over 8 yearsPassing VM into post does not seem to work. I get this error 'No parameterless constructor defined for this object.', before it gets to the Post action. The BuyVM has all the select list items to be bound to the Dropdowns in the View, would this have the selected value or should we explicitly pass that? My DD in the view, are model-binded to the Model using the DD name's.
-
Steve Greene over 8 yearsWhat code is crashing? The bound lists won't come back to the Post, that is why you need to rebuild them. Does your GetBuyVM() populate them?
-
Adi Sekar over 8 yearsIt crashes during model binding 'BuyVM' to my Post action, after submitting my form.public async Task<ActionResult> Buy(BuyVM buyVM). So it does not even reach my post action, it fails trying to bind to BuyVM.
-
Admin over 8 yearsValidation errors will NOT stay intact either way!
BuyModel
does not have any validation attributes so there would hardly be any point inif (ModelState.IsValid)
sinceBuyModel
can never be invalid -
Steve Greene over 8 yearsWould need to see the view, but have you tried debugging it to see the line that crashes? Another option is to comment stuff out to get a minimally working version then add stuff back in.