📱 ASP.NET Core MVC Mobile Shop – Part 4: Dynamic Home Page Settings & Layout Design


 

Introduction 

Welcome back to the Mobile Shop Website development series. In Part 4, we are moving beyond the basic setup to focus on the heart of our application: the Home Page Settings. A professional e-commerce site needs a clean, responsive, and dynamic landing page. In this tutorial, we will configure the Home Page UI, set up the layout, and ensure our ASP.NET Core MVC project is ready for product displays.

Step 1: Implementing the Product Service Pattern

In this section, we move the business logic from the Controller to a dedicated Service Layer. This is a Best Practice in ASP.NET Core Architecture, as it keeps our controllers thin and our code reusable.

Why use an Interface and Service?

By creating IProductService, we define a contract for our application. This allows us to fetch dynamic data for our Mobile Shop Home Page—such as Featured Products, New Arrivals, and Bestsellers—efficiently using Entity Framework Core.

The Code: IProductService.cs

Create a new folder named Services in your project root, then create a file named IProductService.cs and paste the following code:


C# / ProductService.cs

using Microsoft.EntityFrameworkCore;
using MobileShop.Data;
using MobileShop.Models;

namespace MobileShop.Services
{
    public interface IProductService
    {
        Task<List<Product>> GetFeaturedProductsAsync(int count = 8);
        Task<List<Product>> GetNewArrivalsAsync(int count = 8);
        Task<List<Product>> GetBestsellersAsync(int count = 8);
        Task<List<Brand>> GetBrandsAsync(int count = 6);
    }

    public class ProductService : IProductService
    {
        private readonly ApplicationDbContext _context;

        public ProductService(ApplicationDbContext context)
        {
            _context = context;
        }

        public async Task<List<Product>> GetBestsellersAsync(int count = 8)
        {
            return await _context.Products
                .Include(p => p.Brand)
                .Include(p => p.Reviews)
                .Where(p => p.IsBestseller && p.IsActive)
                .Take(count)
                .ToListAsync();
        }

        public async Task<List<Product>> GetFeaturedProductsAsync(int count = 8)
        {
            return await _context.Products
                .Include(p => p.Brand)
                .Include(p => p.Reviews)
                .Where(p => p.IsFeatured && p.IsActive)
                .Take(count)
                .ToListAsync();
        }

        public async Task<List<Product>> GetNewArrivalsAsync(int count = 8)
        {
            return await _context.Products
                .Include(p => p.Brand)
                .Include(p => p.Reviews)
                .Where(p => p.IsNewArrival && p.IsActive)
                .OrderByDescending(p => p.CreatedAt)
                .Take(count)
                .ToListAsync();
        }

        public async Task<List<Brand>> GetBrandsAsync(int count = 6)
        {
            return await _context.Brands.Where(b => b.IsActive == true).Take(count).ToListAsync();
        }
    }
}

Detailed Code Explanation

To understand how this service powers our Mobile Shop Website, let’s break down the key technical components:

1. Asynchronous Programming (async/await)

We use Task<List<...>> and async/await to ensure our application remains responsive. In modern web development, Asynchronous Programming in ASP.NET Core is essential for handling multiple user requests without blocking the server threads.

2. Eager Loading with .Include()

Notice the use of .Include(p => p.Brand). This is known as Eager Loading in Entity Framework Core. It ensures that when we fetch a product, its related Brand and Review data are loaded in a single database query, which improves performance and avoids the "N+1 query" problem.

3. LINQ Filtering for E-commerce

We are using LINQ (Language Integrated Query) to filter our data:

  • IsActive: Ensures only enabled products are shown to customers.

  • OrderByDescending: Used in GetNewArrivalsAsync to show the most recently added mobile phones first.

  • Take(count): Limits the number of items returned, which is perfect for designing a clean, high-speed Home Page UI.

4. Dependency Injection Readiness

