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)
Related
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
}
I am teaching myself asp .net mvc3.
I have a partial view which uses various models and lookup type db. I want to keep it strongly typed and use it in multiple places but I am not sure how to implement it.
The example below should explain it better. This question might get a bit long and I appreciate your patience.
The partial view basically gives a small description of the property. A snippet of the ‘_PropertyExcerptPartial’ is below:
#model Test.ViewModels.PropertyExcerptViewModel
<div>
<h3>#Html.DisplayFor(model => model.Property.NoOfBedrooms) bedroom #Html.DisplayFor(model => model.FurnitureType.FurnitureTypeDescription) flat to Rent </h3>
…
</div>
I want to keep this partial view strongly typed and use it in multiple places. The model that it is strongly typed to is as follows:
public class PropertyExcerptViewModel
{
public Property Property { get; set; }
public FurnitureType FurnitureType { get; set; }
}
The two 2 database that this model looks up is as follows:
public class Property
{
public int PropertyId {get; set; }
...
public int NoOfBedrooms {get; set;}
public int FurnishedType { get; set; }
...
}
public class FurnishedType
{
public int FurnishedTypeId { get; set; }
public string FurnishedTypeDescription { get; set; }
}
The furnished type database is basically just a lookup table with the following data:
1 - Furnished
2 - Not Furnished
3 - Part Furnished
4 - Negotiable
I have many such lookups in that I only store an int value in the property database which can be used to look up the description. These databases are not linked to property database and the value of furniture type is read via a function GetFurnitureType(id). I pass stored int value of Property.FurnitureType as the id.
However, I encounter a problem when I try to use this partial view as I am not sure how to pass these multiple models from a view to partial view.
Say I am trying to create an ‘added property’ page. This page basically list the properties added by the logged in user. To facilitate this, I have created another function called GetAddedProperties(userId) that return the properties added by a particular user. In the ‘added property’ view, I can call a foreach function to loop through all the properties returned by GetAddedProperties and display the _PropertyExcerptPartial. Something like this:
<div>
#foreach (var item in //Not sure what to pass here)
{
#Html.Partial("_PropertyExcerptPartial",item)
}
</div>
However, I can’t use the partial view to display information as it will display the int value of furniture type stored in the property database and I am not sure how to get the corresponding FurnitureTypeDescription and pass it to the partial view from the ‘added property’ page.
Any help would be appreciated. Thanks a lot!
You should start by designing a real view model, not some class that you suffix its name with ViewModel and stuff your domain models inside. That's not a view model.
So think of what information you need to work with in your view and design your real view model:
public class PropertyExcerptViewModel
{
public int NoOfBedrooms { get; set; }
public string FurnishedTypeDescription { get; set; }
}
and then adapt your partial to work with this view model:
#model Test.ViewModels.PropertyExcerptViewModel
<div>
<h3>
#Html.DisplayFor(model => model.NoOfBedrooms)
bedroom
#Html.DisplayFor(model => model.FurnitureTypeDescription)
flat to Rent
</h3>
...
</div>
OK, now that we have a real view model let's see how we could populate it. So basically the main view could be strongly typed to a collection of those view models:
#model IEnumerable<Test.ViewModels.PropertyExcerptViewModel>
<div>
#foreach (var item in Model)
{
#Html.Partial("_PropertyExcerptPartial", item)
}
</div>
and the last bit of course is the main controller that will do all the db querying and building of the view model that will be passed to the main view:
[Authorize]
public ActionResult SomeAction()
{
// get the currently logged in username
string user = User.Identity.Name;
// query the database in order to fetch the corresponding domain entities
IEnumerable<Property> properties = GetAddedProperties(user);
// Now let's build the view model:
IEnumerable<PropertyExcerptViewModel> vm = properties.Select(x => new PropertyExcerptViewModel
{
NoOfBedrooms = x.NoOfBedrooms,
FurnishedTypeDescription = GetFurnitureType(x.FurnishedType).FurnishedTypeDescription
});
// and finally pass the view model to the view
return View(vm);
}
Be careful with the lazy nature of EF if that is the ORM that you are using in order not to fall into the SELECT N + 1 trap.
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.
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.
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();