Food Delivery App using in ASP.NET Core MVC

Food Delivery App using ASP.NET Core MVC

Let us develop a Real-time Food Delivery App using ASP.NET Core MVC and Entity Framework Core (EF Core) using SQL Server Database.

Food Delivery App Development using ASP.NET Core MVC

Let us design and implement a food delivery application, enabling customers to place orders and administrators to manage items efficiently and order statuses. The admin panel should auto-refresh to display the latest orders and automatically reject orders if not processed within a set time frame. The application will be divided into two main modules:

Customer Module:
  • Registration/Login: Customers can create accounts and authenticate.
  • Place Order: Authenticated customers can browse available items, place orders, and view order history.
Admin Module – Order Management:
  • Order Listing: The admin sees orders separated into three sections: Pending, Accepted, and Rejected. Only orders from the current date are shown.
  • Auto Refresh: The page auto-refreshes (using a meta tag or JavaScript) to always display the latest orders.
  • Order Acceptance/Rejection: The admin can manually accept or reject pending orders.
  • Auto-Rejection: If an order is not accepted within five minutes, a background check (invoked when the admin page loads) automatically marks it as Rejected.

Let us first understand the application workflow and the different pages. Then, we will develop this application step by step using ASP.NET Core MVC, Entity Framework Core Code First Approach with SQL Server Database, and also the Auto Page Refresh Functionality.

Home Page or Landing Page of Food Delivery App:

This page serves as the public face of your application. It introduces the food delivery service, highlights key benefits, and often includes calls-to-action (such as Order Now or Sign Up) to guide users into the app.

Home Page or Landing Page of Food Delivery App

Customer Registration Page:

This page is used by new users to sign up for an account. It gathers essential information like name, email, and password, enabling customers to create a secure profile for future logins and orders.

Food Delivery App using ASP.NET Core MVC

Customer Login Page:

This page allows existing customers to sign in. Once logged in, users can access ordering features, view order history, and manage their account details.

Food Delivery App using ASP.NET Core MVC

Customer Order Placing Page:

It provides an interface for customers to browse available food items and place orders. It displays a list or grid of items (often with images, names, prices, and availability). Customers can select quantities and submit an order, which is then recorded in the system.

Food Delivery App using ASP.NET Core MVC

Customer Order History Page:

This page displays a record of previous orders. Customers can review details such as order status, delivery time, and total cost to help them track their past interactions with the service.

Customer Order History Page

Admin Login Page:

Administrators use this secure page to log into the backend system. This ensures that only authorized personnel can manage orders and products.

Admin Login Page

Admin Product or Item Dashboard Page:

This dashboard allows administrators to view and manage the list of available food items. It includes details like item name, description, price, and availability, allowing quick inventory adjustments and oversight.

Admin Product or Item Dashboard Page

Admin Order Manage Page (Pending Order Tab):

This tab in the Orders page displays all orders that are awaiting action. Admins can review pending orders and manually accept or reject them. There is also an automatic mechanism to reject orders if they aren’t processed within five minutes.

Developing Food Delivery App using ASP.NET Core MVC and Entity Framework Core

Admin Order Manage Page (Accepted Order Tab):

Here, administrators can view all accepted orders. This helps them track which orders are in progress and ensures timely processing and delivery.

Developing Food Delivery App using ASP.NET Core MVC and Entity Framework Core

Admin Order Manage Page (Rejected Order Tab):

This page lists orders that have been rejected manually by an administrator or automatically due to processing delays. It provides a clear record of orders that did not proceed.

Developing Food Delivery App using ASP.NET Core MVC and Entity Framework Core

Project Setup:

First, create a new ASP.NET Core MVC project named FoodDeliveryApp and add the following packages. Then, execute the following commands in the Visual Studio Package Manager console to install the EF Core Packages.

  • Install-Package Microsoft.EntityFrameworkCore.SqlServer
  • Install-Package Microsoft.EntityFrameworkCore.Tools
Creating Models:

Models represent the domain entities and map to database tables (using Entity Framework Core). They capture the data structure and relationships in the application.

Admin Model:

Create a class file named Admin.cs within the Models folder, and then copy and paste the following code. This Model represents an administrative user (e.g., SuperAdmin) who can log in to manage items and orders. It holds details such as AdminId, Name, Email, Password, PhoneNumber, and Role. The Admin Model authenticates and authorizes admin users in the admin module.

using System.ComponentModel.DataAnnotations;
namespace FoodDeliveryApp.Models
{
    public class Admin
    {
        [Key]
        public int AdminId { get; set; }

        [Required]
        [StringLength(50)]
        public string Name { get; set; }

        [Required]
        [StringLength(100)]
        [EmailAddress]
        public string Email { get; set; }

        [Required]
        [StringLength(255)]
        public string Password { get; set; }

        [StringLength(15)]
        public string PhoneNumber { get; set; }

        [StringLength(50)]
        public string Role { get; set; } // e.g., "SuperAdmin", "Manager", etc.
    }
}
Customer Model:

Create a class file named Customer.cs within the Models folder, and then copy and paste the following code. This Model represents a customer who can register, log in, and place orders. It contains fields such as CustomerId, CustomerName, Email, Password, PhoneNumber, Address, and a navigation property (Orders) to track the orders a customer has placed. The Customer Model is used during customer registration, login, and order history retrieval.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace FoodDeliveryApp.Models
{
    public class Customer
    {
        [Key]
        public int CustomerId { get; set; }

        [Required]
        [StringLength(50)]
        public string CustomerName { get; set; }

        [Required]
        [StringLength(100)]
        [EmailAddress]
        public string Email { get; set; }

        [Required]
        [StringLength(255)]
        public string Password { get; set; }

        [StringLength(15)]
        public string PhoneNumber { get; set; }

        [StringLength(200)]
        public string Address { get; set; }

        // Navigation property: a Customer can have multiple Orders
        public List<Order> Orders { get; set; }
    }
}
Item Model:

Create a class file named Item.cs within the Models folder and then copy and paste the following code. This Model represents the food items or menu items (e.g., Margherita Pizza, Burger, etc.) that customers can order. It contains properties like ItemId, ItemName, Description, Price, and IsAvailable to determine if an item can be ordered. The Item Model will be used in the customer module to display available items and in the admin module for Product or Item management.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FoodDeliveryApp.Models
{
    public class Item
    {
        [Key]
        public int ItemId { get; set; }
        public string ItemName { get; set; }
        public string? Description { get; set; }

        [Column(TypeName = "decimal(18,2)")]
        public decimal Price { get; set; }
        public bool IsAvailable { get; set; }
    }
}
OrderStatus Enum

Create a class file named OrderStatus.cs within the Models folder, and then copy and paste the following code. This Enum defines an order’s possible statuses, including values such as Pending, Accepted, and Rejected. It is used to set and check an order’s current state throughout its lifecycle.

namespace FoodDeliveryApp.Models
{
    public enum OrderStatus
    {
        Pending = 1,
        Accepted = 2,
        Rejected = 3
    }
}
Order Model:

