POSTing ViewModel contained in other ViewModel asp.net mvc - asp.net-mvc

I have a ViewModel which contains two other ViewModels (stripped for brevity):
class SmallViewModel1
{
public string Item { get; set; }
}
class SmallViewModel2
{
public string Item { get; set;}
}
class BigViewModel {
public SmallViewModel1 Model1 { get; set; }
public SmallViewModel2 Model2 { get; set; }
}
I then have a View which accepts BigViewModel as its model. The view has two forms which POST to two different actions.
#model BigViewModel
#using (Html.BeginForm("Action1","Controller",FormMethod.Post))
{
#Html.TextBoxFor(t=>t.Model1.Item)
}
#using (Html.BeginForm("Action2","Controller",FormMethod.Post))
{
#Html.TextBoxFor(t=>t.Model2.Item)
}
Then in the controller I am trying to do something like this:
public ActionResult Action1(SmallViewModel1 model)
{
....
}
public ActionResult Action2(SmallViewModel2 model)
{
....
}
The issue I am running into is if I use the 'For' controls (EditorFor, TextBoxFor, etc) my POSTed model is null. I think this is because it expects me to POST the full ViewModel (BigViewModel) and it names the inputs expecting this: <input name="Model1.Item". To get around this I have been using the non 'For' control and just setting the name to the SmallViewModel's property, for example: #Html.TextBox("Item"). This maps correctly to the controller and everything ends up working.
My questions is am I doing this correctly? I've been googling around for a bit and haven't found anything but I feel like there is a better, or more 'best-practice' way to do this.

Your Controller should be like this
public ActionResult Action1(BigViewModel model)
{
....
}
public ActionResult Action2(BigViewModel model)
{
....
}
As you have taken BigViewModel, When you submit it should have same model in parameter
Alternatively you can use the Prefix property of the BindAttribute
public ActionResult Action1([Bind(Prefix="Model1")]SmallViewModel1 model)
{
....
}
public ActionResult Action2([Bind(Prefix="Model2")]SmallViewModel2 model)
{
....
}

Related

Using part of a view in a different controller

