How to have a view model which contains two models - c#

In a view i need a view model which contains two models. I dont know how to retrieve data in the repository.
There is a Products model which has a foreign key to another model.
Here is my PRODUCTS model:
public class Products
{
public int ProductId {get; set;}
public string Productname {get; set;}
public int ProductTypeID { get; set; }
[ForeignKey("ProductTypeID")]
public virtual ProductTypes ProductTypes { get; set; }
}
And this is my PRODUCTTYPE model:
public class ProductTypes
{
public int ProductTypeId {get; set;}
public int ProductTypeName {get; set;}
}
Now I want to use them in a View, so I made a viewmodel
public class ProductsViewModel
{
public Products Products { get; set; }
public IEnumerable<ProductTypes> ProductTypes { get; set; }
}
Here is my problem. I don't know how to retrieve data from a viewmodel in a repository like this:
public async Task<IEnumerable< XXX >> GetAllProducts()
{
return await _RepositoryContext.Set<xxx>.ToListAsync();
}
Finally this is my Controller:
public async Task<IActionResult> index()
{
return View(await ProductRepository.GetAllProducts());
}

In your repository you use include expression like this
public async Task<IEnumerable< Products >> GetAllProducts()
{
var products = _RepositoryContext.Set<Products> ();
return await products.include(x =>x.ProductType).ToListAsync();
}
But I think you need to modify your models. you are returning domain classes in controller as view Model which is not a good idea, you should have separate model classes and should use Automapper to map domain to model classes so that on client side you send only model classes which may have many extra columns than domain classes . Here is how your model classes should look like
public class Products
{
public int ProductId {get; set;}
public string Productname {get; set;}
public int ProductTypeID { get; set; }
[ForeignKey("ProductTypeID")]
public virtual ProductTypes ProductTypes { get; set; }
}
public class ProductTypes
{
public int ProductTypeId {get; set;}
public int ProductTypeName {get; set;}
}
Now models should be like this
public class ProductsModel
{
public int ProductId {get; set;}
public string Productname {get; set;}
public int ProductTypeID { get; set; }
public virtual ProductTypesModel ProductTypes { get; set; }
public string ProductTypeName {get; set;}
}
public class ProductTypesModel
{
public int ProductTypeId {get; set;}
public int ProductTypeName {get; set;}
}
Now repository method should look like this
public async Task<IEnumerable< Products >> GetAllProducts()
{
var products = _RepositoryContext.Set<Products> ();
return await products.include(x =>x.ProductType).ToListAsync();
}
Finally this would be your controller Controller,
public async Task<IActionResult> index()
{
var productList= await ProductRepository.GetAllProducts()
var ProductModels = Mapper.Map<IEnumerable<Products>, IEnumerable<ProductsModel>>( productList)
return View(ProductModels);
}
To Know how to setup AutoMapper please refer to my this post, How to setup Automapper in ASP.NET Core

It all depends on your architecture. However, Repository might not be the best place to do the mapping between models and viewmodels because once the ORM is changed the repository will possibly be modified too while this mapping could be somewhere to avoid unnecessary changes. I personally prefer to do the mapping either in BLL or in viewmodel itself without using AutoMapper. Here is one possible way of mapping in viewmodels:
public class ProductsDto
{
public int ProductId { get; set; }
public string Productname { get; set; }
public virtual ProductTypeDto ProductTypes { get; set; }
public void SetDto(Products obj)
{
if (obj == null)
return;
this.ProductId = obj.ProductId;
this.Productname = obj.Productname;
if (obj.ProductTypes != null)
{
this.ProductTypes = new ProductTypeDto();
this.ProductTypes.SetDto(obj.City);
}
}
}
public class ProductTypeDto
{
public int ProductTypeId { get; set; }
public int ProductTypeName { get; set; }
public void SetDto(ProductType obj)
{
if (obj == null)
return;
this.ProductTypeId = obj.ProductTypeId;
this.ProductTypeName = obj.ProductTypeName;
}
}
Using viewmodels:
public ProductsDto GetProductsDto(Products obj)
{
var dto = new ProductsDto();
dto.SetDto(obj);
return dto;
}
----------------------------------------------------------
var products = _RepositoryContext.Products.ToList();
var productsDto = products?.Select(GetProductsDto);