Create a class file named Order.cs within the Models folder and then copy and paste the following code. This Model represents a customer’s order. It contains fields such as OrderId, CustomerId (with a navigation property to Customer), OrderDateTime, Status, Total Price, and a list of OrderItems. The Order Model is used when a customer orders; the order record tracks the overall transaction.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FoodDeliveryApp.Models
{
    public class Order
    {
        [Key]
        public int OrderId { get; set; }

        [ForeignKey("Customer")]
        public int CustomerId { get; set; }
        public Customer Customer { get; set; }

        public DateTime OrderDateTime { get; set; }

        [Required]
        public OrderStatus Status { get; set; }

        // Total price for the entire order
        [Column(TypeName ="decimal(18,2)")]
        public decimal Price { get; set; }

        // Navigation property to all items in this order
        public List<OrderItem> OrderItems { get; set; }
    }
}
OrderItem Model:

Create a class file named OrderItem.cs within the Models folder, and then copy and paste the following code. This Model represents individual items within an order. It contains properties such as OrderItemId, OrderId, ItemId (with a navigation property to Item), Quantity, and UnitPrice (capturing the price at order time). It is part of the Order model to detail which items were ordered and in what quantity.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FoodDeliveryApp.Models
{
    public class OrderItem
    {
        [Key]
        public int OrderItemId { get; set; }

        [ForeignKey("Order")]
        public int OrderId { get; set; }
        public Order Order { get; set; }

        [ForeignKey("Item")]
        public int ItemId { get; set; }
        public Item Item { get; set; }

        public int Quantity { get; set; }

        // The price for a single unit at the time of ordering
        [Column(TypeName = "decimal(18,2)")]
        public decimal UnitPrice { get; set; }
    }
}
ViewModels

View Models package data specifically for display or input in the Views. They help maintain separation of concerns between domain models (used for data/persistence) and the UI layer. We will create View Models to pass data to/from views in a more structured way, rather than directly using entities. So, first create a folder named ViewModels in the project root directory where we will create all our View Models.

CustomerOrderViewModel

Create a class file named CustomerOrderViewModel.cs within the ViewModels folder, and then copy and paste the following code. These two View Models are used to present a customer’s order history in a structured way. The CustomerOrderViewModel holds overall info (OrderId, OrderTotalPrice, Status, etc.) plus a list of CustomerOrderItemViewModel to describe each item’s name, quantity, and unit price. It will be used In the “My Orders” view so customers can see a summarized version of their orders.

namespace FoodDeliveryApp.ViewModels
{
    public class CustomerOrderViewModel
    {
        public int OrderId { get; set; }
        public decimal OrderTotalPrice { get; set; }
        public string Status { get; set; }
        public string OrderDateTime { get; set; }

        // A collection of items in this order
        public List<CustomerOrderItemViewModel> Items { get; set; }
    }

    public class CustomerOrderItemViewModel
    {
        public string ItemName { get; set; }
        public int Quantity { get; set; }
        public decimal UnitPrice { get; set; }
    }
}
AdminOrdersViewModel

Create a class file named AdminOrdersViewModel.cs within the ViewModels folder, and then copy and paste the following code. The AdminOrdersViewModel is used to show order details to the admin. It Contains OrderId, CustomerName, CustomerEmail, a list of items (as strings like “Pizza x1 ($9.99)”), TotalPrice, and OrderDateTime. It is used on the admin dashboard to present orders grouped by their status (Pending, Accepted, Rejected).

namespace FoodDeliveryApp.ViewModels
{
    public class AdminOrdersViewModel
    {
        public int OrderId { get; set; }
        public string CustomerName { get; set; }
        public string CustomerEmail { get; set; }
        public List<string> Items { get; set; } // e.g., "Pizza x1 ($9.99)"
        public decimal TotalPrice { get; set; }
        public string OrderDateTime { get; set; }
    }
}
AdminLoginViewModel

Create a class file named AdminLoginViewModel.cs within the ViewModels folder, and then copy and paste the following code. This View Model is used during login flows for admin. It typically has Email, Password, and relevant validation attributes. The AdminLoginViewModel is used in the admin login view to validate admin credentials.

using System.ComponentModel.DataAnnotations;
namespace FoodDeliveryApp.ViewModels
{
    public class AdminLoginViewModel
    {
        [Required]
        [EmailAddress]
        [StringLength(100)]
        public string Email { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [StringLength(50)]
        public string Password { get; set; }
    }
}
CustomerLoginViewModel

Create a class file named CustomerLoginViewModel.cs within the ViewModels folder, and then copy and paste the following code. This View Model is used during login flows for customers. The CustomerLoginViewModel is used in the customer login view for validating customer credentials

using System.ComponentModel.DataAnnotations;
namespace FoodDeliveryApp.ViewModels
{
    public class CustomerLoginViewModel
    {
        [Required]
        [EmailAddress]
        [StringLength(100)]
        public string Email { get; set; }
        [Required]
        [DataType(DataType.Password)]
        [StringLength(50)]
        public string Password { get; set; }
    }
}
CustomerRegisterViewModel

Create a class file named CustomerRegisterViewModel.cs within the ViewModels folder, and then copy and paste the following code. This View Model is used when a customer registers for a new account. It holds fields for CustomerName, Email, Password, PhoneNumber, and Address, plus validation. It is used in the customer registration view.

using System.ComponentModel.DataAnnotations;
namespace FoodDeliveryApp.ViewModels
{
    public class CustomerRegisterViewModel
    {
        [Required]
        [StringLength(50)]
        public string CustomerName { get; set; }

        [Required]
        [EmailAddress]
        [StringLength(100)]
        public string Email { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [StringLength(255)]
        public string Password { get; set; }

        [StringLength(15)]
        public string PhoneNumber { get; set; }

        [StringLength(200)]
        public string Address { get; set; }
    }
}
MultipleOrderItemViewModel

Create a class file named MultipleOrderItemViewModel.cs within the ViewModels folder, and then copy and paste the following code. These View Models handle the process of placing an order with multiple items. They are used in the “Place Order” view, allowing customers to specify quantities for several items at once.

namespace FoodDeliveryApp.ViewModels
{
    public class MultipleItemOrderViewModel
    {
        public List<OrderItemViewModel> Items { get; set; }
    }

    public class OrderItemViewModel
    {
        public int ItemId { get; set; }
        public string ItemName { get; set; }
        public decimal Price { get; set; }
        public bool IsAvailable { get; set; }

        // Quantity the user wishes to order (0 means not selected)
        public int Quantity { get; set; }
    }
}
DbContext with Data Seeding

First, create a folder named Data in the project root directory. Then, create a class file named ApplicationDbContext.cs within the Data folder and copy and paste the following code. The ApplicationDbContext class acts as the bridge between the application and the SQL Server database using Entity Framework Core. It contains DbSet properties for Admins, Customers, Items, Orders, and OrderItems and also overrides OnModelCreating to seed initial data (for Admins, Customers, Items, Orders, and OrderItems) and configure relationships. The ApplicationDbContext is used throughout the application to query and update the database.

using FoodDeliveryApp.Models;
using Microsoft.EntityFrameworkCore;
using System;

namespace FoodDeliveryApp.Data
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }

        public DbSet<Admin> Admins { get; set; }
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Item> Items { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<OrderItem> OrderItems { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // Admin
            modelBuilder.Entity<Admin>().HasData(
                new Admin { AdminId = 1, Name = "SuperAdmin", Email = "admin@example.com", Password = "admin@123", PhoneNumber = "1234567890", Role = "SuperAdmin" }
            );

            // Customers
            modelBuilder.Entity<Customer>().HasData(
                new Customer { CustomerId = 1, CustomerName = "John Doe", Email = "john@example.com", Password = "john@123", PhoneNumber = "9876543210", Address = "123 Main Street" },
                new Customer { CustomerId = 2, CustomerName = "Jane Smith", Email = "jane@example.com", Password = "jane@123", PhoneNumber = "5554443322", Address = "456 South Avenue" }
            );

            // Items
            modelBuilder.Entity<Item>().HasData(
                new Item { ItemId = 1, ItemName = "Margherita Pizza", Description = "Classic cheese pizza", Price = 9.99m, IsAvailable = true },
                new Item { ItemId = 2, ItemName = "Veggie Burger", Description = "Delicious burger with veggie patty", Price = 7.49m, IsAvailable = true },
                new Item { ItemId = 3, ItemName = "Pepperoni Pizza", Description = "Pizza with pepperoni toppings", Price = 10.99m, IsAvailable = true },
                new Item { ItemId = 4, ItemName = "Chicken Burger", Description = "Juicy chicken burger", Price = 8.49m, IsAvailable = true },
                new Item { ItemId = 5, ItemName = "French Fries", Description = "Crispy golden fries", Price = 3.49m, IsAvailable = true }
            );

            // Orders
            modelBuilder.Entity<Order>().HasData(
                new Order { OrderId = 1, CustomerId = 1, OrderDateTime = DateTime.Today.AddMinutes(10), Status = OrderStatus.Pending, Price = 9.99m },  // Single item #1
                new Order { OrderId = 2, CustomerId = 2, OrderDateTime = DateTime.Today.AddMinutes(25), Status = OrderStatus.Accepted, Price = 7.49m },  // Single item #2
                new Order { OrderId = 3, CustomerId = 1, OrderDateTime = DateTime.Today.AddMinutes(32), Status = OrderStatus.Accepted, Price = 10.98m }, // Items #2, #5
                new Order { OrderId = 4, CustomerId = 2, OrderDateTime = DateTime.Today.AddMinutes(45), Status = OrderStatus.Pending, Price = 10.99m },  // Single item #3
                new Order { OrderId = 5, CustomerId = 2, OrderDateTime = DateTime.Today.AddMinutes(45), Status = OrderStatus.Rejected, Price = 10.98m }, // Items #2, #5
                new Order { OrderId = 6, CustomerId = 1, OrderDateTime = DateTime.Today.AddMinutes(50), Status = OrderStatus.Rejected, Price = 10.99m }  // Single item #3
            );

            // OrderItems
            modelBuilder.Entity<OrderItem>().HasData(
                new OrderItem { OrderItemId = 1, OrderId = 1, ItemId = 1, Quantity = 1, UnitPrice = 9.99m },
                new OrderItem { OrderItemId = 2, OrderId = 2, ItemId = 2, Quantity = 1, UnitPrice = 7.49m },
                new OrderItem { OrderItemId = 3, OrderId = 3, ItemId = 2, Quantity = 1, UnitPrice = 7.49m },
                new OrderItem { OrderItemId = 4, OrderId = 3, ItemId = 5, Quantity = 1, UnitPrice = 3.49m },
                new OrderItem { OrderItemId = 5, OrderId = 4, ItemId = 3, Quantity = 1, UnitPrice = 10.99m },
                new OrderItem { OrderItemId = 6, OrderId = 5, ItemId = 2, Quantity = 1, UnitPrice = 7.49m },
                new OrderItem { OrderItemId = 7, OrderId = 5, ItemId = 5, Quantity = 1, UnitPrice = 3.49m },
                new OrderItem { OrderItemId = 8, OrderId = 6, ItemId = 3, Quantity = 1, UnitPrice = 10.99m }
            );
        }
    }
}
Modify AppSettings.json File:

Please modify the appsettings.json file as follows. It contains the application’s configuration settings. The ConnectionStrings section holds the SQL Server connection string (DefaultConnection) that the DbContext uses to connect to the database.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=FoodDeliveryDB;Trusted_Connection=True;TrustServerCertificate=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
Modify the Program Class:

The Program.cs class serves as the entry point for the ASP.NET Core application and configures the web host. It registers services such as controllers with views, the ApplicationDbContext (using SQL Server with the connection string from appsettings.json), and cookie-based authentication. It also sets up middleware components (HTTPS redirection, static files, routing, authentication, and authorization), maps default routes, and starts the application.

Please modify the Program class as follows. We have registered the required services and middleware components to the request processing pipeline.

using FoodDeliveryApp.Data;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;

namespace FoodDeliveryApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllersWithViews();

            // Configure SQL Server DbContext
            builder.Services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
            );

            // Add Cookie Authentication
            builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    // Wherever you want to redirect when unauthenticated
                    options.LoginPath = "/Home/Index"; 
                    options.AccessDeniedPath = "/Home/AccessDenied";                                                  
                });

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            // Enable Authentication & Authorization
            app.UseAuthentication();
            app.UseAuthorization();

            app.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");

            app.Run();
        }
    }
}
Database Migrations

Open the Package Manager Console in Visual Studio, and then execute the following commands:

  • Add-Migration Mig1
  • Update-Database

This should create the FoodDeliveryDB with the required tables, as shown in the image below.

Database Migrations

Creating Controller and Views

Controllers handle incoming requests, coordinate with the DbContext, and pass data to or from the Views (or redirect as needed). View represents the user interface with which the end user can interact with the application.

AdminAccountController

Create a new Empty MVC controller named AdminAccountController.cs within the Controllers folder, then copy and paste the following code. This Controller manages Admin login/logout functionality. It uses cookie-based authentication (signing in the admin with claims). It allows admin users to access the admin dashboard and management features.

using FoodDeliveryApp.Data;
using FoodDeliveryApp.ViewModels;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;

namespace FoodDeliveryApp.Controllers
{
    public class AdminAccountController : Controller
    {
        private readonly ApplicationDbContext _context;

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

        // GET: /AdminAccount/Login
        [HttpGet]
        public IActionResult Login()
        {
            return View();
        }

        // POST: /AdminAccount/Login
        [HttpPost]
        public async Task<IActionResult> Login(AdminLoginViewModel model)
        {
            if (!ModelState.IsValid) return View(model);

            var admin = _context.Admins
                .FirstOrDefault(a => a.Email == model.Email && a.Password == model.Password);

            if (admin == null)
            {
                ModelState.AddModelError("", "Invalid Admin Credentials.");
                return View(model);
            }

            // Build admin claims (e.g., email, role, etc.)
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, admin.Name),
                new Claim(ClaimTypes.Email, admin.Email),
                new Claim("AdminId", admin.AdminId.ToString()),
                // Optionally, set role for role-based checks
                new Claim(ClaimTypes.Role, "Admin")
            };

            var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
            var principal = new ClaimsPrincipal(claimsIdentity);

            // Sign in: create the auth cookie
            await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                principal);

            // Redirect to admin area
            return RedirectToAction("Orders", "Admin");
        }

        public async Task<IActionResult> Logout()
        {
            // Sign out: remove the auth cookie
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return RedirectToAction("Login");
        }
    }
}
Actions:
  • Login (GET): Renders the login form.
  • Login (POST): Validate credentials, set up claims, and sign in the admin.
  • Logout: Signs the user out by removing the auth cookie.
Customer Admin Layout View