The ProductService constructor accepts ApplicationDbContext. This is a classic example of Dependency Injection (DI). By injecting the database context, we make our service more modular and easier to maintain.

In the next part we will register this service in Program.cs and use it in our Home Controller to display these products on the frontend!</List<...>

Step 2: Registering the Product Service in Program.cs

Once the service and interface are created, we must tell the ASP.NET Core framework how to handle them. This is done in the Program.cs file, which is the entry point of our application.

The Code: Service Registration

Open your Program.cs file, find the area where services are being added (before builder.Build()), and add the following line:


C# / Program.cs (Service Registration)

// Register Custom Services for Dependency Injection
builder.Services.AddScoped<IProductService, MobileShop.Services.ProductService>();

Detailed Explanation

To build a high-performance Mobile Shop Website, understanding service lifetimes is essential. Here is why we use this specific code:

1. Understanding Dependency Injection (DI)

Dependency Injection is a design pattern that allows us to develop loosely coupled code. Instead of manually creating an object using new ProductService(), we ask the system to "inject" it. This makes our application easier to scale, test, and maintain.

2. Why AddScoped? (Service Lifetimes)

In ASP.NET Core, we have three main service lifetimes. For our Product Service, we use AddScoped:

  • AddScoped: This creates a new instance of the service for every HTTP Request. Once the request is finished (e.g., the page is loaded), the instance is disposed of. This is the best practice for database-related services like ours because it ensures the ApplicationDbContext is used efficiently within a single user session.

  • AddTransient: Creates a new instance every time the service is requested.

  • AddSingleton: Creates one single instance for the entire lifetime of the application.

3. Mapping Interface to Implementation

The syntax AddScoped<IProductService, ProductService>() tells the container:

"Whenever a class asks for IProductService, give it an instance of ProductService."

This allows you to change the internal logic of ProductService in the future without ever changing the code in your Controllers or Views.

Step 3: Connecting the Service to the Home Controller

In a professional ASP.NET Core MVC application, the Controller should not talk directly to the database. Instead, it calls the Service Layer. In this step, we use Constructor Injection to bring our IProductService into the HomeController.

The Code: HomeController.cs & HomeViewModel

Update your Controllers/HomeController.cs with the following code:


C# / HomeController.cs & ViewModel

using Microsoft.AspNetCore.Mvc;
using MobileShop.Models;
using MobileShop.Services;
using System.Diagnostics;

namespace MobileShop.Controllers
{
    public class HomeController : Controller
    {
        private readonly IProductService _productService;
        private readonly ILogger<HomeController> _logger;

        public HomeController(IProductService productService, ILogger<HomeController> logger)
        {
            _productService = productService;
            _logger = logger;
        }

        public async Task<IActionResult> Index()
        {
            var viewModel = new HomeViewModel
            {
                FeaturedProducts = await _productService.GetFeaturedProductsAsync(8),
                NewArrivals = await _productService.GetNewArrivalsAsync(8),
                Bestsellers = await _productService.GetBestsellersAsync(8),
                Brands = await _productService.GetBrandsAsync(6)
            };
            return View(viewModel);
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }

    // ViewModel to hold all homepage data in one object
    public class HomeViewModel
    {
        public List<Product> FeaturedProducts { get; set; } = new List<Product>();
        public List<Product> NewArrivals { get; set; } = new List<Product>();
        public List<Product> Bestsellers { get; set; } = new List<Product>();
        public List<Brand> Brands { get; set; } = new List<Brand>();
    }
}

Detailed Technical Explanation

1. The Role of the ViewModel

In MVC Architecture, a View can usually only accept one Model object. However, a Home Page often needs to display several different things (banners, products, brands). By creating the HomeViewModel, we "package" all these lists into one object so the View can access everything easily.

2. Constructor Dependency Injection

Notice the line _productService = productService;. Because we registered our service in Program.cs earlier, ASP.NET Core automatically provides the HomeController with the correct implementation. This is the hallmark of clean, maintainable C# code.

3. High-Performance Data Fetching

Inside the Index() method, we use await for every service call. This ensures that our server can handle other user requests while waiting for the database to return the product lists. This is a critical feature for a fast-loading Mobile Shop Website.

4. SEO & User Experience (UX)

By passing 8 items for products and 6 for brands, we are optimizing the Visual Balance of the home page. Too many items can slow down the page (bad for SEO), while too few look empty. This specific count ensures a perfect grid layout on both mobile and desktop.

Step 4: Creating a Reusable Product Card Partial View

In modern web development, we want our UI to be consistent. Instead of writing the HTML for a product card over and over again, we create a Partial View. This allows us to maintain the design in one single file: _ProductCard.cshtml.

The Code: Views/Shared/_ProductCard.cshtml

Create a new file in your Views/Shared folder named _ProductCard.cshtml and paste the following:


Razor / _ProductCard.cshtml

@model Product

<div class="card h-100 product-card shadow-sm">
    <div class="position-relative">
        <a asp-controller="Products" asp-action="Details" asp-route-id="@Model.Id">
            <img src="@Model.MainImageUrl"
                 class="card-img-top" alt="@Model.Name" style="height: 250px; object-fit: cover;" />
        </a>
        
        @if (Model.DiscountPercentage > 0)
        {
            <span class="badge bg-danger position-absolute top-0 end-0 m-2">-@Model.DiscountPercentage%</span>
        }
        
        @if (Model.IsNewArrival)
        {
            <span class="badge bg-success position-absolute top-0 start-0 m-2">New</span>
        }
        
        <button class="btn btn-light btn-sm position-absolute bottom-0 end-0 m-2 wishlist-btn"
                data-product-id="@Model.Id">
            <i class="bi bi-heart"></i>
        </button>
    </div>

    <div class="card-body d-flex flex-column">
        <p class="text-muted mb-1 small">@Model.Brand?.Name</p>
        <h6 class="card-title">
            <a asp-controller="Products" asp-action="Details" asp-route-id="@Model.Id" class="text-decoration-none text-dark">
                @Model.Name
            </a>
        </h6>

        <!-- Star Rating Section -->
        <div class="mb-2">
            @if (Model.AverageRating > 0)
            {
                <span class="text-warning">
                    @for (int i = 1; i <= 5; i++)
                    {
                        if (i <= Model.AverageRating)
                        {
                            <i class="bi bi-star-fill"></i>
                        }
                        else if (i - 0.5 <= Model.AverageRating)
                        {
                            <i class="bi bi-star-half"></i>
                        }
                        else
                        {
                            <i class="bi bi-star"></i>
                        }
                    }
                </span>
                <small class="text-muted">(@Model.ReviewCount)</small>
            }
        </div>

        <div class="mt-auto">
            <div class="d-flex align-items-center justify-content-between">
                <div>
                    @if (Model.OriginalPrice > Model.SalePrice)
                    {
                        <del class="text-muted small">RS @Model.OriginalPrice.ToString("N0")</del>
                    }
                    <h5 class="text-primary mb-0">RS @Model.SalePrice.ToString("N0")</h5>
                </div>
                
                <form asp-controller="Cart" asp-action="AddToCart" method="post" class="d-inline">
                    <input type="hidden" name="productId" value="@Model.Id" />
                    <button type="submit" class="btn btn-primary btn-sm" @(Model.StockQuantity <= 0 ? "disabled" : "")>
                        <i class="bi bi-cart-plus"></i> Add
                    </button>
                </form>
            </div>

            <!-- Stock Alerts -->
            @if (Model.StockQuantity <= 5 && Model.StockQuantity > 0)
            {
                <small class="text-warning"><i class="bi bi-exclamation-circle"></i> Only @Model.StockQuantity left</small>
            }
            else if (Model.StockQuantity == 0)
            {
                <small class="text-danger"><i class="bi bi-x-circle"></i> Out of stock</small>
            }
        </div>
    </div>
</div>

Detailed Explanation

1. Partial Views in ASP.NET Core

A Partial View is a Razor file that is rendered inside another view. By naming it with an underscore (_), we follow the convention that this file is a reusable component rather than a standalone page.

2. Dynamic UI with Razor Logic

The code includes several logic checks that make the user interface Dynamic and Reactive:

