问题
I am trying to list single product details in a view. The product specification changes dynamically because specifications are added row-wise in table, which means we can add huge number of specifications for each product (as done in ecommerce sites). Right now I am able to meet the requirement using ViewBag
, but I am deciding to use ViewModel
as a better practice.
Model class:
// Product:
public partial class ProductTable
{
public ProductTable()
{
this.SpecificationsTable = new HashSet<SpecificationsTable>();
}
public int ProductID { get; set; }
public string Title { get; set; }
public string SmallDescription { get; set; }
public string FullDescription { get; set; }
public virtual ICollection<SpecificationsTable> SpecificationsTable { get; set; }
}
//Specifications:
public partial class SpecificationsTable
{
public int SpecificationsID { get; set; }
public string SpecificationName { get; set; }
public string SpecificationValue { get; set; }
public Nullable<int> ProductID { get; set; }
public virtual ProductTable ProductTable { get; set; }
}
ViewModel:
public class DetailsViewModel
{
public int ProductID { get; set; }
public string Title { get; set; }
public string SmallDescription { get; set; }
public string FullDescription { get; set; }
public string SpecificationName { get; set; }
public string SpecificationValue { get; set; }
}
ActionMethod
public ActionResult ProductDetails(int id)
{
var details = (from c in dbo.ProductTable
join s in dbo.SpecificationsTable
on c.ProductID equals s.ProductID
where c.ProductID == id
select new DetailViewModel
{
Title = c.Title,
SmallDescription = c.SmallDescription,
FullDescription = c.FullDescription
}).ToList();
// To remove repeated product title , small and full description
var distinctItems = details.GroupBy(x => x.ProductID).Select(y => y.First());
// To show product title, small and full description for this product
ViewBag.ProductDetails = distinctItems;
var specifications = (from c in dbo.ProductTable
join s in dbo.SpecificationsTable
on c.ProductID equals s.ProductID
where c.ProductID == id
select new DetailViewModel
{
SpecificationName = s.SpecificationName,
SpecificationValue = s.SpecificationValue
}).ToList();
// To show list of specifications for this product
ViewBag.Specifcations = specifications;
return View();
}
expected output in view:
Details:
Title: New Samsung offer
SmallDescription : Something small
FullDescription : Something full
Specifcations:
Mobile Name :Samsung
Model : 2015
Price : 70 $
Color: White
I am using database first method and I am trying to learn how we can use view model here.
回答1:
You current view model does not reflect what you want to display in the view, which is multiple specification for each product, so you need a collection property. Change your view models to
public class SpecificationVM
{
public string Name { get; set; }
public string Value { get; set; }
}
public class ProductVM
{
public string Title { get; set; }
public string SmallDescription { get; set; }
public string FullDescription { get; set; }
IEnumerable<SpecificationVM> Specifications { get; set; }
}
Then in the controller, populate you view model using
public ActionResult ProductDetails(int id)
{
var product = db.ProductTable.Where(p => p.ProductID == id).FirstOrDefault();
// note you may need to add .Include("SpecificationsTable") in the above
if (product == null)
{
return new HttpNotFoundResult();
}
ProductVM model = new ProductVM()
{
Title = product.Title,
SmallDescription = product.SmallDescription,
FullDescription = product.FullDescription,
Specifications = product.SpecificationsTable.Select(s => new SpecificationVM()
{
Name = s.SpecificationName,
Value = s.SpecificationValue
})
};
return View(model);
}
Then in the view
@model yourAssembly.ProductVM
<h2>Details</h2>
@Html.DisplayNameFor(m => m.Title)
@Html.DisplayFor(m => m.Title)
.... // ditto for SmallDescription and FullDescription
<h2>Specifications</h2>
@foreach(var item in Model.Specifications)
{
@Html.DisplayFor(m => item.Name)
@Html.DisplayFor(m => item.Value)
}
回答2:
Viewmodels are indeed the way to go here. I won't elaborate on what viewmodels are as you already have some declared in your code so I'm assuming you already know their purpose and functionality. However—for the sake of completeness—viewmodels represent only the data that you want displayed in your view; assuming a view displaying the first and last name of an employee, there's no reason to send back an Employee
object when you only really need two of it's properties.
As per the short viewmodel definition provided above, to return a viewmodel instead of attaching the returned object on ViewBag
, you simply create a class which will only hold the data that you want presented in the view. So, based on your "expected output in view", your viewmodel would look similar to this:
public class YourViewModel
{
public string Title { get; set; }
public string SmallDescription { get; set; }
public string FullDescription { get; set; }
public string MobileName { get; set; }
public int ModelYear { get; set; }
public decimal Price { get; set; }
public string Color { get; set; }
}
Once you have your viewmodel instantiated and populated, pass it to the view:
var yourViewModel = new YourViewModel();
//populate it and pass it to the view
return View(yourViewModel);
On the top part of your view, declare your @model
variable to be of type YourViewModel
:
@model YourViewModel
..and you're good to go. So if you wanted to print out the mobile name in the view:
@Model.MobileName
Bear in mind that while you can only have one @model
per view, you can still have multiple viewmodels. This is possible by creating a parent viewmodel to hold all the view-related viewmodels and setting that as the @model
instead:
public class ParentViewModel
{
//all the viewmodels which are relevant to your view
public DetailsViewModel DetailsViewModel { get; set; }
public YourViewModel YourViewModel { get; set; }
//...etc
}
Once you have your viewmodel instantiated and populated, pass it to the view:
var parentViewModel = new ParentViewModel();
var yourViewModel = new YourViewModel();
//populate it and attach it to the parent viewmodel
parentViewModel.YourViewModel = yourViewModel;
return View(parentViewModel);
This time, on the top part of your view, declare your @model
variable to be of type ParentViewModel
instead:
@model ParentViewModel
..and you're good to go. Using the same example, if you wanted to print out the mobile name in the view:
@Model.YourViewModel.MobileName
Bear in mind that I haven't really given much attention to how you have structured your viewmodels, but rather explained how to pass one (or more) viewmodels back to your view and use those instead of ViewBag
(as per your question). For how your viewmodels should be actually populated and look like, Stephen Muecke's answer is the way to go.
来源:https://stackoverflow.com/questions/31854913/asp-mvc-list-product-details-in-view-using-view-model