Create a view named _AdminLayout.cshtml within the Views/Shared folder, then copy and paste the following code. This provides all admin pages with the overall structure (header, navigation, footer). It includes admin-specific navigation links (e.g., Products, Orders, Logout) and references to Bootstrap and jQuery libraries.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Admin - @ViewData["Title"]</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="@Url.Action("Index", "Home")">FoodDeliveryApp Admin</a>

        <div class="collapse navbar-collapse">
            @if (User?.Identity?.IsAuthenticated == true && User.IsInRole("Admin"))
            {
                <ul class="navbar-nav mr-auto">
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="Admin" asp-action="Products">Products</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="Admin" asp-action="Orders">Orders</a>
                    </li>
                </ul>
            }
           
            <ul class="navbar-nav ml-auto">
                @if (User?.Identity?.IsAuthenticated == true && User.IsInRole("Admin"))
                {
                    <!-- Admin is logged in -->
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="AdminAccount" asp-action="Logout">Logout</a>
                    </li>
                }
                else
                {
                    <!-- Not logged in or not Admin role -->
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="AdminAccount" asp-action="Login">Login</a>
                    </li>
                }
            </ul>
        </div>
    </nav>

    <div class="container mt-4">
        @RenderBody()
    </div>

    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>
Creating Admin Login View

Create a view named Login.cshtml within the Views/AdminAccount folder and copy and paste the following code. The Login view provides the login form for admin users using AdminLoginViewModel.

@model FoodDeliveryApp.ViewModels.AdminLoginViewModel

@{
    Layout = "~/Views/Shared/_AdminLayout.cshtml";
    ViewData["Title"] = "Admin Login";
}

<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <!-- Admin Login Card -->
            <div class="card shadow-sm">
                <div class="card-header bg-primary text-white text-center">
                    <h4 class="mb-0">Admin Login</h4>
                </div>
                <div class="card-body">
                    <form asp-controller="AdminAccount" asp-action="Login" method="post">
                        <div class="mb-3">
                            <label asp-for="Email" class="form-label"></label>
                            <input asp-for="Email" class="form-control" placeholder="Enter your email" />
                            <span asp-validation-for="Email" class="text-danger"></span>
                        </div>
                        <div class="mb-3">
                            <label asp-for="Password" class="form-label"></label>
                            <input asp-for="Password" type="password" class="form-control" placeholder="Enter your password" />
                            <span asp-validation-for="Password" class="text-danger"></span>
                        </div>
                        <div class="d-grid">
                            <button type="submit" class="btn btn-primary">Login</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

@section Scripts {
    @await Html.PartialAsync("_ValidationScriptsPartial")
}
Creating CustomerAccountController

Create a new Empty MVC controller named CustomerAccountController.cs within the Controllers folder and then copy and paste the following code. This controller manages Customer registration, login, and logout flows. That means it is used for customer onboarding and authentication.

using FoodDeliveryApp.Data;
using FoodDeliveryApp.Models;
using FoodDeliveryApp.ViewModels;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;

namespace FoodDeliveryApp.Controllers
{
    public class CustomerAccountController : Controller
    {
        private readonly ApplicationDbContext _context;

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

        // GET: /CustomerAccount/Register
        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }

        // POST: /CustomerAccount/Register
        [HttpPost]
        public IActionResult Register(CustomerRegisterViewModel model)
        {
            if (!ModelState.IsValid)
            {
                // Return the same view with validation errors
                return View(model);
            }

            // Check if email exists
            bool emailExists = _context.Customers
                .AsNoTracking()
                .Any(c => c.Email == model.Email);

            if (emailExists)
            {
                ModelState.AddModelError("", "Email already in use.");
                return View(model);
            }

            // Create a new Customer entity from the ViewModel
            var newCustomer = new Customer
            {
                CustomerName = model.CustomerName,
                Email = model.Email,
                Password = model.Password, // In production, store a hashed password!
                PhoneNumber = model.PhoneNumber,
                Address = model.Address
            };

            _context.Customers.Add(newCustomer);
            _context.SaveChanges();

            return RedirectToAction("Login");
        }

        // GET: /CustomerAccount/Login
        [HttpGet]
        public IActionResult Login()
        {
            return View();
        }

        // POST: /CustomerAccount/Login
        [HttpPost]
        public async Task<IActionResult> Login(CustomerLoginViewModel model)
        {
            if (!ModelState.IsValid) return View(model);

            var customer = _context.Customers
                .FirstOrDefault(c => c.Email == model.Email && c.Password == model.Password);

            if (customer == null)
            {
                ModelState.AddModelError("", "Invalid Customer Credentials.");
                return View(model);
            }

            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, customer.CustomerName),
                new Claim(ClaimTypes.Email, customer.Email),
                new Claim("CustomerId", customer.CustomerId.ToString()),
                // Optionally set a role for customers
                new Claim(ClaimTypes.Role, "Customer")
            };

            var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
            var principal = new ClaimsPrincipal(claimsIdentity);

            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);

            return RedirectToAction("MyOrders", "Customer");
        }

        public async Task<IActionResult> Logout()
        {
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return RedirectToAction("Login");
        }
    }
}
Actions:
  • Register (GET/POST): Registers a new customer after validating input and ensuring the email isn’t already used.
  • Login (GET/POST): Validates customer credentials and signs in via cookie-based auth.
  • Logout: Signs out the customer.
Creating Customer Layout View

Create a view named _CustomerLayout.cshtml within the Views/Shared folder, then copy and paste the following code. This serves as the base layout for customer-facing pages. It contains navigation links for customers (e.g., Place Order, My Orders, Login/Register) and ensures consistent styling across customer pages.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Customer - @ViewData["Title"]</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="@Url.Action("Index", "Home")">FoodDeliveryApp Customer</a>

        <div class="collapse navbar-collapse">
            @if (User?.Identity?.IsAuthenticated == true && User.IsInRole("Customer"))
            {
                <ul class="navbar-nav">
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="Customer" asp-action="PlaceOrder">Place Order</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="Customer" asp-action="MyOrders">My Orders</a>
                    </li>
                </ul>
            }
            
            <ul class="navbar-nav ml-auto">
                @if (User?.Identity?.IsAuthenticated == true && User.IsInRole("Customer"))
                {
                    <!-- The user is logged in as Customer -->
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="CustomerAccount" asp-action="Logout">Logout</a>
                    </li>
                }
                else
                {
                    <!-- The user is not logged in or not a Customer -->
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="CustomerAccount" asp-action="Login">Login</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="CustomerAccount" asp-action="Register">Register</a>
                    </li>
                }
            </ul>
        </div>
    </nav>

    <div class="container mt-4">
        @RenderBody()
    </div>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>
Creating Customer Registration View

Create a view named Register.cshtml within the Views/CustomerAccount folder, then copy and paste the following code. The Register view provides the registration form for new customer registration using CustomerRegisterViewModel.

@model FoodDeliveryApp.ViewModels.CustomerRegisterViewModel

@{
    Layout = "~/Views/Shared/_CustomerLayout.cshtml";
    ViewData["Title"] = "Customer Registration";
}