  • Discount Badge: Only shows if DiscountPercentage is greater than zero.

  • Star Rating System: Uses a @for loop to render star icons dynamically based on the AverageRating.

  • Stock Status: Changes color and text (Warning for low stock, Red for out of stock) to create a sense of urgency for the buyer.

3. Bootstrap 5 Integration

We are using Bootstrap 5 utility classes like shadow-sm, d-flex, and position-absolute to create a modern "Amazon-style" product card. The object-fit: cover style ensures that images look consistent even if they have different original dimensions.

Step 5: Designing the Home Page UI in Index.cshtml

Now it’s time to display our data. We will use Bootstrap 5 Grid and Razor Foreach Loops to loop through our product lists and render them beautifully.

The Code: Views/Home/Index.cshtml

Replace the content of your Views/Home/Index.cshtml with the following code:

Razor / Views/Home/Index.cshtml

@using MobileShop.Controllers
@model HomeViewModel
@{
    ViewData["Title"] = "Home";
}

<!-- Featured Products Section -->
<section class="py-5">
    <div class="container">
        <div class="d-flex justify-content-between align-items-center mb-4">
            <h2>Featured Products</h2>
            <a class="btn btn-outline-primary">View All <i class="bi bi-arrow-right"></i></a>
        </div>
        <div class="row g-4">
            @foreach (var product in Model.FeaturedProducts)
            {
                <div class="col-lg-3 col-md-4 col-sm-6">
                    <partial name="_ProductCard" model="product" />
                </div>
            }
        </div>
    </div>
</section>

<!-- New Arrivals Section -->
<section class="py-5 bg-light">
    <div class="container">
        <div class="d-flex justify-content-between align-items-center mb-4">
            <h2>New Arrivals</h2>
            <a class="btn btn-outline-primary">View All <i class="bi bi-arrow-right"></i></a>
        </div>
        <div class="row g-4">
            @foreach (var product in Model.NewArrivals.Take(4))
            {
                <div class="col-lg-3 col-md-4 col-sm-6">
                    <partial name="_ProductCard" model="product" />
                </div>
            }
        </div>
    </div>
</section>

<!-- Bestsellers Section -->
<section class="py-5">
    <div class="container">
        <div class="d-flex justify-content-between align-items-center mb-4">
            <h2>Bestsellers</h2>
            <a class="btn btn-outline-primary">View All <i class="bi bi-arrow-right"></i></a>
        </div>
        <div class="row g-4">
            @foreach (var product in Model.Bestsellers.Take(4))
            {
                <div class="col-lg-3 col-md-4 col-sm-6">
                    <partial name="_ProductCard" model="product" />
                </div>
            }
        </div>
    </div>
</section>

<!-- Popular Brands Section -->
<section class="py-5 bg-light">
    <div class="container">
        <h2 class="text-center mb-5">Popular Brands</h2>
        <div class="row g-4 text-center">
            @foreach (var brand in Model.Brands)
            {
                <div class="col-md-2 col-4">
                    <a asp-controller="Products" asp-action="Index" asp-route-brandId="@brand.Id" class="text-decoration-none">
                        <div class="brand-card p-4 bg-white rounded shadow-sm">
                            <h5 class="text-dark mb-0">@brand.Name</h5>
                        </div>
                    </a>
                </div>
            }
        </div>
    </div>
</section>

Detailed Explanation

1. Mastering the Grid System

We use col-lg-3 col-md-4 col-sm-6. This is critical for Mobile-First Design. It tells the browser:

  • Show 4 products per row on large screens (Desktops).

