
📖 Table of Contents
Welcome to Web Development: Your First Real ASP.NET Core Application
Understanding MVC Architecture: The Professional Pattern
Setting Up Your MVC Project Structure
Building Models: Data Structures for Real-World Applications
Creating Controllers: The Brain of Your Application
Designing Views with Razor Syntax: Beautiful, Dynamic UIs
Building a Complete E-Commerce Product Catalog
Implementing Shopping Cart Functionality
User Authentication and Authorization
Adding Admin Dashboard and Management
Form Validation and Error Handling
Responsive Design and Frontend Integration
Testing Your MVC Application
Deployment and Production Ready Setup
What's Next: Advanced Features in Part 4
Build Your First ASP.NET Core Web App: Complete MVC & Razor Tutorial (Part 3)
1. Welcome to Web Development: Your First Real ASP.NET Core Application 🎉
1.1 From Console to Web: Your Transformation Journey
Welcome to the most exciting part of our series! In Parts 1 and 2, you set up your environment like a pro. Now, you'll build your first real web application using ASP.NET Core MVC. This isn't just another tutorial—this is where you transform from a beginner into a web developer.
Real-World Perspective: Think of building your first web app like opening your first restaurant:
Planning = Project structure and architecture
Kitchen = Controllers and business logic
Menu = Views and user interface
Customers = Users interacting with your application
Service = The complete user experience
1.2 What You'll Build: Professional E-Commerce Store
By the end of this guide, you'll have a fully functional e-commerce application with:
✅ Product Catalog: Browse and search products
✅ Shopping Cart: Add, remove, and manage items
✅ User Accounts: Registration and login system
✅ Admin Dashboard: Manage products and orders
✅ Responsive Design: Works on all devices
✅ Real Database: SQL Server with Entity Framework
2. Understanding MVC Architecture: The Professional Pattern 🏗️
2.1 MVC Demystified: How Web Applications Really Work
MVC (Model-View-Controller) is not just a pattern—it's a way of thinking about web applications:
csharp
// Real-World MVC Analogy: Restaurant Order System
public class RestaurantMVC
{
// MODEL = Kitchen & Ingredients (Data & Business Logic)
public class OrderModel
{
public List<MenuItem> Menu { get; set; }
public decimal CalculateTotal() { /* Business logic */ }
public bool ValidateOrder() { /* Validation rules */ }
}
// VIEW = Menu & Presentation (User Interface)
public class MenuView
{
public void DisplayMenu(List<MenuItem> items) { /* Render UI */ }
public void ShowOrderConfirmation(Order order) { /* Show results */ }
}
// CONTROLLER = Waiter (Request Handler)
public class OrderController
{
public ActionResult TakeOrder(OrderRequest request)
{
// Coordinate between Model and View
var order = _model.CreateOrder(request);
return _view.ShowConfirmation(order);
}
}
}2.2 MVC Request Lifecycle in ASP.NET Core
text
🌐 User Request → 🔍 Routing → 🎯 Controller → 📊 Model → 👁️ View → 🌐 Response
↓ ↓ ↓ ↓ ↓ ↓
Browser ASP.NET Business Data UI HTML
Core Logic Access Render Response2.3 Why MVC is Perfect for Beginners
Advantages:
Separation of Concerns: Each component has a clear responsibility
Testability: Easy to unit test individual components
Maintainability: Changes in one area don't break others
Team Collaboration: Multiple developers can work simultaneously
Industry Standard: Used by 70% of enterprise web applications
3. Setting Up Your MVC Project Structure 📁
3.1 Creating Your MVC Project
bash
# Professional MVC Project Creation
dotnet new mvc -n TechShop -f net8.0 --auth Individual -o TechShop
cd TechShop
# Explore the generated structure
dotnet run3.2 Understanding the Generated Structure
text
TechShop/
├── Controllers/ # 🎯 Request handlers
│ ├── HomeController.cs
│ └── ...
├── Models/ # 📊 Data and business logic
│ ├── ErrorViewModel.cs
│ └── ...
├── Views/ # 👁️ User interface
│ ├── Home/
│ │ ├── Index.cshtml
│ │ └── ...
│ ├── Shared/
│ │ ├── _Layout.cshtml
│ │ └── ...
│ └── _ViewStart.cshtml
├── wwwroot/ # 🌐 Static files (CSS, JS, images)
│ ├── css/
│ ├── js/
│ └── lib/
├── Program.cs # 🚀 Application entry point
└── appsettings.json # ⚙️ Configuration3.3 Enhanced Project Structure for E-Commerce
bash
# Custom directory structure for our e-commerce app
TechShop/
├── Controllers/
│ ├── HomeController.cs
│ ├── ProductsController.cs
│ ├── ShoppingCartController.cs
│ ├── AccountController.cs
│ └── AdminController.cs
├── Models/
│ ├── Entities/ # Database entities
│ │ ├── Product.cs
│ │ ├── Category.cs
│ │ ├── Order.cs
│ │ └── User.cs
│ ├── ViewModels/ # UI-specific models
│ │ ├── ProductViewModel.cs
│ │ ├── CartViewModel.cs
│ │ └── ...
│ └── Enums/
│ ├── OrderStatus.cs
│ └── ProductCategory.cs
├── Views/
│ ├── Home/
│ ├── Products/
│ ├── ShoppingCart/
│ ├── Account/
│ ├── Admin/
│ └── Shared/
├── Services/ # Business logic services
│ ├── ProductService.cs
│ ├── CartService.cs
│ └── ...
├── Data/ # Data access layer
│ ├── ApplicationDbContext.cs
│ └── ...
└── wwwroot/
├── images/
│ └── products/
├── css/
├── js/
└── lib/4. Building Models: Data Structures for Real-World Applications 📊
4.1 Core Business Entities
csharp
// Models/Entities/Product.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace TechShop.Models.Entities
{
public class Product
{
public int Id { get; set; }
[Required(ErrorMessage = "Product name is required")]
[StringLength(100, ErrorMessage = "Name cannot exceed 100 characters")]
public string Name { get; set; } = string.Empty;
[Required]
[StringLength(500)]
public string Description { get; set; } = string.Empty;
[Required]
[Range(0.01, 10000, ErrorMessage = "Price must be between $0.01 and $10,000")]
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
[Required]
[Range(0, 1000, ErrorMessage = "Stock must be between 0 and 1000")]
public int StockQuantity { get; set; }
[Required]
[StringLength(50)]
public string Category { get; set; } = string.Empty;
[Url(ErrorMessage = "Please enter a valid image URL")]
public string ImageUrl { get; set; } = "/images/products/default.png";
public string Brand { get; set; } = string.Empty;
public string SKU { get; set; } = string.Empty; // Stock Keeping Unit
public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
public DateTime UpdatedDate { get; set; } = DateTime.UtcNow;
public bool IsActive { get; set; } = true;
// Navigation properties
public ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>();
// Business logic methods
public bool IsInStock() => StockQuantity > 0;
public bool IsLowStock() => StockQuantity > 0 && StockQuantity <= 10;
public decimal CalculateDiscountedPrice(decimal discountPercentage)
{
if (discountPercentage < 0 || discountPercentage > 100)
throw new ArgumentException("Discount must be between 0 and 100");
return Price * (1 - discountPercentage / 100);
}
public void ReduceStock(int quantity)
{
if (quantity <= 0)
throw new ArgumentException("Quantity must be positive");
if (quantity > StockQuantity)
throw new InvalidOperationException("Insufficient stock");
StockQuantity -= quantity;
UpdatedDate = DateTime.UtcNow;
}
}
}4.2 Shopping Cart and Order Models
csharp
// Models/Entities/ShoppingCart.cs
namespace TechShop.Models.Entities
{
public class ShoppingCart
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string UserId { get; set; } = string.Empty; // For logged-in users
public string SessionId { get; set; } = string.Empty; // For guest users
public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
public DateTime UpdatedDate { get; set; } = DateTime.UtcNow;
// Navigation properties
public ICollection<CartItem> Items { get; set; } = new List<CartItem>();
// Business logic methods
public decimal CalculateTotal()
{
return Items.Sum(item => item.Quantity * item.Product.Price);
}
public int TotalItems => Items.Sum(item => item.Quantity);
public void AddItem(Product product, int quantity = 1)
{
var existingItem = Items.FirstOrDefault(item => item.ProductId == product.Id);
if (existingItem != null)
{
existingItem.Quantity += quantity;
}
else
{
Items.Add(new CartItem
{
ProductId = product.Id,
Product = product,
Quantity = quantity,
UnitPrice = product.Price
});
}
UpdatedDate = DateTime.UtcNow;
}
public void RemoveItem(int productId)
{
var item = Items.FirstOrDefault(item => item.ProductId == productId);
if (item != null)
{
Items.Remove(item);
UpdatedDate = DateTime.UtcNow;
}
}
public void Clear()
{
Items.Clear();
UpdatedDate = DateTime.UtcNow;
}
}
public class CartItem
{
public int Id { get; set; }
public string ShoppingCartId { get; set; } = string.Empty;
public int ProductId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
// Navigation properties
public ShoppingCart ShoppingCart { get; set; } = null!;
public Product Product { get; set; } = null!;
public decimal LineTotal => Quantity * UnitPrice;
}
}4.3 Order Management System
csharp
// Models/Entities/Order.cs
namespace TechShop.Models.Entities
{
public class Order
{
public int Id { get; set; }
public string OrderNumber { get; set; } = GenerateOrderNumber();
public string UserId { get; set; } = string.Empty;
// Customer information
public string CustomerName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
// Shipping address
public string ShippingAddress { get; set; } = string.Empty;
public string ShippingCity { get; set; } = string.Empty;
public string ShippingState { get; set; } = string.Empty;
public string ShippingZipCode { get; set; } = string.Empty;
public string ShippingCountry { get; set; } = "USA";
// Order details
public DateTime OrderDate { get; set; } = DateTime.UtcNow;
public OrderStatus Status { get; set; } = OrderStatus.Pending;
public decimal Subtotal { get; set; }
public decimal Tax { get; set; }
public decimal ShippingCost { get; set; }
public decimal Total => Subtotal + Tax + ShippingCost;
public string? Notes { get; set; }
// Navigation properties
public ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>();
// Business logic methods
public static string GenerateOrderNumber()
{
return $"ORD-{DateTime.UtcNow:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpper()}";
}
public bool CanBeCancelled()
{
return Status == OrderStatus.Pending || Status == OrderStatus.Confirmed;
}
public void CalculateTotals()
{
Subtotal = OrderItems.Sum(item => item.Quantity * item.UnitPrice);
Tax = Subtotal * 0.08m; // 8% tax for example
ShippingCost = Subtotal > 50 ? 0 : 5.99m; // Free shipping over $50
}
}
public class OrderItem
{
public int Id { get; set; }
public int OrderId { get; set; }
public int ProductId { get; set; }
public string ProductName { get; set; } = string.Empty;
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
// Navigation properties
public Order Order { get; set; } = null!;
public Product Product { get; set; } = null!;
public decimal LineTotal => Quantity * UnitPrice;
}
public enum OrderStatus
{
Pending,
Confirmed,
Processing,
Shipped,
Delivered,
Cancelled,
Refunded
}
}5. Creating Controllers: The Brain of Your Application 🎯
5.1 Products Controller - The Heart of E-Commerce
csharp
// Controllers/ProductsController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TechShop.Models.Entities;
using TechShop.Models.ViewModels;
using TechShop.Data;
namespace TechShop.Controllers
{
public class ProductsController : Controller
{
private readonly ApplicationDbContext _context;
private readonly ILogger<ProductsController> _logger;
public ProductsController(ApplicationDbContext context, ILogger<ProductsController> logger)
{
_context = context;
_logger = logger;
}
// GET: /Products
public async Task<IActionResult> Index(
string category = "",
string search = "",
string sortBy = "name",
int page = 1,
int pageSize = 12)
{
_logger.LogInformation("Loading products with filters: Category={Category}, Search={Search}", category, search);
var query = _context.Products
.Where(p => p.IsActive)
.AsQueryable();
// Apply filters
if (!string.IsNullOrEmpty(category))
{
query = query.Where(p => p.Category == category);
}
if (!string.IsNullOrEmpty(search))
{
query = query.Where(p =>
p.Name.Contains(search) ||
p.Description.Contains(search) ||
p.Brand.Contains(search));
}
// Apply sorting
query = sortBy.ToLower() switch
{
"price" => query.OrderBy(p => p.Price),
"price_desc" => query.OrderByDescending(p => p.Price),
"newest" => query.OrderByDescending(p => p.CreatedDate),
_ => query.OrderBy(p => p.Name) // Default sort by name
};
// Pagination
var totalItems = await query.CountAsync();
var totalPages = (int)Math.Ceiling(totalItems / (double)pageSize);
var products = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
var viewModel = new ProductListViewModel
{
Products = products,
CurrentCategory = category,
SearchTerm = search,
SortBy = sortBy,
CurrentPage = page,
TotalPages = totalPages,
TotalItems = totalItems,
PageSize = pageSize,
Categories = await _context.Products
.Where(p => p.IsActive)
.Select(p => p.Category)
.Distinct()
.OrderBy(c => c)
.ToListAsync()
};
return View(viewModel);
}
// GET: /Products/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
_logger.LogWarning("Product details requested without ID");
return NotFound();
}
var product = await _context.Products
.FirstOrDefaultAsync(p => p.Id == id && p.IsActive);
if (product == null)
{
_logger.LogWarning("Product with ID {ProductId} not found", id);
return NotFound();
}
// Get related products
var relatedProducts = await _context.Products
.Where(p => p.Category == product.Category && p.Id != product.Id && p.IsActive)
.OrderBy(p => p.Name)
.Take(4)
.ToListAsync();
var viewModel = new ProductDetailViewModel
{
Product = product,
RelatedProducts = relatedProducts
};
return View(viewModel);
}
// GET: /Products/Category/{category}
public async Task<IActionResult> Category(string category, int page = 1)
{
if (string.IsNullOrEmpty(category))
{
return RedirectToAction(nameof(Index));
}
var products = await _context.Products
.Where(p => p.Category == category && p.IsActive)
.OrderBy(p => p.Name)
.Skip((page - 1) * 12)
.Take(12)
.ToListAsync();
var totalProducts = await _context.Products
.CountAsync(p => p.Category == category && p.IsActive);
var viewModel = new ProductCategoryViewModel
{
CategoryName = category,
Products = products,
CurrentPage = page,
TotalPages = (int)Math.Ceiling(totalProducts / 12.0),
TotalProducts = totalProducts
};
return View(viewModel);
}
// POST: /Products/Search
[HttpPost]
public IActionResult Search(string searchTerm)
{
if (string.IsNullOrWhiteSpace(searchTerm))
{
return RedirectToAction(nameof(Index));
}
return RedirectToAction(nameof(Index), new { search = searchTerm.Trim() });
}
// AJAX: /Products/QuickSearch
[HttpGet]
public async Task<IActionResult> QuickSearch(string term)
{
if (string.IsNullOrWhiteSpace(term) || term.Length < 2)
{
return Json(new List<object>());
}
var products = await _context.Products
.Where(p => p.IsActive &&
(p.Name.Contains(term) || p.Brand.Contains(term)))
.OrderBy(p => p.Name)
.Take(5)
.Select(p => new
{
id = p.Id,
name = p.Name,
brand = p.Brand,
price = p.Price.ToString("C"),
imageUrl = p.ImageUrl,
url = Url.Action("Details", "Products", new { id = p.Id })
})
.ToListAsync();
return Json(products);
}
}
}5.2 Shopping Cart Controller
csharp
// Controllers/ShoppingCartController.cs
using Microsoft.AspNetCore.Mvc;
using TechShop.Models.Entities;
using TechShop.Models.ViewModels;
using TechShop.Data;
using Microsoft.EntityFrameworkCore;
namespace TechShop.Controllers
{
public class ShoppingCartController : Controller
{
private readonly ApplicationDbContext _context;
private readonly ILogger<ShoppingCartController> _logger;
public ShoppingCartController(ApplicationDbContext context, ILogger<ShoppingCartController> logger)
{
_context = context;
_logger = logger;
}
// GET: /Cart
public async Task<IActionResult> Index()
{
var cart = await GetOrCreateCartAsync();
var viewModel = new CartViewModel
{
Items = cart.Items.ToList(),
Total = cart.CalculateTotal(),
ItemCount = cart.TotalItems
};
return View(viewModel);
}
// POST: /Cart/Add/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Add(int productId, int quantity = 1)
{
try
{
var product = await _context.Products
.FirstOrDefaultAsync(p => p.Id == productId && p.IsActive);
if (product == null)
{
_logger.LogWarning("Attempt to add non-existent product {ProductId} to cart", productId);
return NotFound();
}
if (!product.IsInStock())
{
TempData["Error"] = "Sorry, this product is out of stock.";
return RedirectToAction("Details", "Products", new { id = productId });
}
if (quantity > product.StockQuantity)
{
TempData["Error"] = $"Only {product.StockQuantity} items available in stock.";
return RedirectToAction("Details", "Products", new { id = productId });
}
var cart = await GetOrCreateCartAsync();
cart.AddItem(product, quantity);
await _context.SaveChangesAsync();
TempData["Success"] = $"{product.Name} added to cart!";
_logger.LogInformation("Product {ProductId} added to cart with quantity {Quantity}", productId, quantity);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error adding product {ProductId} to cart", productId);
TempData["Error"] = "There was an error adding the product to your cart.";
}
return RedirectToAction("Details", "Products", new { id = productId });
}
// POST: /Cart/Update
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(int productId, int quantity)
{
try
{
var cart = await GetOrCreateCartAsync();
var cartItem = cart.Items.FirstOrDefault(item => item.ProductId == productId);
if (cartItem == null)
{
return NotFound();
}
if (quantity <= 0)
{
// Remove item if quantity is 0 or negative
cart.RemoveItem(productId);
}
else
{
var product = await _context.Products.FindAsync(productId);
if (product != null && quantity > product.StockQuantity)
{
TempData["Error"] = $"Only {product.StockQuantity} items available in stock.";
return RedirectToAction(nameof(Index));
}
cartItem.Quantity = quantity;
}
await _context.SaveChangesAsync();
TempData["Success"] = "Cart updated successfully!";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating cart item {ProductId}", productId);
TempData["Error"] = "There was an error updating your cart.";
}
return RedirectToAction(nameof(Index));
}
// POST: /Cart/Remove/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Remove(int productId)
{
try
{
var cart = await GetOrCreateCartAsync();
cart.RemoveItem(productId);
await _context.SaveChangesAsync();
TempData["Success"] = "Item removed from cart!";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error removing product {ProductId} from cart", productId);
TempData["Error"] = "There was an error removing the item from your cart.";
}
return RedirectToAction(nameof(Index));
}
// POST: /Cart/Clear
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Clear()
{
try
{
var cart = await GetOrCreateCartAsync();
cart.Clear();
await _context.SaveChangesAsync();
TempData["Success"] = "Cart cleared successfully!";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error clearing cart");
TempData["Error"] = "There was an error clearing your cart.";
}
return RedirectToAction(nameof(Index));
}
// GET: /Cart/Summary (Partial View for Layout)
public async Task<IActionResult> Summary()
{
var cart = await GetOrCreateCartAsync();
var viewModel = new CartSummaryViewModel
{
ItemCount = cart.TotalItems,
Total = cart.CalculateTotal()
};
return PartialView("_CartSummary", viewModel);
}
private async Task<ShoppingCart> GetOrCreateCartAsync()
{
var cartId = GetCartId();
var cart = await _context.ShoppingCarts
.Include(c => c.Items)
.ThenInclude(i => i.Product)
.FirstOrDefaultAsync(c => c.Id == cartId);
if (cart == null)
{
cart = new ShoppingCart
{
Id = cartId,
SessionId = HttpContext.Session.Id
};
_context.ShoppingCarts.Add(cart);
await _context.SaveChangesAsync();
}
return cart;
}
private string GetCartId()
{
var cartId = HttpContext.Session.GetString("CartId");
if (string.IsNullOrEmpty(cartId))
{
cartId = Guid.NewGuid().ToString();
HttpContext.Session.SetString("CartId", cartId);
}
return cartId;
}
}
}6. Designing Views with Razor Syntax: Beautiful, Dynamic UIs 👁️
6.1 Master Layout with Bootstrap 5
html
<!-- Views/Shared/_Layout.cshtml -->
<!DOCTYPE html>
<html lang="en" data-bs-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - TechShop</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css" rel="stylesheet">
<!-- Custom CSS -->
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
@await RenderSectionAsync("Styles", required: false)
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary sticky-top">
<div class="container">
<a class="navbar-brand fw-bold" asp-area="" asp-controller="Home" asp-action="Index">
<i class="bi bi-laptop"></i> TechShop
</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 dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
Products
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" asp-controller="Products" asp-action="Index">All Products</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" asp-controller="Products" asp-action="Category" asp-route-category="Laptops">Laptops</a></li>
<li><a class="dropdown-item" asp-controller="Products" asp-action="Category" asp-route-category="Smartphones">Smartphones</a></li>
<li><a class="dropdown-item" asp-controller="Products" asp-action="Category" asp-route-category="Tablets">Tablets</a></li>
<li><a class="dropdown-item" asp-controller="Products" asp-action="Category" asp-route-category="Accessories">Accessories</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="Home" asp-action="About">About</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="Home" asp-action="Contact">Contact</a>
</li>
</ul>
<!-- Search Form -->
<form class="d-flex me-3" asp-controller="Products" asp-action="Search" method="post">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search products..." name="searchTerm"
id="searchInput" aria-label="Search products">
<button class="btn btn-outline-light" type="submit">
<i class="bi bi-search"></i>
</button>
</div>
</form>
<!-- Cart & Auth -->
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link position-relative" asp-controller="ShoppingCart" asp-action="Index">
<i class="bi bi-cart3"></i> Cart
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger"
id="cartCount">
@await Component.InvokeAsync("CartSummary")
</span>
</a>
</li>
<partial name="_LoginPartial" />
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main role="main" class="pb-3">
<!-- Notification Messages -->
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-dismissible fade show m-3" role="alert">
<i class="bi bi-check-circle-fill me-2"></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 me-2"></i> @TempData["Error"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
@RenderBody()
</main>
<!-- Footer -->
<footer class="bg-dark text-light py-5 mt-5">
<div class="container">
<div class="row">
<div class="col-md-4">
<h5><i class="bi bi-laptop"></i> TechShop</h5>
<p>Your trusted partner for the latest technology products at competitive prices.</p>
</div>
<div class="col-md-2">
<h6>Quick Links</h6>
<ul class="list-unstyled">
<li><a href="#" class="text-light text-decoration-none">Home</a></li>
<li><a href="#" class="text-light text-decoration-none">Products</a></li>
<li><a href="#" class="text-light text-decoration-none">About</a></li>
<li><a href="#" class="text-light text-decoration-none">Contact</a></li>
</ul>
</div>
<div class="col-md-3">
<h6>Customer Service</h6>
<ul class="list-unstyled">
<li><a href="#" class="text-light text-decoration-none">Shipping Info</a></li>
<li><a href="#" class="text-light text-decoration-none">Returns</a></li>
<li><a href="#" class="text-light text-decoration-none">Privacy Policy</a></li>
<li><a href="#" class="text-light text-decoration-none">Terms of Service</a></li>
</ul>
</div>
<div class="col-md-3">
<h6>Contact Us</h6>
<p>
<i class="bi bi-envelope me-2"></i> [email protected]<br>
<i class="bi bi-telephone me-2"></i> 1-800-TECHSHOP<br>
<i class="bi bi-clock me-2"></i> Mon-Fri: 9AM-6PM
</p>
</div>
</div>
<hr class="my-4">
<div class="row align-items-center">
<div class="col-md-6">
<p>© 2024 TechShop. All rights reserved.</p>
</div>
<div class="col-md-6 text-md-end">
<div class="d-flex justify-content-md-end">
<a href="#" class="text-light me-3"><i class="bi bi-facebook"></i></a>
<a href="#" class="text-light me-3"><i class="bi bi-twitter"></i></a>
<a href="#" class="text-light me-3"><i class="bi bi-instagram"></i></a>
<a href="#" class="text-light"><i class="bi bi-linkedin"></i></a>
</div>
</div>
</div>
</div>
</footer>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<!-- Quick Search Functionality -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('searchInput');
if (searchInput) {
// Quick search implementation would go here
}
// Update cart count dynamically
function updateCartCount() {
fetch('@Url.Action("Summary", "ShoppingCart")')
.then(response => response.text())
.then(html => {
document.getElementById('cartCount').innerHTML = html;
});
}
// Update cart count every 30 seconds
setInterval(updateCartCount, 30000);
});
</script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Join the conversation! Your thoughts help the community grow.