<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <!-- Registration Card -->
            <div class="card shadow-sm">
                <div class="card-header bg-success text-white text-center">
                    <h4 class="mb-0">Customer Registration</h4>
                </div>
                <div class="card-body">
                    <form asp-controller="CustomerAccount" asp-action="Register" method="post">
                        <div class="mb-3">
                            <label asp-for="CustomerName" class="form-label"></label>
                            <input asp-for="CustomerName" class="form-control" placeholder="Enter your full name" />
                            <span asp-validation-for="CustomerName" class="text-danger"></span>
                        </div>
                        <div class="mb-3">
                            <label asp-for="Email" class="form-label"></label>
                            <input asp-for="Email" class="form-control" placeholder="Enter your email address" />
                            <span asp-validation-for="Email" class="text-danger"></span>
                        </div>
                        <div class="mb-3">
                            <label asp-for="Password" class="form-label"></label>
                            <input asp-for="Password" type="password" class="form-control" placeholder="Enter a secure password" />
                            <span asp-validation-for="Password" class="text-danger"></span>
                        </div>
                        <div class="mb-3">
                            <label asp-for="PhoneNumber" class="form-label"></label>
                            <input asp-for="PhoneNumber" class="form-control" placeholder="Enter your phone number" />
                            <span asp-validation-for="PhoneNumber" class="text-danger"></span>
                        </div>
                        <div class="mb-3">
                            <label asp-for="Address" class="form-label"></label>
                            <input asp-for="Address" class="form-control" placeholder="Enter your address" />
                            <span asp-validation-for="Address" class="text-danger"></span>
                        </div>
                        <div class="d-grid mt-4">
                            <button type="submit" class="btn btn-success">Register</button>
                        </div>
                    </form>
                    <div class="mt-3 text-center">
                        <small>Already have an account? <a asp-controller="CustomerAccount" asp-action="Login">Login here</a>.</small>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

@section Scripts {
    @await Html.PartialAsync("_ValidationScriptsPartial")
}
Creating Customer Login View

Create a view named Login.cshtml within the Views/CustomerAccount folder, then copy and paste the following code. The Login.cshtml view provides the login form for customer to login using CustomerLoginViewModel.

@model FoodDeliveryApp.ViewModels.CustomerLoginViewModel

@{
    Layout = "~/Views/Shared/_CustomerLayout.cshtml";
    ViewData["Title"] = "Customer Login";
}

<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <!-- Card for the login form -->
            <div class="card shadow-sm">
                <div class="card-header bg-primary text-white text-center">
                    <h4 class="mb-0">Customer Login</h4>
                </div>
                <div class="card-body">
                    <form asp-controller="CustomerAccount" asp-action="Login" method="post">
                        <div class="mb-3">
                            <label asp-for="Email" class="form-label"></label>
                            <input asp-for="Email" class="form-control" placeholder="Enter your email" />
                            <span asp-validation-for="Email" class="text-danger"></span>
                        </div>
                        <div class="mb-3">
                            <label asp-for="Password" class="form-label"></label>
                            <input asp-for="Password" class="form-control" placeholder="Enter your password" />
                            <span asp-validation-for="Password" class="text-danger"></span>
                        </div>
                        <div class="d-grid">
                            <button type="submit" class="btn btn-primary">Login</button>
                        </div>
                    </form>
                    <div class="mt-3 text-center">
                        <small>Don't have an account? <a asp-controller="CustomerAccount" asp-action="Register">Register now</a></small>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

@section Scripts {
    @await Html.PartialAsync("_ValidationScriptsPartial")
}
Customer Controller for Placing Orders

Create a new Empty MVC controller named CustomerController.cs within the Controllers folder and then copy and paste the following code. This Controller is used to manage customer order interactions. It requires the customer to be logged in ([Authorize] or [Authorize(Roles=”Customer”)]). It is used when the customer wants to place orders and view their order history

using FoodDeliveryApp.Data;
using FoodDeliveryApp.Models;
using FoodDeliveryApp.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace FoodDeliveryApp.Controllers
{
    [Authorize] // or [Authorize(Roles = "Customer")]
    public class CustomerController : Controller
    {
        private readonly ApplicationDbContext _context;
        public CustomerController(ApplicationDbContext context)
        {
            _context = context;
        }

        // GET: /Customer/PlaceOrder
        // Displays all items with Quantity fields in one form
        [HttpGet]
        public IActionResult PlaceOrder()
        {
            // 1) Check CustomerId claim
            var customerIdClaim = User.FindFirst("CustomerId");
            if (customerIdClaim == null)
            {
                return RedirectToAction("Login", "CustomerAccount");
            }

            // 2) Get only available items
            var items = _context.Items
                .AsNoTracking()
                .Where(i => i.IsAvailable)
                .ToList();

            // 3) Map items to the new view model
            var model = new MultipleItemOrderViewModel
            {
                Items = items.Select(i => new OrderItemViewModel
                {
                    ItemId = i.ItemId,
                    ItemName = i.ItemName,
                    Price = i.Price,
                    IsAvailable = i.IsAvailable,
                    Quantity = 0
                }).ToList()
            };

            // 4) Return the view with the form
            return View(model);
        }

        // POST: /Customer/PlaceOrder
        // Processes the selected items and creates a single Order with multiple OrderItems
        [HttpPost]
        public IActionResult PlaceOrder(MultipleItemOrderViewModel model)
        {
            // 1) Verify the user is still authenticated
            var customerIdClaim = User.FindFirst("CustomerId");
            if (customerIdClaim == null)
            {
                return RedirectToAction("Login", "CustomerAccount");
            }
            int customerId = int.Parse(customerIdClaim.Value);

            // 2) Filter out items with Quantity > 0
            var selectedItems = model.Items.Where(x => x.Quantity > 0).ToList();

            if (!selectedItems.Any())
            {
                ModelState.AddModelError("", "No items selected. Please pick at least one item.");
                return View(model); // Re-display the form
            }

            // 3) Create a new order
            var order = new Order
            {
                CustomerId = customerId,
                OrderDateTime = DateTime.Now,
                Status = OrderStatus.Pending,
                OrderItems = new List<OrderItem>()
            };

            // 4) For each selected item, create an OrderItem
            foreach (var sel in selectedItems)
            {
                // 1) Look up the real item from the DB
                var dbItem = _context.Items.FirstOrDefault(i => i.ItemId == sel.ItemId && i.IsAvailable);

                if (dbItem == null)
                {
                    // The item was not found or not available
                    continue; // or handle error
                }

                // 2) Use the real, current price from DB
                var orderItem = new OrderItem
                {
                    ItemId = dbItem.ItemId,
                    Quantity = sel.Quantity,
                    UnitPrice = dbItem.Price  
                };

                order.OrderItems.Add(orderItem);
            }

            // 5) Calculate total Price
            order.Price = order.OrderItems.Sum(oi => oi.UnitPrice * oi.Quantity);

            // 6) Save
            _context.Orders.Add(order);
            _context.SaveChanges();

            // 7) Redirect to MyOrders
            return RedirectToAction("MyOrders");
        }

        public IActionResult MyOrders()
        {
            var customerIdClaim = User.FindFirst("CustomerId");
            if (customerIdClaim == null)
            {
                return RedirectToAction("Login", "CustomerAccount");
            }
            int customerId = int.Parse(customerIdClaim.Value);

            // Get all orders for this customer
            var orders = _context.Orders
                .AsNoTracking()
                .Include(o => o.OrderItems)
                    .ThenInclude(oi => oi.Item)
                .Where(o => o.CustomerId == customerId)
                .OrderByDescending(o => o.OrderDateTime)
                .ToList();

            var customerOrders = new List<CustomerOrderViewModel>();

            foreach (var order in orders)
            {
                var orderVm = new CustomerOrderViewModel
                {
                    OrderId = order.OrderId,
                    OrderTotalPrice = order.Price,
                    Status = order.Status.ToString(),
                    OrderDateTime = order.OrderDateTime.ToString("g"),
                    Items = new List<CustomerOrderItemViewModel>()
                };

                // Add each item in the order
                foreach (var oi in order.OrderItems)
                {
                    orderVm.Items.Add(new CustomerOrderItemViewModel
                    {
                        ItemName = oi.Item.ItemName,
                        Quantity = oi.Quantity,
                        UnitPrice = oi.UnitPrice
                    });
                }

                customerOrders.Add(orderVm);
            }

            return View(customerOrders);
        }
    }
}
Actions:
  • PlaceOrder (GET): Retrieves available items and maps them to the view model for the order form.
  • PlaceOrder (POST): Processes the submitted order, creates Order and OrderItem entries, calculates total price, and saves the order.
  • MyOrders: Retrieves the authenticated customer’s order history.