I want to do the following and I can't figure it out. I have a controller with a view that shows a list of data. I would like to show that list of data on the home page. Is there any way to use the view of that list of data in the controller of the home page?
You want to achieve two (or more) models to render in one view? If yes, then here is your answer.
Let's suppose that you have two models, Book and Movie
public class Book
{
public int BookId { get; set; }
public string BookTitle { get; set; }
}
public class Movie
{
public int MovieId { get; set; }
public string MovieTitle { get; set; }
}
And then you want to get all Books and all Movies in one view. So create new class (for example in new folder named ViewModels and your class name can be anything, but let's name it for example ViewModel. In your class ViewModel write your Book and Movie model.
public class ViewModel
{
public Book Book { get; set; }
public IEnumerable<Book> Books { get; set; }
public Movie Movie { get; set; }
public IEnumerable<Movie> Movies { get; set; }
}
If you want also to display list you need to specify IEnumerable list, here is for Books and Movies.
And finally in your controller you can simply write:
public ActionResult Index()
{
var viewModel = new ViewModel
{
Movies = db.Movies.ToList(),
//you can also do this:
Movies = db.Movies.Where() //and so on
Books = db.Books.ToList()
};
return View(viewModel);
}
And #model for your view will be
#model MovieSubtitles.ViewModels.ViewModel
Hope it helps.
So you're saying you currently have something like:
public DataController() {
public ActionResult Index()
{
.. load data
return View(data);
}
}
If you convert data list to a PartialView you can then use that in any other view, eg:
public DataController() {
public ActionResult Index()
{
return View();
}
public ActionResult DataPartial()
{
.. load data
return Partial(data);
}
}
then in your original view, move out just the parts related to data into DataPartial.cshtml and in index.cshtml add call to the action:
#Html.Action("DataPartial");
You can then re-use this partial/action anywhere you want by calling it as above, but also specifying the controller
public HomeController() {
public ActionResult Index()
{
return View();
}
}
and home\index.cshtml:
#Html.Action("DataPartial", "Data")
Thanks, I figured it out. I made a partial view and I implemented that partial view in both index views (from home and the actual page of the list of data)

How to use SelectList in View without a model?

I have read many times experts of MVC saying that if I were to use a SelectList, it's best to have a IEnumerable<SelectList> defined in my model.
For example, in this question.
Consider this simple example:
public class Car()
{
public string MyBrand { get; set; }
public IEnumerable<SelectListItem> CarBrands { get; set; } // Sorry, mistyped, it shoudl be SelectListItem rather than CarBrand
}
In Controller, people would do:
public ActionResult Index()
{
var c = new Car
{
CarBrands = new List<CarBrand>
{
// And here goes all the options..
}
}
return View(c);
}
However, from Pro ASP.NET MVC, I learned this way of Creating a new instance.
public ActionResult Create() // Get
{
return View()
}
[HttpPost]
public ActionResult Create(Car c)
{
if(ModelState.IsValid) // Then add it to database
}
My question is: How should I pass the SelectList to View? Since in the Get method there is no model existing, there seems to be no way that I could do this.
I could certainly do it using ViewBag, but I was told to avoid using ViewBag as it causes problems. I'm wondering what are my options.
You could create a ViewModel that has all the properties of Car which you want on your form then make your SelectList a property of that ViewModel class
public class AddCarViewModel
{
public int CarName { get; set; }
public string CarModel { get; set; }
... etc
public SelectList MyList
{
get;
set;
}
}
Your controller will look like
public ActionResult Create() // Get
{
AddCarViewModel model = new AddCarViewModel();
return View(model)
}
[HttpPost]
public ActionResult Create(AddCarViewModel c)
{
if(ModelState.IsValid) // Then add it to database
}
MarkUp
#Html.DropDownListFor(#model => model.ListProperty, Model.MyList, ....)
Easy way, this is a copy of my code
without model
In the controller
ViewBag.poste_id = new SelectList(db.Postes, "Id", "designation");
In the view
#Html.DropDownList("poste_id", null," -- ", htmlAttributes: new { #class = "form-control" })

UpdateModel with different source and destination types

In my action I use a InsertPerson model that has the following properties:
pulbic class InsertPerson
{
[Required]
public string Name{get;set;}
}
and I have a DTO model that is passed to my repository:
public class PersonDto
{
public int Id{get;set;}
public string Name{get;set;}
public string LastName{get;set;}
}
my action:
[HttpPost]
public ActionResult Create(InsertPerson insertPerson)
{
if(ModelState.IsValid){
var personDto = new PersonDto();
UpdateModel(personDto)
//UpdateModel(personDto, "insertPerson") I've tried this too
}
}
why it doesn't work(after UpdateModel all properties is still null)?
Is there any way to update my personDto using UpdateModel?
I know about AutoMapper but I think it's boring to use in controller.
My view:
#model Help_Desk.ViewModel.InsertPersonViewModel
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.InsertPerson.Name)
<button type="submit">Save</button>
}
and my viewmodel:
public class InsertPersonViewModel
{
public InsertPerson InsertPerson{ get; set; }
}
Ok, now we see the problem. The information that was missing is that you were using a View Model, and the View Model creates a "container" of sorts that must be accounted for in the UpdateModel.
What's happening is that UpdateModel is trying to literally update "InsertPerson.Name" in your personDto object. Since that doesn't exist, it's not working.
You can fix this by specifying UpdateModel(personDto, "InsertPerson")

bind attribute include and exclude property with complex type nested objects

