3

I am fairly new to MVC and trying to explore more into ViewModels. I have an product category page that I would like to display product categories as well as products and their associated images. I am going to simplify some of these tables and just focus on the logic of returning this data to the view. I have the view working with the populated dropdownlist, but I am not sure how to populate the ProductViewModel within the CategoryViewModel.

Database

Category Table

CategoryId
CategoryName
CategoryDescription

Product Table

ProductId
ProductName
ProductDescription
ProductPrice
CategoryId

ProductImage Table

ProductId
ProductImage1
ProductImage2
ProductImage3
ProductImage4
ProductImage5
ProductImage6
ProductImage7
ProductImage8
ProductImage9
ProductImage10

ViewModel

public class ProductViewModel
{
  public Product ProductVM { get; set; }
  public ProductImage ProductImageVM { get; set; }
}

public class CategoryViewModel
{
  public List<Category> Category { get; set; }
  public List<ProductViewModel> Products { set;get;} 
}

Controller

public ActionResult Index()
{
    var model = new CategoryViewModel();
    model.Category = db.Categories.OrderBy(d => d.CategoryName).ToList();
    model.Products = from p in db.Products
                             join pi in db.ProductImages on p.ProductId equals pi.ProductId
                             orderby p.ProductPrice descending

    return View(model);
}

View

@model CategoryViewModel

@Html.DropDownListFor(x => x.CategoryId, new SelectList(Model.Category, "CategoryId", "CategoryName"), "View all Categories")

<table>
@foreach (var product in Model.Products)
    {
        <tr>
            <td>@item.ProductImage.ProductImage1</td>
            <td>@item.Product.ProductName</td>
            <td>@item.Product.ProductPrice</td>
            <td>@item.Product.ProductDescription</td>
        </tr>
    }
</table
2
  • Is there any reference like a foreign key to join category with product. As in MVC, we create the template look of database tables as models and finally by use of services(in java) to retrieve the model Commented Oct 31, 2018 at 18:29
  • The Product table has the FK CategoryId. However, my example does not need a join on the Category and Product table. Commented Oct 31, 2018 at 18:30

2 Answers 2

3

Whenever I'm using ViewModels, I also develop a Service class to populate it. This helps keep my controller clean and my logic isolated.

First, create a folder named "Services" to contain these classes. If using Areas, create the folder at the same level in the project hierarchy as the Controller using it.

Then, create a "Service" class in that folder. For example, I would create a class named CategoryService since the ViewModel is named CategoryViewModel.

In this class, I would put the code to intialize the ViewModel:

public class CategoryServices
{
    private MyDbContext db = new MyDbContext();

    internal CategoryViewModel GetCategoryViewModel(){
        return new CategoryViewModel(){
            Category = GetCategories(),
            Products = GetProductViewModel()
        };
    }

    internal List<Category> GetCategories(){
        return db.Categories.OrderBy(d => d.CategoryName).ToList();
    }

    internal List<ProductViewModel> GetProductViewModel(){
        return db.Products.Select(x => new ProductViewModel()
        {
            ProductVM = x,
            ProductImageVM = x.ProductImage
        });
    }
}

Now, you can easily retrieve the ViewModel from your controller:

public ActionResult Index()
{
    CategoryService service = new CategoryService();
    return View(service.GetCategoryViewModel());
}

On your view, you'll have to update your model references to handle what is in the ViewModel.

@model CategoryViewModel

@Html.DropDownListFor(x => x.CategoryId, new SelectList(Model.Category, "CategoryId", "CategoryName"), "View all Categories")

<table>
@foreach (var item in Model.ProductViewModels)
    {
        <tr>
            <td>@item.ProductImage.ProductImage1</td>
            <td>@item.Product.ProductName</td>
            <td>@item.Product.ProductPrice</td>
            <td>@item.Product.ProductDescription</td>
        </tr>
    }
</table

This should set you in the general direction that you need to go. Feel free to leave if a comment if you have a question on anything above, and I'll try to clarify.

EDIT: Also, I would recommend breaking down the functions in your service class even further. I avoided that here because I didn't want to provide you with a 10+ function class.

EDIT2: Updated GetProductViewModel() function. Since there is a 1-to-1 relationship between the Product and ProductImage models, and ProductImage has a foreign key on ProductId referencing Product's ProductId, ProductImage should be available as a child entity on the Product model.

Because of this, you can use a handy lambda expression to generate the list of ProductViewModels in one database trip. I've used this lambda expression to generate many lists, but you may need to modify it to work correctly.

Sign up to request clarification or add additional context in comments.

5 Comments