It depends on what you actually want to do within your view: A view model is supposed to contain exactly the data necessary to display the view. Usually, you would not want to include more data there. So using database models inside of a view model is not the best choice; it would be better to design actual view models that match the stuff you display, and then you decide how to properly get that data.
From how your view model looks, I could assume two use cases:
You want to display a set of products, and list those product types that that set of products have.
You want to display a set of products, and a list of all product types.
Option 2 is really simple and requires you to only query each entity type once:
var viewModel = new ProductsViewModel
{
Products = await db.Products.ToListAsync(),
ProductTypes = await db.ProductTypes.ToListAsync(),
};
Option 1 can be solved naively by including the product types through the navigation property in the Product entity:
var products = await db.Products.Include(p => p.ProductType).ToListAsync();
var viewModel = new ProductsViewModel
{
Products = products,
ProductTypes = products.Select(p => p.ProductType).Distinct().ToList(),
};
This however has the downside that with few distinct product types you will be loading each product type multiple times. Because your product type has only an id and a name, this is not that problematic but for more complex types it can be.
A different approach would be to query the product type ids first from the list of products, and then loading the product types afterwards:
var products = await db.Products.Include(p => p.ProductType).ToListAsync();
var productTypeIds = product.Select(p => p.ProductTypeId).Distinct().ToList();
var viewModel = new ProductsViewModel
{
Products = products,
ProductTypes = await db.ProductTypes.Select(t => productTypeIds.Contains(t.Id)).ToListAsync(),
};
Two notes:
I ignored the existence of your repository here and just assumed that you are using an EntityFramework DbContext here. If you want to abstract that into a repository, you can do so.
The entity is named Products but only contains information about a single product. Similarly, the view model contains a property Products of that exact same type. That does not make a lot of sense to me, so I just assumed that the entity is called Product and represents a single item, while the ProductsViewModel has Products, a list of Product.

Related

Passing value from one model to another in a ViewModel