Ok, this is weird. I cannot use BindAttribute's Include and Exclude properties with complex type nested objects on ASP.NET MVC.
Here is what I did:
Model:
public class FooViewModel {
public Enquiry Enquiry { get; set; }
}
public class Enquiry {
public int EnquiryId { get; set; }
public string Latitude { get; set; }
}
HTTP POST action:
[ActionName("Foo"), HttpPost]
public ActionResult Foo_post(
[Bind(Include = "Enquiry.EnquiryId")]
FooViewModel foo) {
return View(foo);
}
View:
#using (Html.BeginForm()) {
#Html.TextBoxFor(m => m.Enquiry.EnquiryId)
#Html.TextBoxFor(m => m.Enquiry.Latitude)
<input type="submit" value="push" />
}
Does not work at all. Can I only make this work if I define the BindAttribute for Enquiry class as it is stated here:
How do I use the [Bind(Include="")] attribute on complex nested objects?
Yes, you can make it work like that:
[Bind(Include = "EnquiryId")]
public class Enquiry
{
public int EnquiryId { get; set; }
public string Latitude { get; set; }
}
and your action:
[ActionName("Foo"), HttpPost]
public ActionResult Foo_post(FooViewModel foo)
{
return View(foo);
}
This will include only the EnquiryId in the binding and leave the Latitude null.
This being said, using the Bind attribute is not something that I would recommend you. My recommendation is to use view models. Inside those view models you include only the properties that make sense for this particular view.
So simply readapt your view models:
public class FooViewModel
{
public EnquiryViewModel Enquiry { get; set; }
}
public class EnquiryViewModel
{
public int EnquiryId { get; set; }
}
There you go. No longer need to worry about binding.
IMHO there is a better way to do this.
Essentially if you have multiple models in the view model the post controller's signature would contain the same models, as opposed to the view model.
I.E.
public class FooViewModel {
public Bar BarV { get; set; }
public Enquiry EnquiryV { get; set; }
public int ThisNumber { get; set; }
}
public class Bar {
public int BarId { get; set; }
}
public class Enquiry {
public int EnquiryId { get; set; }
public string Latitude { get; set; }
}
And the post action in the controller would look like this.
[ActionName("Foo"), HttpPost]
public ActionResult Foo_post(
[Bind(Include = "EnquiryId")]
Enquiry EnquiryV,
[Bind(Include = "BarId"])]
Bar BarV,
int ThisNumber
{
return View(new FooViewModel { Bar = BarV, Enquiry = EnquiryV, ThisNumber = ThisNumber });
}
All while the view still looks like this
#using (Html.BeginForm()) {
#Html.TextBoxFor(m => m.EnquiryV.EnquiryId)
#Html.TextBoxFor(m => m.EnquiryV.Latitude)
#Html.TextBoxFor(m => m.BarV.BarId)
#Html.TextBoxFor(m => m.ThisNumber)
<input type="submit" value="push" />
}
Keep in mind, this form will still post Latitude back (the way you had it set up), however since it is not included in the Bind Include string for Enquiry on the post action, the action will not accept the new value in the resultant Enquiry. I'd suggest making latitude either disabled or not a form element to prevent additional posting data.
In any other scenario you can use bind just fine, but for some reason it dislikes the dot notation for complex models.
As a side note, I wouldn't put the bind attribute on the class directly as it can cause other issues like code replication, and doesn't account for certain scenarios where you may want to have a different binding.
(I modified the variable names for some clarity. I am also aware your question is rather dated, however in searching for the answer myself this is the first SO I stumbled upon before trying my own solutions and coming to the one I posted. I hope it can help out other people seeking a solution to the same issue.)

Different models for Get and Post - MVC

As I understand from the question below it should be possible to use different models for Get and Post actions. But somehow I'm failing to achieve just that.
What am I missing?
Related question: Using two different Models in controller action for POST and GET
Model
public class GetModel
{
public string FullName;
public string Name;
public int Id;
}
public class PostModel
{
public string Name;
public int Id;
}
Controller
public class HomeController : Controller
{
public ActionResult Edit()
{
return View(new GetModel {Id = 12, Name = "Olson", FullName = "Peggy Olson"});
}
[HttpPost]
public ActionResult Edit(PostModel postModel)
{
if(postModel.Name == null)
throw new Exception("PostModel was not filled correct");
return View();
}
}
View
#model MvcApplication1.Models.GetModel
#using (Html.BeginForm()) {
#Html.EditorFor(x => x.Id)
#Html.EditorFor(x=>x.Name)
<input type="submit" value="Save" />
}
Your models aren't using proper accessors so model binding doesn't work. Change them to this and it should work:
public class GetModel
{
public string FullName { get; set; }
public string Name { get; set; }
public int Id { get; set; }
}
public class PostModel
{
public string Name { get; set; }
public int Id { get; set; }
}
A bit of clarification
GET and POST controller actions can easily use whatever types they need to. Actually we're not talking about models here. Model is a set of classes/types that represent some application state/data. Hence application or data model.
What we're dealing here are:
view model types
action method parameter types
So your application model is still the same. And GetModel and PostModel are just two classes/types in this model. They're not model per-se.
Different types? Of course we can!
In your case you're using a view model type GetModel and then pass its data to PostModel action parameter. Since these two classes/types both have properties with same matching names, default model binder will be able to populate PostModel properties. If property names wouldn't be the same, you'd have to change the view to rename inputs to reflect POST action type property names.
You could as well have a view with GetModel type and then post action with several different prameters like:
public ActionResult Edit(Person person, IList<Address> addresses)
{
...
}
Or anything else. You'll just have to make sure that post data can be bound to these parameters and their type properties...

Resources