2 models in 1 view [duplicate] - asp.net-mvc-3

This question already has answers here:
Closed 10 years ago.
I know my question is stupid, but I dont know solution of my problem and can understand similar questions on stackoverflow.
I doing simple blog.
And when I go to one post in this blog I must see text of post and comments for him. They there are in my datebase, but I dont know how display both.
Please help me

You can create a custom ViewModel for this particular View. Something like this:
public class BlogReaderViewModel
{
// various fields which exist on either the post or the comments
}
Then you'd bind to that ViewModel for the View. The Controller action would get the Models it needs and build an instance of the ViewModel to pass to the View.
Another option would be to use a Tuple. It's a generic class which acts as a strongly-typed container for multiple other types. So the View's Model would be something like this:
Tuple<Post, Comments>
From an overall design perspective, my biggest recommendation would be to consider how your Models relate to one another and find your "aggregate root." In the case of a blog post with comments, it sounds like the post should be the aggregate root. The Model itself should have the comments within it. Something like this:
public class BlogPost
{
public string Title { get; set; }
public string Body { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
The idea is that the aggregate root is the parent object and internally knows about its child objects. You shouldn't have to manually compose those hierarchies of objects every time you want to use them.

You have to create a ViewModel to represent this View or the data that this view need, for example:
public class OrderViewModel {
public int Id { get; set; }
public DateTime DateOrder { get; set; }
public decimal Total { get; set; }
public string CustomerName { get; set; }
public List<Item> Items { get; set; }
// other properties
}
And you shoul use this ViewModel to type your view, for sample (using razor):
#model Models.ViewModels.OrderViewModel

It depends on the relationship of the comments in the model. Usually comments should be a child collection of post. So in the view you should be able to render the comments with something like this (Razor):
#foreach (var comment in Model.Comments) {
// comments display goes here
}
Be sure when you pass the model to the view from the controller that you don't produce an inefficient query. Make sure that the query gets the comments with the blog, depending on how you are getting your model in the DB. If you are using EF that would be the "Include" directive, e.g.
.Include(p => p.Comment);

One option is to Create a composite model that represents both groups of data required to render the view, and pass the off each sub model to editor templates on the view itself.

Related

I have a complex View Model that has built complex named html elements. How do I build a matching post model?

I have a lot of data that needs to be passed from a controller to a view and I am trying to use strongly typed View Models where possible.
Take an example where you have a database of loads of people - We want to edit a person whilst also presenting a list of everyone with the same surname.
public class person
{
public string ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class testviewmodel
{
public List<person> people { get; set; }
public person newperson { get; set; }
}
I can't use testviewmodel as the model for the post because there is a lot more going on in the form/data. I have managed to build a model that contains nearly all the form data, other than the ones from the View Model.
I generate some items in the form via:
<input asp-for="newperson.Firstname" class="form-control"/>
This in return generates:
<input class="form-control" disabled type="text" id="newperson_Firstname" name="newperson_Firstname" value="xxxx" />
However, I have tried adding newperson_Firstnameto my model alongside quite a few other combinations, and, I am just not able to see the data.
Can anyone please assist and let me know what I am doing wrong - or, should I just be adjusting the view model to be more fit for purpose?
...Lastly, is there any equivalent of var_dump($_REQUEST);? At the moment, I'm adding breakpoints and trying to open up different items within Locals, but, it's trial and error and taking ages... I'm just trying to find where the form is!
You shouldn't need to dig around in the Request object. If you pass an instance of your ViewModel to your post action, model binding will take care of populating the Person property automatically:
[HttpPost]
public IActionResult Edit(TestviewModel model)
{
var person = model.Person; // add a breakpoint here, should represent the posted values
}

Pass Multiple Parameter within Controller to View in MVC3 Razor view Engine

How can i send two Model from controller to view using same action
Let's assume your two models are instances of MyModel and MyOtherModel.
I can think of two options:
Pass MyModel as the Model and put MyOtherModel in the ViewBag.
Create class MyBigModel with a property containing MyModel and another property containing MyOtherModel and pass MyBigModel as the Model.
Option 1 is really not your ideal solution. Since your model should relate to your view (that's why I prefer the name ViewModel), I'd really go for option 2.
Option 2 would look like this:
public class MyBigModel
{
public MyModel { get; set; }
public MyOtherModel { get; set; }
}
Use ViewModel - create one more model that would contain both of the models, and send that to view
public class MyCustomViewModel
{
public MyFirstModel First { get; set; }
public MySecondModel Second { get; set; }
}
And in controller
public ActionResult Action()
{
MyFirstModel first = new MyFirstModel();
MySecondModel second = new MySecondModel();
MyCustomViewModel model = new MyCustomViewModel();
model.First = first;
model.Second = second;
return View(model);
}
Generally, as the name suggests, you should be using custom ViewModel for every view in your application, and then use tools like AutoMapper to map those view models back and forth to domain models. View models give you great flexibility in composing your view, as you can give any shape and form to them without changing domain.

AutoMapper on complicated Views (multiple looping)

Earlier today, a helpful person (here on Stack Overflow) pointed me towards AutoMapper, I checked it out, and I liked it a lot! Now however I am a little stuck.
In my Code First MVC3 Application, on my [Home/Index] I need to display the following information from my Entities:
List of Posts [ int Id, string Body, int Likes, string p.User.FirstName, string p.User.LastName ]
List of Tags [int Id, string Name]
List of All Authors that exist on my Database [ string UrlFriendlyName ]
So far I have managed only point 1 in the list by doing the following for my Index ViewModel:
public class IndexVM
{
public int Id { get; set; }
public string Body { get; set; }
public int Likes { get; set; }
public string UserFirstName { get; set; }
public string UserLastName { get; set; }
}
And on the Home Controller, Index ActionMethod I have:
public ActionResult Index()
{
var Posts = postsRepository.Posts.ToList();
Mapper.CreateMap<Post, IndexVM>();
var IndexModel = Mapper.Map<List<Post>, List<IndexVM>>(Posts);
return View(IndexModel);
}
Finally on my View I have it strongly typed to:
#model IEnumerable<BlogWeb.ViewModels.IndexVM>
And I am passing each Item in the IndexVM IEnumberable to a Partial View via:
#foreach (var item in Model)
{
#Html.Partial("_PostDetails", item)
}
My question is, how can I also achieve point 2 and 3, whilst not breaking what I've achieved in point 1.
I tried putting the stuff I currently have for IndexVM into a SubClass, and having a List Property on the Parent class, but it didn't work.
From the ASP.NET MVC2 In Action Book:
Some screens are more complex than a single table. They may feature
multiple tables and additional fields of other data: images, headings,
subtotals, graphs, charts, and a million other things that complicate
a view. The presentation model solution scales to handle them all.
Developers can confidently maintain even the gnarliest screens as long
as the presentation model is designed well. If a screen does contain
multiple complex elements, a presentation model can be a wrapper,
composing them all and relieving the markup file of much complexity. A
good presentation model doesn’t hide this complexity—it represents it
accurately and as simply as possible, and it separates the data on a
screen from the display.
Make a ViewModel that represents your screen. Then build it up and pass it to the View. This book is great and talks about using a presentation model. With AutoMapper, think about how you would accomplish your mapping without it, then make use of it. AutoMapper isn't going to do anything magic, it eliminates keyboard slapping.
AutoMapper aside, take your list of requirments:
List of Posts [ int Id, string Body, int Likes, string p.User.FirstName, string p.User.LastName ]
List of Tags [int Id, string Name]
List of All Authors that exist on my Database [ string
UrlFriendlyName ]
and assuming you have these Model entites: Post, Tag, Author
Personally I don't like passing Model entities to my presentation in MVC or MVVM but that's me. Say we follow that here and create PostDisplay, TagDisplay, and AuthorDisplay.
Based on the View's requirements the ViewModel will look like this:
Public class IndexVM
{
Public List<PostDisplay> Posts {get; set;}
Public List<TagDisplay> Tags {get; set;}
Public List<AuthorDisplay> Authors {get; set;}
}
In this case the way the View is composed will require you to build it up:
public ActionResult Index()
{
var posts = postsRepository.Posts.ToList();
var tags = postsRepository.Tags.ToList();
var authors = postsRepository.Authors.ToList();
Mapper.CreateMap<Post, PostDisplay>();
Mapper.CreateMap<Tag, TagDisplay>();
Mapper.CreateMap<Author, AuthorDisplay>();
private var IndexVM = new IndexVM
{
Posts = Mapper.Map<List<Post>, List<PostDisplay>>(posts),
Tags = Mapper.Map<List<Tag>, List<TagDisplay>>(tags),
Authors = Mapper.Map<List<Author>, List<AuthorDisplay>>(authors)
};
return View(IndexVM);
}
So, what you end up with is a ViewModel to pass to your view that represents exactly what you want to display and isn't tightly coupled to your Domain Model. I can't think of a way to have AutoMapper map three separate result lists into one object.
To clarify, AutoMapper will map child collections so a structure like:
public class OrderItemDto{}
public class OrderDto
{
public List<OrderItemDto> OrderItems { get; set; }
}
will map to:
public class OrderItem{}
public class Order
{
public List<OrderItem> OrderItems { get; set; }
}
As long as you tell it how to map the types: OrderDto -> Order and OrderItemDto -> OrderItem.
As an alternative to including all of your lists of entities on a single viewmodel, you could use #Html.Action. Then, in your screen view:
#Html.Action("Index", "Posts")
#Html.Action("Index", "Tags")
#Html.Action("Index", "Authors")
This way, your Index / Screen view & model don't need to know about the other viewmodels. The partials are delivered by separate child action methods on separate controllers.
All of the automapper stuff still applies, but you would still map your entities to viewmodels individually. The difference is, instead of doing the mapping in HomeController.Index(), you would do it in PostsController.Index(), TagsController.Index(), and AuthorsController.Index().
Response to comment 1
public class IndexVM
{
// need not implement anything for Posts, Tags, or Authors
}
Then, implement 3 different methods on 3 different controllers. Here is one example for the PostsController. Follow the same pattern for TagsController and AuthorsController
// on PostsController
public PartialViewResult Index()
{
var posts = postsRepository.Posts.ToList();
// as mentioned, should do this in bootstrapper, not action method
Mapper.CreateMap<Post, PostModel>();
// automapper2 doesn't need source type in generic args
var postModels = Mapper.Map<List<PostModel>>(posts);
return PartialView(postModels);
}
You will have to create a corresponding partial view for this, strongly-typed as #model IEnumerable<BlogWeb.ViewModels.PostModel>. In that view, put the HTML that renders the Posts UI (move from your HomeController.Index view).
On your HomeController, just do this:
public ActionResult Index()
{
return View(new IndexVM);
}
Keep your view strongly-typed on the IndexVM
#model IEnumerable<BlogWeb.ViewModels.IndexVM>
... and then get the Posts, Tags, and Authors like so:
#Html.Action("Index", "Posts")
Response to comment 2
Bootstrapping... your Mapper.CreateMap configurations only have to happen once per app domain. This means you should do all of your CreateMap calls from Application_Start. Putting them in the controller code just creates unnecessary overhead. Sure, the maps need to be created - but not during each request.
This also helps with unit testing. If you put all of your Mapper.CreateMap calls into a single static method, you can call that method from a unit test method as well as from Global.asax Application_Start. Then in the unit test, one method can test that your CreateMap calls are set up correctly:
AutoMapperBootStrapper.CreateAllMaps();
Mapper.AssertConfigurationIsValid();

Reusable editor template with DropDownList for business objects

I'm using MVC3 with Razor views and would like to build reusable DropDownLists for several of my classes, but after much searching I have not found an example that performs exactly how I need it...
For this example I have two classes like this:-
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public Group Group { get; set; }
}
public class Group
{
public int ID { get; set; }
public string Name { get; set; }
}
I have a working Controller/View for Person. The view has a DropDownListFor control:
#model Person
...
#Html.DropDownListFor(o => o.Group.ID, (ViewData["groups"] as SelectList))
The view uses the Person class directly, not an intermediary model, as I haven't found a compelling reason to abstract one from the other at this stage.
The above works fine... in the controller I grab the value from Group.ID in the Person returned from the view, look it up, and set Person.Group to the result. Works, but not ideal.
I've found a binder here: MVC DropDownList values posted to model aren't bound that will work this out for me, but I haven't got that working yet... as it only really seems useful if I can reuse.
What I'd like to do is have something like this in a template:-
#model Group
#Html.DropDownListFor(o => o.Group.ID, (ViewData["groups"] as SelectList))
And use it in a view like this:-
#Html.EditorFor(o => o.Group)
However the above doesn't seem to work... the above EditorFor line inserts editors for the whole class (e.g. a textbox for Group.Description as well)... instead of inserting a DropDownList with my groups listed
I have the above template in a file called Group.cshtml under Views/Shared/EditorTemplates
If this worked, then whenever a class has a property of type Group, this DropDownList editor would be used by default (or at least if specified by some attribute)
Thanks in advance for any advice provided...
You can create a drop down list user control to handle this. Under your Shared folder create a folder called EditorTemplates and place your user control there. MVC, by convention, looks in the Shared/EditorTemplates for any editor templates. You can override where it looks for the editor templates but I won't go in to that here.
Once you have your user control created, you'll need to decorate the appropriate property with the "UIHint" attribute to tell the engine what editor it should use for that property.
Following would be a sample implementation.
In the Shared/EditorTemplates folder your user control (_GroupsDropDown.cshtml in this case) would look like:
#model Group
#Html.DropDownListFor(o => o.ID, (ViewData["groups"] as SelectList))
Modify the Group property in the Person to add the UIHint attribute as follows:
**[UIHint("_GroupsDropDown")]**
public Group Group { get; set; }
In your controller you would need
ViewData["groups"] = new SelectList(<YourGroupList>, "ID", "Name");
Once you have the above code you can use the EditorFor syntax like you desire.
Hope this helps.

Passing different parameters to multiple partial views within a view

How do I pass variables to multiple partial views within a view? My idea is that every partial view will make up a different field in a form, and I plan on using dynamically created DOM objects with each partial view. So I need to pass in 3 parameters. An ID (to uniquely identify each DOM object), and its particular controller and action. How would I go about doing this in a view?
For example, I want to do something along the lines of...
#Html.Partial("_BookField", Model, 1, "book", "updateauthor")
#Html.Partial("_BookField", Model, 2, "book", "updatetitle")
#Html.Partial("_BookField", Model, 3, "book", "updatepublisher")
Is there a way to do this without adding additional attributes in the model?
Edit: After conferring with one of my coworkers, he also brought up a point that I'd like clarification on. He suggested that what I'm doing, which is introducing controller/action logic in the view is breaking the MVC concept. But things like ActionLinks, you have to put in the controller and action in an actionlink for it to be able to do something. Is what I'm trying to do considered breaking the MVC paradigm?
You use view models of course:
public class SubViewModel
{
public int Id { get; set; }
public string Book { get; set; }
public string Author { get; set; }
}
and then have your main view model different properties of this SubViewModel:
public class MyMainViewModel
{
public SubViewModel Sub1 { get; set; }
public SubViewModel Sub2 { get; set; }
public SubViewModel Sub3 { get; set; }
}
and then simply:
#Html.Partial("_BookField", Model.Sub1)
#Html.Partial("_BookField", Model.Sub2)
#Html.Partial("_BookField", Model.Sub3)
Of course it is the now the controller action responsibility to fill those view models (as always). And if you use editor/display templates your code might be even simpler, just like this:
#Html.DisplayFor(x => x.Sub1)
#Html.DisplayFor(x => x.Sub2)
#Html.DisplayFor(x => x.Sub3)
and because this strangely looks to me like a collection, well, you could use a collection:
public class MyMainViewModel
{
public SubViewModel[] Subs { get; set; }
}
and then simply:
#Html.DisplayFor(x => x.Subs)

Resources