I'm new to ASP.NET and currently working on a simple app to show a list of people and their hobbies. I have the following classes:
public class Hobby
{
public int HobbyID {get;set;}
public string Name {get;set;}
public string Type {get;set;}
public ICollection<PersonHobby> PersonHobbies {get;set;}
}
public class Person
{
public int PersonID {get;set;}
public int Age {get;set;}
public string Name {get;set;}
public ICollection<PersonHobby> PersonHobbies {get;set;}
}
public class PersonHobby
{
public int PersonHobbyID {get;set;}
public int PersonID {get;set;}
public int HobbyID {get;set;}
}
When viewing a person's Details page, I also need to display their hobbies. I did some research and found that ViewModels are a good way to accomplish this. So I created one, but I'm not sure if I did it correctly:
public class PersonHobbiesViewModel
{
public Person Person {get;set;}
public IEnumerable<PersonHobby> PersonHobbies {get;set;}
public IEnumerable<Hobby> Hobbies {get;set;}
}
And at this point I know that I need to create a viewmodel object in my controller's Details method and populate it with data, but I don't know how to navigate through the different tables. I have this:
public ActionResult Details(int? id)
{
var viewModel = new PersonHobbiesViewModel();
viewModel.Person = db.Person.find(id);
viewModel.Hobbies = ???
return View(viewModel);
}
On the other hand, if I'm going in the completely wrong direction, let me know! Thanks in advance.
Firstly what you might like to do, is change your entity models ever so slightly and let EF6 deal with the many to many complexity for you.
Your new model might look like this:
public class Hobby
{
public int HobbyID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public Person Person { get; set; }
}
public class Person
{
public int PersonID { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public ICollection<Hobby> Hobbies { get; set; }
}
Your context might be like:
public class MyContext : DbContext
{
public DbSet<Hobby> Hobbies { get; set; }
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Hobby>()
.HasRequired<Person>(s => s.Person)
.WithMany(s => s.Hobbies)
.HasForeignKey(s => s.PersonId);
}
}
Then when you are creating your model you can simply query it like:
var person = db.People.Include(c => c.Hobbies).SingleOrDefault(x => x.PersonID == id);
var viewModel = new PersonHobbiesViewModel();
viewModel.Person = person;
viewModel.Hobbies = person.Hobbies;
return View(viewModel);
Have a read through the following tutorials about complex entity models and reading from them, they cover the same content.
Ps. I have not tested any of this.
#shenku's answer has correct way to map entities. But it is unnecessary if you don't consider about naming conventions.
Additionally Entity Framework has much of c#'s object oriented programming basics. That means if you pass the person object from your controller to view, you could access to your entities on view like below.
//Controller
var person = db.People.Include(c => c.Hobbies).SingleOrDefault(x => x.PersonID == id);
return View(person);
#*View Page*#
#using Project.Models
#* prints Person Name and Age *#
#Model.Name #Model.age
#* prints Hobby Names of Person
#foreach (var item in Model)
{
#item.name
}
You've answered your own problem correct as "circular reference" on your comment.
So you don't need PersonHobby class, Entity framework will automatically creates this table for you. This magic happens because you've defined hobby and person as a collection on their own classes.
Also you don't need a viewModel for your situation. Just pass the person or hobby object. Do not pass the Icollection because it's already loaded when you wrote db.People.Include(c => c.Hobbies). Also you can define classes as 'virtual' so it will load entities without include method.(Lazy-loading)
Related
I am using MVC5 Code First and have a couple of classes that look like;
public class Asset
{
public int Id { get; set; }
public string FileName { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public int AssetId { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
}
I would like to return a view that lists all Assets that have a particular Category. I was hoping for something along the lines of
public ActionResult ListByCategory(string categoryName)
{
var model =
from r in _db.Assets
.Where(r => r.Categories.CategoryName == categoryName)
select r;
return View(model);
}
I know I have some assets from my seed method that have categories that exist. But the compiler is saying "System.Collection.Generic.ICollection Does not contain a definition for CategoryName and no extension method could be found, am I missing a reference?" This is on my .Where line.
I don't fully understand what it's trying to tell me. I do have a reference to my Models as I can reference them elsewhere within the controller. I know that a single Asset might be in several Categories, hence I created the ICollection at the class level.
Here is my context class;
public class AssetsDb : DbContext
{
public AssetsDb() : base("DefaultConnection")
{
}
public DbSet<Asset> Assets { get; set; }
public DbSet<Category> Categories { get; set; }
}
Could somebody help my understanding on how I can get to my underlying data? I'm trying to learn EF / MVC so appreciate any help.
Thanks.
You cannot get a CategoryName from a collection of categories, you need to check the name of each category within the collection.
Try using this query instead:
var model =
from r in _db.Assets
.Where(r => r.Categories.Any(c => c.CategoryName == categoryName))
select r;
I am new to asp.net, there are some questions on stack overflow but they don't fulfill my purpose. My question is..
How would I implement view model for the following two models?
public class model1
{
int student-id{ get;set}
string student-name{get; set;}
}
public class model2
{
int course-code{get; set;}
string course-name{get; set;}
}
Now I want to write a view model that could pass to a view and this view displays student-name and corresponding course-names.
Note: a student can enrolled in more than one course.
First of all you should modify your model. Student and courses have to be related. You can implement these relations like:
public class Student
{
public int Id { get; set }
public string Name { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Course
{
public int Code { get; set; }
public string Name{ get; set; }
public ICollection<Student> Students { get; set; }
}
After - you create view model. View model class must contain only what you actually need in you view. In you case - student and courses names. You can consider several options here. If you want just to display all course names in one line you can build you StudentViewModel like this:
public class StudentViewModel
{
public string Name { get; set; }
// In this case you can just join all courses' names to one string using string.Join(", ")
public string Courses { get; set; }
}
... or like this - if you want courses' names separated (to use them in some select or list html element). But you can create JoinedCources property which will return courses' names joined into one string.
public class StudentViewModel
{
public string Name { get; set; }
public ICollection<string> Courses { get; set; }
public string JoinedCources {
get {
return string.Join(", ", Courses);
}
}
}
Note: this is view model for only one student! If you want to display view which shows you the list of students and their courses you should either create new view model with property which is collection of StudentViewModel or in your view define model like #model ICollection<StudentViewModel> instead of #model StudentViewModel.
Now you have to map your model to view model. For example in your controller action when you get your student from database (or any other data source - file or web service):
public ActionResult StudentDetails(int studentId)
{
var student = _dataSource.GetStudent(studentId);
var model = AutoMapper.Mapper.Map<StudentViewModel>(student);
return View(model);
}
Now few words about mapping. AutoMapper is external class library you should definitely get to learn about if you want to work with view models and mapping in the future. It will help you simplify action method code and make it more readable. But since you're new to ASP. Net you can implement mapping by your self for the first time. For example like below:
public ActionResult StudentDetails(int studentId)
{
var student = _dataSource.GetStudent(studentId);
var model = new StudentViewModel()
{
Name = student.Name,
Courses = student.Courses.Select(c => c.Name)
}
return View(model);
}
public class StudentViewModel
{
public string Name{get;set;}
public int StudentId{ get;set}
public List<model2> Courses
}
You can consider combining the two models into the above view model if the goal is to display a student with course info.
I'm trying to do something i that feels like a small task, but i cannot figure out a simple way to do it. All my approaches for doing this gets really complex for a simple task.
I have these models:
public class Blog
{
public int Id { get; set; }
public String Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public String Name { get; set; }
public int BlogId { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public String CommentText { get; set; }
public int PostId { get; set; }
public int UserProfileUserId { get; set; }
}
public class UserProfile
{
public int UserId { get; set; }
public string UserName { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
}
In the Added Comments partial view, i want to show the full user name of the user that made a comment. If i just use my base classes in my views and partial views, i get everything i need except full user name on added comments. So far, i've thought of the following ways:
ViewModels - This will result in creating a ViewModel for each of my Classes and then populate / map them manually in my controller.
Code in Views - I have the UserProfileUserId so i can just ask the repository from the view but this Kills the MVC in MVC so i don't want to do it.
Actually Adding UserProfileFirstName and UserProfileLastName to the Comment Class as foreign keys - This feels like filling the database with view specific data. It doesn't belong in a relational database.
Using regular SQL and Query the database - Just because i know SQL, this -could- be a way to do it. but then again i'm killing the MVC in MVC.
How should i do this? Where is my silly overlooked option? I've searched a lot but could not find an answer, but this could be related to me not knowing all the technical terms yet. Sorry if this is answered 1000 times before.
Ideally i would change my domain model to include a Author property of type UserProfile and load that data as well using a JOIN (Comment table and User table)
public class Comment
{
public int Id { get; set; }
public String CommentText { get; set; }
public int PostId { get; set; }
public UserProfile Author { get; set; }
}
EDIT : As per the questions in the comment
This is how i will do this.
My Repositary method will have these methods
List<Comment> GetCommentsForPost(int postId);
BlogPost GetPost(int postId);
I would have ViewModel for representing a single blog post like this
public class PostViewModel
{
public int PostID { set;get;}
public string PostText { set;get;}
public string AuthorDisplayName { set;get;}
public List<CommentViewModel> Comments { set;get;}
public PostViewModel()
{
Comments=new List<CommentViewModel>();
}
}
public class CommentViewModel
{
public int CommentID {set;get;}
public string Text { set;get;}
public string AuthorDisplayName { set;get;}
}
Now in your GET Action, Get the data from your Repositary and Map that to ViewModel and send it to view
public ActionResult ViewPost(int id)
{
var post=repositary.GetPost(id);
if(post!=null)
{
PostViewModel vm=new PostViewModel { PostID=id };
vm.PostText=post.Name;
var comments=repo.GetCommentsForPost(id);
foreach(var item in comments)
{
vm.Comments.Add(new CommentViewModel { CommentID=item.Id,
AuthorDisplayName=item.Author.FirstName});
}
return View(vm);
}
return View("NotFound");
}
Now your view will be strongly typed to The PostViewModel
#model PostViewModel
<h2>#Model.PostText</h2>
#Html.Partial("Comments",Model.Comments)
And your partial view(Comments.cshtml) will be strongly typed to a collection of CommentViewModel
#model List<CommentViewModel>
#foreach(var item in Model)
{
<div>
#item.Text
<p>Written by #item.AuthorDisplayName</p>
</div>
}
Now our views are not depending directly to Domain models. This allows us to bring data from another source tomorrow if we need (Ex :Get comments from a web service) and simply map to our view model.
Some notes
Do not add too much of code to Views. Let's keep it pure HTML as much as possible. No data access calls directly from Views!
I manually mapped the domain model to viewmodel for your understanding. You may use a mapping library like Automapper to do so. Also you may move part of the code we have in the GET action method to another servier layer so that it can be reused in multiple places.
I'm getting started with the concept of mapping domain models to view models in ASP.NET MVC after watching a recommendation to do this to pass specific viewModels to the views.
I've been able to manage a basic mapping of one domain model to a simpler viewmodel with less properties but now need to produce a more complex viewmodel and can't figure it out. I have the following domain models
public class Club
{
public int ClubID { get; set; }
public string FullName { get; set; }
public string Description { get; set; }
public string Telephone { get; set; }
public string URL { get; set; }
public DateTime CreatedDate { get; set; }
public virtual ICollection<Member> Members{ get; set; }
}
public class Member
{
public int MemberID{ get; set; }
public string Name { get; set; }
public MemberType Membership{ get; set; }
public virtual Club Club { get; set; }
public virtual int ClubID { get; set; }
}
public enum MemberType
{
Standard,
Special,
Limited
}
I want to map to a view model such as this (note: I've split it like this because I think it makes sense but I'm not sure)...
public class ClubDetailsViewModel
{
public int ClubID { get; set; }
public string FullName { get; set; }
public string Description { get; set; }
public IList<ClubDetailsMemberSummaryViewModel> Members { get; set; }
}
public class ClubDetailsMemberSummaryViewModel
{
public MemberType Membership { get; set; }
public int MemberCount { get; set; }
}
What I'm trying to end up with is a page which displays some of the club details plus a summary report of the member types at the club with a count of the members. Such as:
Some Club Name
Description of the club.....
CLUB MEMBERS
Limited - 15
Standard - 100
So I think the viewmodel makes sense for this (although might be a better way to do it). Where I'm struggling is how to map the elements. I can get the Club to map the main fields to the club viewmodel but really can't work out how to map the result of the list of clubs onto their view model and then add that to the main view model as a list.
I'm getting the clubs from my repository using this
var clubs = _clubRepository.GetClubByID(ID);
Then I can transform the Courts which are returned using an include in the data access layer from entity framework using this
var grpCourts = from c in clubs.Members
group c by c.Membership into grp
select new { st = grp.Key, count = grp.Distinct().Count() };
How would I loop through the resulting records and map those to the ClubDetailsMemberSummaryViewModel and then add the list of those to the main ClubDetailsViewModel?
Your mapping from Club to ClubDetailsViewModel will be trivial with the exception of Members. For that property, you could write a quick resolver inline or write your own custom resolver. An inline resolver would look something like this:
Mapper.CreateMap<Club, ClubDetailsViewModel>()
.ForMember(dest => dest.Members, opt => opt.ResolveUsing(src =>
{
return src.Members
.GroupBy(m => m.Membership)
.Select(grp => new ClubDetailsMemberSummaryViewModel
{
Membership = grp.Key,
MemberCount = grp.Distinct().Count()
});
}));
I think it's good practice to refactor more complex resolvers like this out to their own classes:
public class MembershipClubDetailsResolver : ValueResolver<Club, IList<ClubDetailsMemberSummaryViewModel>>
{
protected override IList<ClubDetailsMemberSummaryViewModel> ResolveCore (Club source)
{
return source.Members
.GroupBy (m => m.Membership)
.Select(grp => new ClubDetailsMemberSummaryViewModel
{
Membership = grp.Key,
MemberCount = grp.Distinct().Count()
})
.ToList();
}
}
And then use that resolver in your mapping:
Mapper.CreateMap<Club, ClubDetailsViewModel>()
.ForMember(dest => dest.Members, opt => opt.ResolveUsing<MembershipClubDetailsResolver>());
Your mapping appears to be rather complex, I think I would use the .ConvertUsing method of automapper
Mapper.CreateMap<List<Club>,List<ClubDetailsViewModel>>()
.ConvertUsing<ClubToClubDetailsViewModel>();
The conversion class has the following inheritance
public class ClubToClubDetailsViewModel: TypeConverter<List<Club>,List<ClubDetailsViewModel>>
{
....
}
Alternatively you can tinker with creating two "simple" mappings
Mapper.CreateMap<Club,ClubDetailsViewModel>()
That will map everything except the property called Members
Then you need to create a mapping for the members to ClubDetailsMemberSummaryViewModel, you can do that mapping manually or you can configure this in automapper aswell.
For more specific details on automapper you can visit https://github.com/AutoMapper/AutoMapper/wiki
i'd like to know how can I get a property like an entity, for example:
My Model:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public Category Category { get; set; }
}
View:
Name: <%=Html.TextBoxFor(x => x.Name) %>
Category: <%= Html.DropDownList("Category", IEnumerable<SelectListItem>)ViewData["Categories"]) %>
Controller:
public ActionResult Save(Product product)
{
/// produtct.Category ???
}
and how is the category property ? It's fill by the view ? ASP.Net MVC know how to fill this object by ID ?
Thanks!
This is one of the reasons why it's bad to bind directly to entities. Consider
public class ProductForm {
public int Id { get; set; }
public string Name { get; set; }
public string CategoryId { get; set; }
}
public ActionResult Save(ProductForm form)
{
var product = new Product
{
Id = form.Id,
Name = form.Name,
Category = database.GetCategory(form.CategoryId)
};
}
In case of view models as above, it may be OK to use custom model binders to automatically get entities by Id from database. See here for sample implementation (in S#arp Architecture) that binds IDs to entities from database. But I think for now you better go with simpler implementation like above.
You can also use AutoMapper to simplify form->entity mapping.