4

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.

ryanyuyu
  • 6,366
  • 10
  • 48
  • 53
Shaiju T
  • 6,201
  • 20
  • 104
  • 196
  • 2
    Bit unclear what your asking. You have a view model which does not represent what you want to display. It would need properties `Title`, `SmallDescription`, `FullDescription` and `IEnumerable` where `SpecificationViewModel` contains properties `SpecificationName` and `SpecificationValue` –  Aug 06 '15 at 12:18
  • @StephenMuecke , i have designed using [this](http://dba.stackexchange.com/questions/62271/database-design-for-ecommerce-website-with-multiple-products-having-huge-specifi) post and product specifications changes dynamically i have explained [here](http://stackoverflow.com/questions/31827767/asp-mvc-list-ecommerce-product-with-dynamically-changing-specifications) hope that helps, thank you. – Shaiju T Aug 06 '15 at 12:45
  • 1
    Yes I understand what your trying to do. What I am saying is your view model is wrong (and so are your queries for populating it). Give me 30 minutes and I'll add an answer showing what you need to do. –  Aug 06 '15 at 12:47
  • @StephenMuecke,yes sure, thank you. – Shaiju T Aug 06 '15 at 12:49

2 Answers2

4

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.

Community
  • 1
  • 1
trashr0x
  • 6,457
  • 2
  • 29
  • 39
  • my specification changes for different products, its not just mobiles, if clothing other specification name and value comes instead, i have explained [here](http://stackoverflow.com/questions/31827767/asp-mvc-list-ecommerce-product-with-dynamically-changing-specifications) hope that helps, thank you. – Shaiju T Aug 06 '15 at 12:47
  • 2
    This is why I have also showed you how to pass more than one viewmodels back to the view as an example. You need to reorganise and restructure your viewmodels. What Stephen Muecke suggested in the comments of your question is the way to go. – trashr0x Aug 06 '15 at 12:57
4

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)
}
  • minor typos: close the two `.Select()` :) – trashr0x Aug 06 '15 at 13:18
  • Oops. Thanks @trashr0x :) –  Aug 06 '15 at 13:23
  • thank you for your time, i get this error `'myproject.Models.ProductTable' does not contain a definition for 'Select' and no extension method 'Select' accepting a first argument of type 'myproject.Models.ProductTable' could be found (are you missing a using directive or an assembly reference?)` near the `product.Select` – Shaiju T Aug 06 '15 at 13:30
  • Have you included `using System.Linq;` –  Aug 06 '15 at 13:31
  • yes its included, but i have not included `.Include("SpecificationsTable "` – Shaiju T Aug 06 '15 at 13:33
  • thank you so much for your time and patience, learned how to use now, i have to format the data, may be [this](http://stackoverflow.com/questions/1160420/how-do-i-group-data-in-an-asp-net-mvc-view) will help. were can i learn more on view models ? – Shaiju T Aug 06 '15 at 14:07
  • 1
    Not sure what you mean by _"i have to format the data"_. This link might help with understanding view models - [What is ViewModel in MVC?](http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc) –  Aug 06 '15 at 23:29