Creating Customer Place Order View

Create a view named PlaceOrder.cshtml within the Views/Customer folder, then copy and paste the following code. The PlaceOrder view provides a form (with a table of available items) for placing orders with multiple items.

@model FoodDeliveryApp.ViewModels.MultipleItemOrderViewModel
@{
    Layout = "~/Views/Shared/_CustomerLayout.cshtml";
    ViewData["Title"] = "Place an Order";
}

<div class="container mt-4">
    <div class="row">
        <div class="col-md-8 offset-md-2">
            <!-- Card Wrapper -->
            <div class="card">
                <div class="card-header bg-primary text-white">
                    <h5 class="mb-0">Place an Order</h5>
                </div>
                <div class="card-body">
                   
                    <!-- Form -->
                    <form asp-action="PlaceOrder" method="post" class="form-horizontal">
                        <p class="text-muted mb-3">
                            Select the desired quantity for each available item you wish to order.
                            Quantities set to "0" will be ignored.
                        </p>

                        <table class="table table-bordered table-hover">
                            <thead class="thead-light">
                                <tr>
                                    <th>Item Name</th>
                                    <th>Price</th>
                                    <th style="width:120px;">Quantity</th>
                                </tr>
                            </thead>
                            <tbody>
                                @for (int i = 0; i < Model.Items.Count; i++)
                                {
                                    var item = Model.Items[i];
                                    <tr>
                                        <td>@item.ItemName</td>
                                        <td>@item.Price.ToString("C")</td>
                                        <td>
                                            <input asp-for="@Model.Items[i].Quantity"
                                                   class="form-control"
                                                   min="0" />

                                            <!-- Hidden field to post necessary data back -->
                                            <input asp-for="@Model.Items[i].ItemId" type="hidden" />
                                           
                                        </td>
                                    </tr>
                                }
                            </tbody>
                        </table>

                        <div class="text-right">
                            <button type="submit" class="btn btn-success">
                                <i class="fas fa-shopping-cart"></i> Place Order
                            </button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

@section Scripts {
    <!-- Optional: Include FontAwesome for the cart icon -->
    <script src="https://kit.fontawesome.com/your-fontawesome-kit.js" crossorigin="anonymous"></script>
    @await Html.PartialAsync("_ValidationScriptsPartial")
}
Creating Customer My Orders View (Order History)

Create a view named MyOrders.cshtml within the Views/Customer folder, then copy and paste the following code. The MyOrders view displays the logged-in customer’s order history in a professional, tabular layout with status badges.

@model List<FoodDeliveryApp.ViewModels.CustomerOrderViewModel>
@{
    Layout = "~/Views/Shared/_CustomerLayout.cshtml";
    ViewData["Title"] = "My Orders";
}

<div class="container mt-5">
    <!-- Card container for orders -->
    <div class="card shadow-sm">
        <div class="card-header bg-dark text-white">
            <h2 class="mb-0">My Orders</h2>
            <small class="text-muted">Here are your recently placed orders.</small>
        </div>
        <div class="card-body">
            <div class="table-responsive">
                <table class="table table-striped table-hover">
                    <thead>
                        <tr>
                            <th>Order #</th>
                            <th>Items</th>
                            <th>Order Total</th>
                            <th>Status</th>
                            <th>Placed At</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var order in Model)
                        {
                            <tr>
                                <td>@order.OrderId</td>
                                <td>
                                    <ul class="list-unstyled mb-0">
                                        @foreach (var item in order.Items)
                                        {
                                            <li>
                                                <strong>@item.ItemName</strong>
                                                <span class="text-muted">x @item.Quantity</span>
                                                <span class="text-muted">(@item.UnitPrice.ToString("C"))</span>
                                            </li>
                                        }
                                    </ul>
                                </td>
                                <td>@order.OrderTotalPrice.ToString("C")</td>
                                <td>
                                    @if (order.Status == "Pending")
                                    {
                                        <span class="badge bg-warning text-dark">@order.Status</span>
                                    }
                                    else if (order.Status == "Accepted")
                                    {
                                        <span class="badge bg-success">@order.Status</span>
                                    }
                                    else if (order.Status == "Rejected")
                                    {
                                        <span class="badge bg-danger">@order.Status</span>
                                    }
                                    else
                                    {
                                        <span class="badge bg-secondary">@order.Status</span>
                                    }
                                </td>
                                <td>@order.OrderDateTime</td>
                            </tr>
                        }
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>
Admin Controller for Managing Orders

Create a new Empty MVC controller named AdminController.cs within the Controllers folder, then copy and paste the following code. This controller enables admins to manage orders and view products. It requires an Admin role ([Authorize(Roles=”Admin”)]). It is used in the admin dashboard to manage orders and products.