I am building my first MVC website and I am struggling to pass a value from one model to another in a ViewModel. The two classes are Music and MusicGenre. In Music, there is a field called 'GenreId' which is an int and is related to the 'Id' field in MusicGenres. I want to return the genre name relating to the Id that's passed over.
Here's what I've got so far:
MusicViewModel vmMusic = new MusicViewModel
{
Music = _context.Music.SingleOrDefault(c => c.Id == id),
MusicGenres = _context.MusicGenres.Where(gi => gi.Id == xxx).ToList()
};
return View(vmMusic);
This all renders nicely minus displaying the right genre. (I replaced xxx with Id but that just uses the id field from Music, not the GenreId field)
So xxx is where I want to pass 'GenreId' but I don't know how. Any help?
Thanks
To expand on my initial comment to the question, I'd just do something like:
var music = _context.Music.SingleOrDefault(c => c.Id == id);
var musicGenres = _context.MusicGenres.Where(gi => gi.Id == music.GenreId).ToList(); // whatever the genre id property is called.
MusicViewModel vmMusic = new MusicViewModel
{
Music = music,
MusicGenres = musicGenre
};
return View(vmMusic);
You should separate your view models from your persistence models. View model is designed to display what you want to display to the users. Persistence model might reflect your data storage structures.
Also from your example it looks like you might already have been using ORM like EntityFramework. EntityFramework provides features like navigation properties and lazy loading.
Either way, I strongly disagree accepted answer's approach, which sends the music and its genre persistence models to the view, when you only need to get the genre name.
Rather, I will approach it this way:
Persistence Models
You might have one-to-may relationship between a genre and an album in your persistence storage:
public class Album
{
public int AlbumId { get; set; }
public string Title { get; set; }
public double Price { get; set; }
public int GenreId { get; set; }
public Genre Genre { get; set; }
}
public class Genre
{
public int GenreId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Album> Albums { get; set; }
}
But obviously you don't want/need to reveal everything you have in your persistence storage to the user. Hence you create view models and only contain what you want to display.
View Models
Let's say you have a view to display information about the selected album. You might create a view model like this:
public class AlbumViewModel
{
public int AlbumId { get; set; }
public string AlbumName { get; set; }
public string Genre { get; set; }
}
Then in the album controller, you can build the view model by using any ORM, lazy loading, etc:
Controller
If you happen to use EntityFramework and have lazy loading enable, you can fetch genre name via its navigation property:
public ActionResult Details(int id)
{
var album = _dbContext.Albums.SingleOrDefault(x => x.AlbumId == id);
if (album == null)
{
return HttpNotFound();
}
var vm = new AlbumViewModel
{
AlbumId = album.AlbumId,
AlbumName = album.Title,
// You can get the genre name via Navigation property, and/or lazy
// loading
Genre = album.Genre.Name
};
return View(vm);
}
Now in a more advanced architecture, the read and write is separated, which is
referred as CQRS. For all the reads (i.e., displaying information to the user), you can build your view model with data directly from executing plain SQL statement.
CQRS with Dapper
using Dapper;
using System.Data;
...
public class AlbumController : Controller
{
private readonly IDbConnection _dbConnection;
public AlbumController(IDbConnection dbConnection)
{
_dbConnection = dbConnection;
}
public ActionResult Details(int id)
{
const string sql = #"
SELECT a.AlbumId, a.Title AS [AlbumName], g.Name AS [Genre]
FROM [Album] a
JOIN [Genre] g ON a.GenreId = g.GenreId
WHERE a.AlbumId = #albumId;
";
var vm = _dbConnection.QuerySingleOrDefault<AlbumViewModel>(sql, new {
albumId = id
});
if (vm == null)
{
return HttpNotFound();
}
return View(vm);
}
}

Entity framework cannot seem to create relationship

The entity framework isn't super new to me, however is confusing as I continue to expand my data models. I am attempting to create a class that has an array of another class. Class A or County.cs has a list of Class B or Product.cs
I cannot seem to create write these classes in a way that when you ask for context.counties you also get the array of products attached to it.
Class A or County.cs
public class County
{
[Key]
public int ID { get; set; }
public String Name { get; set; }
public List<Product> Products { get; set; } = new List<Product>();
[NotMapped]
public DateTime firstAppearance {
get {
var data = (from obj in Products orderby obj.Date descending select obj.Date).FirstOrDefault();
if (this.softwareIn)
{
return data;
}
else
{
var date = new DateTime(1,1,1);
return date;
}
}
set {
this.firstAppearance = value;
}
}
[NotMapped]
public bool softwareIn {
get {
return Products.Count() >= 1;
}
set {
this.softwareIn = value;
}
}
}
Class B or Product.cs
public class Product
{
[Key]
public int ID { get; set; }
public String Name { get; set; }
public DateTime Date { get; set; }
[NotMapped]
public DateTime DateUtc {
get {
return getUtcDate();
}
set {
this.DateUtc = value;
}
}
public DateTime getUtcDate() {
return this.Date.ToUniversalTime();
}
}
I just don't understand and haven't created enough of 1:M relations in the entity framework. Why cannot I do something like this and have it work all the time? The first time I run this I get the type of data I expect, the xx county has a product. However if I remove all this and just return the context.counties I get nothing in the products array.
[Route("Counties")]
public object GetCounties() {
var data = new County() {
Name = "xxx",
};
data.Products.Add(new Product() { Name="Cool Software", Date = DateTime.Now});
db.Counties.Add(data);
db.SaveChanges();
var da = db.Counties.ToList();
return db.Counties;
}
The reason you have having this issue is because the foreign keys are not correctly configured. Take a look at your database and look at the foreign keys. For Entity Framework to understand the relationships properly, you must mark related entities as virtual. So do this:
public virtual List<Product> Products { get; set;}
And then in your Product class add the navigation property back to the parent County:
public virtual County County { get; set;}
I found this tutorial really good:
http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx
Hope this helps.