I see for each iteration of a product, you are making a roundtrip to the database for the corresponding images. Is this the most efficient way?
No, definitely not. If your model is set up correctly, you would not need to do that at all. But, you didn't post your model code so I assumed that there are no foreign keys linking the tables together. If you do have a foreign key on ProductImage, you could significantly improve that. I also wasn't sure if it is a guaranteed 1-to-1 relationship or 1-to-many between Product and ProductImage.
Sorry for the lack of clarity, Product and ProductImage have a 1-to-1 relationship and ProductImage has a FK of ProductId which links to the PK of ProductId in the Product table.
Oh, that's much simpler then. Let me update my answer.
@BrianSalta I've updated the code to use a lambda expression to generate the ViewModel in one DB trip. I didn't test it since I don't have the model code, but I'll try to help you if you run into any errors from it.
1

I take a similar approach to Jack, except I separate it out further.

Seems like overkill (and might be for some cases), but it sets you up for much more flexibility down the road.

I'd create two classes:

  • ProductViewModelFactory (creates a ProductViewModel)
  • CategoryViewModelFactory (creates a CategoryVieWModel)

ProductViewModelFactory can internally use any number of repositories, services, or other necessary view model factories to build its data.

In this case, ProductViewModelFactory would internally call the CategoryViewModelFactory to create the CategoryViewModel. ProductViewModelFactory would also probably call a ProductRepository or ProductService and map the returned Product, retrieved from some sort of persitent storage, to a view model.

It might call a few other things too - if it doesn't now, good chance it will later as more features are added.

CategoryViewModelFactory would probably call a CategoryService or CategoryRepository, and map that data into a CategoryViewModel.

So, what advantages do these extra layers give us? After all, it's more work.

Sure, there's YAGNI, but this approach in my experience gives the most flexibility to handle unexpected requirements with the least amount of work.

The times where you thought YAGNI, but you just found out you really are gonna need it can cause some catastrophic situations - so IMHO, it's worth putting in an extra 20% of effort to create a simple consistent structure that in most cases ensures you're not painting yourself into a corner.

  1. ViewModelFactories are completely separate from the repositories or services that actually retrieve the data.

This means when an entirely different view is needed, you don't end up mucking around with your service or repository - these should return the rawest data possible.

They can also call other ViewModelFactories, which becomes handy since many times as an application grows you need to include other ViewModels inside a ViewModel inside a ViewModel inside a... you get the point.

  1. It becomes very simple to mock up a fake ViewModelFactory, Repository, or Service and inject it with dependency injection when you need to test something, or can't use the actual implementation for whatever reason.

This happens more than you'd think when: - there's a bug under certain conditions you need to test, - another dependent component is unfinished, - you need to return a specific set of data for a view for frontend development, - you want to create a series of tests, - you need to return specific data for a client demo, etc

  1. Working with any class in the application becomes simple since everything is a black box.

You don't have to care how a view model factory, service, or repository get their data or return it. You don't have to care what's consuming it outside the class you're currently working on.

If you need a chunk of data, you just inject its view model factory, service, or repository depending on what you need, pass some arguments, and everything works in a predictable, consistent pattern across the whole application.

So to summarize, here's the approximate lifecycle:

  1. Client makes request to controller action.
  2. Controller action calls a single view model factory to get data for the action's view.
  3. The view model factory calls any number of other services, repositories, or other view model factories (which in turn, can call their own services, repositories, and view model factories...)
  4. The view model factory returns the finished view model.
  5. The controller action takes the returned view model and shoves it into the view.
  6. The client gets the rendered HTML.

This can also give you some interesting options to automatically handle things based on naming conventions with action filters, resulting in very empty controller actions.

3 Comments

Great post! I think this opening sentence sums it up: "Seems like overkill (and might be for some cases), but it sets you up for much more flexibility down the road." The factory pattern can be a great tool that saves a lot of time down the road. However, it may indeed be overkill for someone who is just learning what a ViewModel is and does. That being said, I recommend @BrianSalta read more about the factory pattern and what it can do in MVC. Please note that link is for ViewFactories.
Also, to be thorough, take a look at this question which argues against using the factory pattern for ViewModels.
It's a good argument and one I agreed with about five years ago. However, its premise is that the viewmodel will never be shared. In my experience with apps being actively developed for 4+ years, at some point in the future, the viewmodel needs to be shared or have other cross cutting concerns in the majority of cases. Without the factory pattern, developers do precisely what makes sense given the circumstances - copy/paste, or blissfully rewrite the same logic with subtle differences, introducing bugs / maintenance issues. Costs more in the long run for MANY apps - not necessarily yours.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.