  • Show 3 products per row on medium screens (Tablets).

  • Show 2 products per row on small screens (Mobiles). This improves your Mobile-Friendly SEO score.

2. Using the Partial Tag Helper

Instead of writing the card HTML four times, we use <partial name="_ProductCard" model="product" />. This makes the code incredibly easy to update. If you want to change the look of your products later, you only edit one file!

3. Performance Optimization with .Take()

Even though our service might fetch 8 items, we use .Take(4) in specific sections like "New Arrivals" to keep the home page looking clean. This ensures the page doesn't become too long, reducing "scroll fatigue" for your users.

4. Semantic HTML5 Tags

Notice the use of <section> tags. Using Semantic HTML helps search engines understand the structure of your page, which is a key factor in ranking for keywords like "Mobile Shop Online" or "Latest Smartphone Deals."

Step 6: Setting Up the Professional _Layout.cshtml

The _Layout.cshtml file is the master template for your entire website. In this step, we configure the global navigation, search bar, and footer to ensure a consistent user experience across the Mobile Shop.

The Code: Views/Shared/_Layout.cshtml

Update your main layout file with the following code. It includes Bootstrap 5, Bootstrap Icons, and a modern e-commerce header.


Razor / Views/Shared/_Layout.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Mobile Shop</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    @await RenderSectionAsync("Styles", required: false)
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <div class="container">
                <a class="navbar-brand fw-bold" asp-controller="Home" asp-action="Index">
                    <i class="bi bi-phone-fill"></i> Mobile Shop
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarNav">
                    <ul class="navbar-nav me-auto">
                        <li class="nav-item">
                            <a class="nav-link" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link">Products</a>
                        </li>
                        <li class="nav-item dropdown">
                            <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">Brands</a>
                            <ul class="dropdown-menu">
                                <li><a class="dropdown-item">Apple</a></li>
                                <li><a class="dropdown-item">Samsung</a></li>
                                <li><a class="dropdown-item">Xiaomi</a></li>
                                <li><a class="dropdown-item">OnePlus</a></li>
                                <li><a class="dropdown-item">Google</a></li>
                                <li><hr class="dropdown-divider"></li>
                                <li><a class="dropdown-item">View All</a></li>
                            </ul>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link">Contact</a>
                        </li>
                    </ul>

                    <!-- Search Bar -->
                    <form class="d-flex me-3" method="get">
                        <div class="input-group">
                            <input type="search" name="search" class="form-control" placeholder="Search phones..."
                                   id="searchInput" autocomplete="off" />
                            <button class="btn btn-light" type="submit"><i class="bi bi-search"></i></button>
                        </div>
                    </form>

                    <ul class="navbar-nav">
                        <!-- Cart -->
                        <li class="nav-item">
                            <a class="nav-link position-relative">
                                <i class="bi bi-cart3 fs-5"></i>
                                <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" id="cartCount">
                                    0
                                </span>
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link">Login</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link">Register</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>

    <!-- Search Suggestions Dropdown -->
    <div id="searchSuggestions" class="position-absolute w-100" style="z-index: 1050; display: none;">
        <div class="container">
            <div class="list-group shadow" id="suggestionsList"></div>
        </div>
    </div>

    <main role="main">
        @if (TempData["Success"] != null)
        {
            <div class="alert alert-success alert-dismissible fade show m-3" role="alert">
                <i class="bi bi-check-circle-fill"></i> @TempData["Success"]
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
            </div>
        }
        @if (TempData["Error"] != null)
        {
            <div class="alert alert-danger alert-dismissible fade show m-3" role="alert">
                <i class="bi bi-exclamation-triangle-fill"></i> @TempData["Error"]
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
            </div>
        }
        @RenderBody()
    </main>