AutoMapper - flatten domain model to view model. One is to many relationship

I have a complex domain model which has many relationships with other entities in the system.
For the purpose of editing this model I want to set up a view model that simplifies things. I want to break up the model into smaller logical chunks with edit screens for each part instead of trying to represent the whole model on one screen.
In the domain model where I have a one is to many relationship it is represented like this:
public partial class CbItemsContent
{
public CbItemsContent()
{
this.cbItemsContentRegulators = new HashSet<cbItemsContentRegulator>();
}
public int ItemContentId { get; set; }
public int ItemID { get; set; }
......
public virtual CbItem CbItem { get; set; }
public virtual ICollection<cbItemsContentRegulator> cbItemsContentRegulators { get; set; }
}
cbItemsContentRegulator is another set of entities that are owned by CbItemsContent (the model shown above)
I would like to replace the Collection of cbItemsContentRegulators in my model with a simplified viewModel called ItemContentRegulatorsViewModel like this:
public class ItemContentRegulatorsViewModel
{
public int ItemContentId { get; set; }
public int[] RegulatorIds { get; set; }
}
which reduces the relationship to the ItemContent parent ID and an int array if Regulator IDs
is what I am trying to do possible?
How do I Map My collection of CbItemsContentRegulators to an int[] of Ids
Assuming that you're using Entity Framework, you should change the view model to
public class ItemContentRegulatorsViewModel
{
public int ItemContentId { get; set; }
public IList<int> RegulatorIds { get; set; } // IList
}
No you can define the mapping:
var profile = Mapper.CreateProfile("SomeName");
profile.CreateMap<CbItemsContent,ItemContentRegulatorsViewModel>()
.ForMember(dest => dest.RegulatorIds,
m => m.MapFrom(src => src.cbItemsContentRegulators.Select(c => c.RegulatorId)));
Now you can use it in a query like:
context.CbItemsContents.ProjectTo<ItemContentRegulatorsViewModel>()
With RegulatorIds as an array this would throw an exception that ToArray is not recognized.

Populate associated entities on DBContext Find

I have a Product which needs to have some fields in multiple languages. Therefore I have made a ProductLanguage table which has the composite key and language specific fields (ProductID, LanguageID, Name).
In my Product class I tried something like this:
[Table("Product")]
public class Product
{
DBContext db = new DBContext();
public Product()
{
this.Multimedias = new List<Multimedia>();
this.ProductLanguages = new List<ProductLanguages>();
this.ProductLanguage = db.ProductLanguages.Find(this.ID, Global.Language) ?? new ProductLanguage();
}
public int ID { get; set; }
public string Code { get; set; }
public virtual ICollection<Multimedia> Multimedias { get; set; }
public virtual ICollection<ProductLanguage> ProductLanguages { get; set; }
[NotMapped]
public virtual ProductLanguage ProductLanguage { get; set; }
}
So I could immediately access the language specific fields without needing to go through the collection - the problem is the object obviously doesn't have the ID yet.
Is there any way so when I do
Product product = db.Products.Find(id);
in the controller it will automatically populate my ProductLanguage property?
You can move your assignment of ProductLanguage in the Get for that property.
You don't need the property
[NotMapped]
public virtual ProductLanguage ProductLanguage { get; set; }
The ProductLanguages collection will be populated via lazy loading when you hit it. What you need is a method like this, that will return a ProductLanguage by id:
public ProductLanguage GetProductLanguageById(int id)
{
if (ProductLanguages != null)
{
return ProductLanguages.Where(pl => pl.Id == id).FirstOrDefault();
}
}
This is not confirming to any practice/usage i have seen before.., regardless I would consider it very!! very!! bad practice. Don't make an instance of your context within the Entity(table).
Also why are you doing this... I suggest you read up on Lazy and eager loading.
Quote
"in the controller it will automatically populate my ProductLanguage property?"
Yes.... use eager loading.
Product product = db.Products.Include("ProductLanguage").Find(id);
But I would highly suggest that you don't do all that other weird stuff in the initialization of your entity.
DBContext db = new DBContext();
Product product = db.Products.Include("ProductLanguage").Find(id);