using FoodDeliveryApp.Data;
using FoodDeliveryApp.Models;
using FoodDeliveryApp.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace FoodDeliveryApp.Controllers
{
    [Authorize(Roles = "Admin")] // Only Admin role can access
    public class AdminController : Controller
    {
        private readonly ApplicationDbContext _context;

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

        // GET: /Admin/Products
        public IActionResult Products()
        {
            // If you want the AdminId from the claims (optional):
            // var adminIdClaim = User.FindFirst("AdminId");
            // int adminId = adminIdClaim != null ? int.Parse(adminIdClaim.Value) : 0;

            // Fetch items (AsNoTracking for read-only)
            var items = _context.Items
                .AsNoTracking()
                .ToList();

            return View(items);
        }

        // GET: /Admin/Orders
        public IActionResult Orders()
        {
            // If you need the admin ID:
            // var adminIdClaim = User.FindFirst("AdminId");
            // int adminId = adminIdClaim != null ? int.Parse(adminIdClaim.Value) : 0;

            // AutoRejectOldPendingOrders();

            var today = DateTime.Today;
            var tomorrow = today.AddDays(1);

            // Fetch only today's orders AsNoTracking
            var ordersToday = _context.Orders
                .AsNoTracking()
                .Include(o => o.Customer)
                .Include(o => o.OrderItems).ThenInclude(oi => oi.Item)
                .Where(o => o.OrderDateTime >= today && o.OrderDateTime < tomorrow)
                .OrderByDescending(o => o.OrderDateTime)
                .ToList();

            var pendingOrders = ordersToday.Where(o => o.Status == OrderStatus.Pending);
            var acceptedOrders = ordersToday.Where(o => o.Status == OrderStatus.Accepted);
            var rejectedOrders = ordersToday.Where(o => o.Status == OrderStatus.Rejected);

            // Convert to ViewModels
            ViewBag.PendingOrders = MapToAdminOrdersViewModel(pendingOrders);
            ViewBag.AcceptedOrders = MapToAdminOrdersViewModel(acceptedOrders);
            ViewBag.RejectedOrders = MapToAdminOrdersViewModel(rejectedOrders);

            return View();
        }

        // POST: /Admin/AcceptOrder
        [HttpPost]
        public IActionResult AcceptOrder(int orderId)
        {
            // No more session checks - [Authorize(Roles="Admin")] ensures only admins can reach here
            var order = _context.Orders.Find(orderId);
            if (order != null && order.Status == OrderStatus.Pending)
            {
                order.Status = OrderStatus.Accepted;
                _context.SaveChanges();
            }
            return RedirectToAction("Orders");
        }

        // POST: /Admin/RejectOrder
        [HttpPost]
        public IActionResult RejectOrder(int orderId)
        {
            var order = _context.Orders.Find(orderId);
            if (order != null && order.Status == OrderStatus.Pending)
            {
                order.Status = OrderStatus.Rejected;
                _context.SaveChanges();
            }
            return RedirectToAction("Orders");
        }

        private void AutoRejectOldPendingOrders()
        {
            var fiveMinutesAgo = DateTime.Now.AddMinutes(-5);

            var oldPendingOrders = _context.Orders
                .Where(o => o.Status == OrderStatus.Pending && o.OrderDateTime < fiveMinutesAgo)
                .ToList();

            foreach (var ord in oldPendingOrders)
            {
                ord.Status = OrderStatus.Rejected;
            }
            _context.SaveChanges();
        }

        // Maps Orders to AdminOrdersViewModel
        private List<AdminOrdersViewModel> MapToAdminOrdersViewModel(IEnumerable<Order> orders)
        {
            var result = new List<AdminOrdersViewModel>();
            foreach (var o in orders)
            {
                var itemsList = o.OrderItems.Select(oi =>
                    $"{oi.Item.ItemName} x{oi.Quantity} (${oi.UnitPrice})").ToList();

                result.Add(new AdminOrdersViewModel
                {
                    OrderId = o.OrderId,
                    CustomerName = o.Customer.CustomerName,
                    CustomerEmail = o.Customer.Email,
                    Items = itemsList,
                    TotalPrice = o.Price,
                    OrderDateTime = o.OrderDateTime.ToString("g")
                });
            }
            return result;
        }
    }
}
Actions:
  • Products: Lists all items available (for product management).
  • Orders: Displays today’s orders (with auto-refresh), groups them by status (Pending, Accepted, Rejected), and applies auto-rejection logic for pending orders older than five minutes.
  • AcceptOrder (POST) / RejectOrder (POST): Allows admins to manually change the status of pending orders.
Creating Admin Product or Items View

Create a view named Products.cshtml within the Views/Admin folder, then copy and paste the following code. The Products view displays the list of food items/products available to manage.

@model List<FoodDeliveryApp.Models.Item>
@{
    Layout = "~/Views/Shared/_AdminLayout.cshtml";
    ViewData["Title"] = "Admin Product Dashboard";
}

<div class="container-fluid mt-4">
    <div class="card shadow-sm">
        <div class="card-header bg-primary text-white">
            <h3 class="card-title mb-0">Admin Product Dashboard</h3>
        </div>
        <div class="card-body">
            <p class="card-text">Review and manage your current inventory items below. Use the dashboard to quickly identify items that are available or need attention.</p>
            <div class="table-responsive">
                <table class="table table-hover table-bordered">
                    <thead class="thead-light">
                        <tr>
                            <th>Item Name</th>
                            <th>Description</th>
                            <th>Price</th>
                            <th>Availability</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var i in Model)
                        {
                            <tr>
                                <td>@i.ItemName</td>
                                <td>@i.Description</td>
                                <td>@i.Price.ToString("C")</td>
                                <td>
                                    @if (i.IsAvailable)
                                    {
                                        <span class="badge bg-success">Available</span>
                                    }
                                    else
                                    {
                                        <span class="badge bg-danger">Unavailable</span>
                                    }
                                </td>
                            </tr>
                        }
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>
Creating Admin Orders View

Create a view named Orders.cshtml within the Views/Admin folder, and then copy and paste the following code. The Orders view uses a tabbed interface to show today’s orders categorized as Pending, Accepted, and Rejected. It also includes auto page refresh functionality. It also provides buttons for admins to accept or reject pending orders.

@{
    Layout = "~/Views/Shared/_AdminLayout.cshtml";
    ViewData["Title"] = "Today's Orders";
}

@*  Automatically refresh the page every 60 seconds *@ 
<meta http-equiv="refresh" content="60" />

<div class="container mt-4">
    <div class="row mb-3">
        <div class="col">
            <h2 class="mb-0">@ViewData["Title"]</h2>
            <small class="text-muted">The page refreshes every 60 seconds.</small>
        </div>
    </div>

    <!-- Nav Tabs -->
    <ul class="nav nav-tabs" id="ordersTab" role="tablist">
        <li class="nav-item" role="presentation">
            <button class="nav-link active" id="pending-tab" data-bs-toggle="tab" data-bs-target="#pendingTab" type="button" role="tab" aria-controls="pendingTab" aria-selected="true">
                Pending Orders
            </button>
        </li>
        <li class="nav-item" role="presentation">
            <button class="nav-link" id="accepted-tab" data-bs-toggle="tab" data-bs-target="#acceptedTab" type="button" role="tab" aria-controls="acceptedTab" aria-selected="false">
                Accepted Orders
            </button>
        </li>
        <li class="nav-item" role="presentation">
            <button class="nav-link" id="rejected-tab" data-bs-toggle="tab" data-bs-target="#rejectedTab" type="button" role="tab" aria-controls="rejectedTab" aria-selected="false">
                Rejected Orders
            </button>
        </li>
    </ul>

    <!-- Tab Content -->
    <div class="tab-content border border-top-0 p-3" id="ordersTabContent">
        <!-- PENDING ORDERS TAB -->
        <div class="tab-pane fade show active" id="pendingTab" role="tabpanel" aria-labelledby="pending-tab">
            @if (ViewBag.PendingOrders == null || !((List<FoodDeliveryApp.ViewModels.AdminOrdersViewModel>)ViewBag.PendingOrders).Any())
            {
                <div class="alert alert-info mt-3">
                    No pending orders today.
                </div>
            }
            else
            {
                <table class="table table-hover table-bordered align-middle">
                    <thead class="table-light">
                        <tr>
                            <th>Order #</th>
                            <th>Customer</th>
                            <th>Items</th>
                            <th>Total</th>
                            <th>Placed At</th>
                            <th>Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var order in (List<FoodDeliveryApp.ViewModels.AdminOrdersViewModel>)ViewBag.PendingOrders)
                        {
                            <tr>
                                <td>@order.OrderId</td>
                                <td>
                                    <strong>@order.CustomerName</strong><br />
                                    <small class="text-muted">(@order.CustomerEmail)</small>
                                </td>
                                <td>
                                    @foreach (var item in order.Items)
                                    {
                                        <span class="d-block">@item</span>
                                    }
                                </td>
                                <td>@order.TotalPrice.ToString("C")</td>
                                <td>@order.OrderDateTime</td>
                                <td>
                                    <form asp-controller="Admin" asp-action="AcceptOrder" method="post" class="d-inline">
                                        <input type="hidden" name="orderId" value="@order.OrderId" />
                                        <button class="btn btn-success btn-sm" type="submit">
                                            <i class="fas fa-check"></i> Accept
                                        </button>
                                    </form>
                                    <form asp-controller="Admin" asp-action="RejectOrder" method="post" class="d-inline">
                                        <input type="hidden" name="orderId" value="@order.OrderId" />
                                        <button class="btn btn-danger btn-sm" type="submit">
                                            <i class="fas fa-times"></i> Reject
                                        </button>
                                    </form>
                                </td>
                            </tr>
                        }
                    </tbody>
                </table>
            }
        </div>

        <!-- ACCEPTED ORDERS TAB -->
        <div class="tab-pane fade" id="acceptedTab" role="tabpanel" aria-labelledby="accepted-tab">
            @if (ViewBag.AcceptedOrders == null || !((List<FoodDeliveryApp.ViewModels.AdminOrdersViewModel>)ViewBag.AcceptedOrders).Any())
            {
                <div class="alert alert-info mt-3">
                    No accepted orders today.
                </div>
            }
            else
            {
                <table class="table table-hover table-bordered align-middle">
                    <thead class="table-light">
                        <tr>
                            <th>Order #</th>
                            <th>Customer</th>
                            <th>Items</th>
                            <th>Total</th>
                            <th>Placed At</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var order in (List<FoodDeliveryApp.ViewModels.AdminOrdersViewModel>)ViewBag.AcceptedOrders)
                        {
                            <tr>
                                <td>@order.OrderId</td>
                                <td>@order.CustomerName</td>
                                <td>
                                    @foreach (var item in order.Items)
                                    {
                                        <span class="d-block">@item</span>
                                    }
                                </td>
                                <td>@order.TotalPrice.ToString("C")</td>
                                <td>@order.OrderDateTime</td>
                            </tr>
                        }
                    </tbody>
                </table>
            }
        </div>

        <!-- REJECTED ORDERS TAB -->
        <div class="tab-pane fade" id="rejectedTab" role="tabpanel" aria-labelledby="rejected-tab">
            @if (ViewBag.RejectedOrders == null || !((List<FoodDeliveryApp.ViewModels.AdminOrdersViewModel>)ViewBag.RejectedOrders).Any())
            {
                <div class="alert alert-info mt-3">
                    No rejected orders today.
                </div>
            }
            else
            {
                <table class="table table-hover table-bordered align-middle">
                    <thead class="table-light">
                        <tr>
                            <th>Order #</th>
                            <th>Customer</th>
                            <th>Items</th>
                            <th>Total</th>
                            <th>Placed At</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var order in (List<FoodDeliveryApp.ViewModels.AdminOrdersViewModel>)ViewBag.RejectedOrders)
                        {
                            <tr>
                                <td>@order.OrderId</td>
                                <td>@order.CustomerName</td>
                                <td>
                                    @foreach (var item in order.Items)
                                    {
                                        <span class="d-block">@item</span>
                                    }
                                </td>
                                <td>@order.TotalPrice.ToString("C")</td>
                                <td>@order.OrderDateTime</td>
                            </tr>
                        }
                    </tbody>
                </table>
            }
        </div>
    </div>