    <footer class="bg-dark text-light mt-5">
        <div class="container py-5">
            <div class="row">
                <div class="col-md-4">
                    <h5><i class="bi bi-phone-fill"></i> MobileShop</h5>
                    <p>Your one-stop destination for the latest smartphones at the best prices.</p>
                    <div class="social-links">
                        <a href="#" class="text-light me-3"><i class="bi bi-facebook fs-4"></i></a>
                        <a href="#" class="text-light me-3"><i class="bi bi-twitter-x fs-4"></i></a>
                        <a href="#" class="text-light me-3"><i class="bi bi-instagram fs-4"></i></a>
                        <a href="#" class="text-light"><i class="bi bi-youtube fs-4"></i></a>
                    </div>
                </div>
                <div class="col-md-2">
                    <h6>Quick Links</h6>
                    <ul class="list-unstyled">
                        <li><a class="text-light text-decoration-none">About Us</a></li>
                        <li><a class="text-light text-decoration-none">Contact</a></li>
                        <li><a class="text-light text-decoration-none">Privacy Policy</a></li>
                        <li><a class="text-light text-decoration-none">Terms of Service</a></li>
                    </ul>
                </div>
                <div class="col-md-3">
                    <h6>Customer Service</h6>
                    <ul class="list-unstyled">
                        <li><a class="text-light text-decoration-none">Track Order</a></li>
                        <li><a class="text-light text-decoration-none">Shipping Info</a></li>
                        <li><a class="text-light text-decoration-none">Returns & Exchanges</a></li>
                        <li><a class="text-light text-decoration-none">FAQ</a></li>
                    </ul>
                </div>
                <div class="col-md-3">
                    <h6>Contact Us</h6>
                    <p><i class="bi bi-geo-alt"></i> 123 Mobile Street, Tech City</p>
                    <p><i class="bi bi-telephone"></i> +92-3143076781</p>
                    <p><i class="bi bi-envelope"></i> support@mobileshop.com</p>
                    <p><i class="bi bi-clock"></i> Mon - Sat: 9AM - 8PM</p>
                </div>
            </div>
            <hr class="my-4">
            <div class="text-center">
                <p class="mb-0">© @DateTime.Now.Year MobileShop. All rights reserved.</p>
            </div>
        </div>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Detailed Technical Explanation

To help your students understand why this layout is professional-grade, let's break down the key features:

1. Modern Navigation & Brand Identity

The navbar uses bg-primary for a bold, professional look. Using the Tag Helper asp-controller="Home" asp-action="Index" ensures that clicking the logo always returns the user to the home page, which is vital for Site Navigation SEO.

2. Integrated Search & Cart UI

  • Search Bar: We’ve added a search input with an id="searchInput". This is ready for advanced features like AJAX suggestions, which we will cover in future parts.

  • Cart Badge: The shopping cart uses a position-absolute badge to show the item count. This "sticky" notification style is a high-conversion design pattern used by top e-commerce sites.

3. TempData for User Feedback

We’ve included two @if blocks for TempData["Success"] and TempData["Error"].

  • Why? This allows the server to send "Success" or "Error" messages (like "Item added to cart") that appear as beautiful Bootstrap alerts at the top of the page.

4. The @RenderBody() and Sections

  • @RenderBody(): This is where our Index.cshtml content is injected.

  • RenderSectionAsync: We use this for "Scripts" and "Styles". This allows specific pages to load their own custom CSS or JavaScript files only when needed, keeping the rest of the site fast and optimized.

5. Professional Footer & Social SEO

The footer is divided into four columns:

  • About Us: Briefly describes the shop (great for keyword density).

  • Quick Links & Customer Service: Improves internal linking for better Google crawling.

  • Contact Info: Including a real phone number and address increases your site's "Trust Score" with search engines.


Conclusion of Part 4

We have successfully set up the Home Page Settings for our Mobile Shop project! We covered:

  1. Creating a Service Layer for data.

  2. Registering services in Program.cs.

  3. Connecting the Home Controller and ViewModel.

  4. Designing a reusable Product Card partial view.

  5. Finalizing the Home View and Global Layout.

  6. Download Product Image

Comments