How to do Lists in a view model? + dataannotations

I am using asp.net mvc 3, data annotations and auto mapper.
I want to have all my annotations on properties in my view model once the properties pass validation I use auto mapper to map it back to my domain object.
I have a viewmodel that has properties that I want to have a collection of since I want to generate a table from them. I also want to use them later to use as a form to add rows to this table.
So what do I do? How do I take these properties and make a collection of them?
public class UserViewModel()
{
[Required()]
public string UserName = {get; set;}
[Required()]
public string FirstName = {get; set;}
[Required()]
public string LastName = {get; set;}
}
I want to use these properties to generate my table and be used for a form.
The only thing I can think of is doing this
public class AddUserViewModel()
{
[Required()]
public string UserName = {get; set;}
[Required()]
public string FirstName = {get; set;}
[Required()]
public string LastName = {get; set;}
}
public class UserViewModel()
{
public List<User> Users {get; set;}
public AddUserViewModel {get; set;}
public UserViewModel()
{
Users = new List<Users>();
}
}
So basically I have it as a separate view model that is enclosed into another viewmodel that contains a list of Users(my domain model)
That way I use my domain model to generate the table and my AddUserViewModel for my adding of users.
Seems kinda redundant so I am not sure if there is a better way.
Edit
I have something like this
var viewModel = new UserViewModel();
List<Users> users= UserService.GetAllUsers();
viewModel = Mapper.Map<Users, UserViewModel>(users);
return View("Index", viewModel);
I also tried
var viewModel = new UserViewModel();
List<Users> users= UserService.GetAllUsers();
viewModel.AddUserViewModel = Mapper.Map<Users, AddUserViewModel>(users);
return View("Index", viewModel);
Edit 2
I have this and it compiles but I get this error
SomeViewModel viewModel = new SomeViewModel ();
List<User> users= userService.GetAllUsers();
viewModel.UserViewModel = Mapper.Map<List<User>, List<UserViewModel>>(users);
return View("Index", viewModel);
Trying to map Domain.User to ViewModels.UserViewModel.
Missing type map configuration or unsupported mapping.
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown.
Why would you want to return a list of domain objects in your view model? That's not what view models are supposed to be. View models should reference only other view models. So you have a nice UserViewModel which represents a user. Now you need to work with multiple users in your view, so either you pass an IEnumerable<UserViewModel> or if you need some other properties you design a view model for this:
public class UserViewModel
{
[Required]
public string UserName = { get; set; }
[Required]
public string FirstName = { get; set; }
[Required]
public string LastName = { get; set; }
}
public class SomeViewModel
{
public List<UserViewModel> Users { get; set; }
public string SomeOtherProperty { get; set; }
}
and now your controller action might look like this:
public ActionResult Foo()
{
SomeModel model = _repository.GetModel();
SomeViewModel viewModel = Mapper.Map<SomeModel, SomeViewModel>(model);
return View(viewModel);
}
Now inside your view you could simply use a display template for this Users property (Html.DisplayFor(x => x.Users)) to show a list of them.
UPDATE:
After seeing your update here's how to proceed in terms of good practices:
public ActionResult Foo()
{
IEnumerable<Users> users = _repository.GetUsers();
IEnumerable<UserViewModel> usersViewModel = Mapper
.Map<IEnumerable<Users>, IEnumerable<UserViewModel>>(users);
return View(usersViewModel);
}
I've also used an AutoMap attribute in a sample project which could simplify your code to this:
[AutoMap(typeof(IEnumerable<Users>), typeof(IEnumerable<UserViewModel>))]
public ActionResult Foo()
{
IEnumerable<Users> users = _repository.GetUsers();
return View(users);
}
This attribute will automatically run after the controller action and before the view is rendered and would use AutoMapper to replace the model with the corresponding view model.

Categories

Resources