</div>

@section Scripts {
    <!-- Optional: FontAwesome for icons (Accept/Reject buttons). If not needed, remove <i> tags above. -->
    <script src="https://kit.fontawesome.com/your-fontawesome-kit.js" crossorigin="anonymous"></script>
}
Home Controller

Please modify the HomeController.cs as follows. It shows a landing page that links to Admin or Customer logins. It is used as the initial entry point for public users.

using Microsoft.AspNetCore.Mvc;
namespace FoodDeliveryApp.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}
Modifying Home Index View

Modify the Index.cshtml view which is inside the Views/Home folder as follows. This is the public landing page of the application.

@{
    ViewData["Title"] = "Home Page";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<!-- Hero Section with Reduced Height -->
<div class="hero-section" style="background: url('https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?ixlib=rb-1.2.1&auto=format&fit=crop&w=1600&q=80') no-repeat center center; background-size: cover; min-height: 300px; position: relative;">
    <div class="overlay" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6);"></div>
    <div class="container text-center" style="position: relative; z-index: 2; padding-top: 80px;">
        <h1 class="display-4 text-white">Fast. Fresh. Delivered.</h1>
        <p class="lead text-white">Experience the best in food delivery. Order from top-rated restaurants and enjoy your favorite meals at lightning speed.</p>
        <a href="@Url.Action("PlaceOrder", "Customer")" class="btn btn-lg btn-primary mt-3">Order Now</a>
    </div>
</div>

<!-- Features Section -->
<div class="container my-3">
    <div class="row text-center">
        <div class="col-md-4 mb-4">
            <i class="fas fa-motorcycle fa-3x text-primary mb-3"></i>
            <h4>Lightning Fast Delivery</h4>
            <p>Your food arrives hot and fresh, delivered to your doorstep in minutes.</p>
        </div>
        <div class="col-md-4 mb-4">
            <i class="fas fa-utensils fa-3x text-primary mb-3"></i>
            <h4>Diverse Cuisines</h4>
            <p>Explore a wide variety of dishes from local favorites to international delights.</p>
        </div>
        <div class="col-md-4 mb-4">
            <i class="fas fa-star fa-3x text-primary mb-3"></i>
            <h4>Top-Rated Restaurants</h4>
            <p>We partner with the best restaurants to ensure quality meals every time.</p>
        </div>
    </div>
</div>

<!-- Call to Action Section -->
<div class="container text-center my-3">
    <h2>Ready to Savor Your Favorite Meal?</h2>
    <p>Join thousands of happy customers and get started with our quick and reliable service.</p>
    <a href="@Url.Action("Register", "CustomerAccount")" class="btn btn-lg btn-success">Sign Up Now</a>
</div>
Modifying the _Layout Page:

Please modify the _Layout.cshtml view as follows. The _Layout.cshtml acts as the main shared layout for public pages like the Home page. It provides a navigation bar that links to both the Admin and Customer login pages and includes references to CSS and JavaScript files.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - FoodDeliveryApp</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/FoodDeliveryApp.styles.css" asp-append-version="true" />
</head>
<body>
    <!-- Navigation Bar -->
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="#">FoodDeliveryApp</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse"
                data-target="#mainNavbar" aria-controls="mainNavbar"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="mainNavbar">
            <ul class="navbar-nav ml-auto">
                <!-- Redirects to Admin Login -->
                <li class="nav-item">
                    <a class="nav-link"
                       asp-controller="AdminAccount"
                       asp-action="Login">Admin</a>
                </li>

                <!-- Redirects to Customer Login -->
                <li class="nav-item">
                    <a class="nav-link"
                       asp-controller="CustomerAccount"
                       asp-action="Login">Customer</a>
                </li>
            </ul>
        </div>
    </nav>

    <!-- Page Body -->
    <div class="container mt-4">
        @RenderBody()
    </div>

    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Modifying Validation Scripts File

To ensure client-side validation works correctly, make sure you include the _ValidationScriptsPartial.cshtml in your Views/Shared folder. Please modify the _ValidationScriptsPartial view as follows.

<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>

That’s it. We have completed our Food Delivery App development using ASP.NET Core MVC, Entity Framework Core Code First Approach with SQL Server database. Now, run the application and test the functionalities, and it should work as expected.

In the next article, I will discuss developing a Real-time Employee Portal Application using ASP.NET Core MVC. I hope you enjoy this in-depth article on Real-time Food Delivery App Development using ASP.NET Core MVC. Please give your valuable feedback about this Food Delivery App using the ASP.NET Core MVC article and tell me how we can improve this project.

Leave a Reply

Your email address will not be published. Required fields are marked *