Food Ordering System Part 29 | Displaying Order Details with Eager Loading in ASP.NET MVC Core

 

Learn how to implement a detailed Order Details view in ASP.NET MVC Core. In Part 29, we explore using Entity Framework Core’s Include and ThenInclude methods to fetch nested data, ensuring your Admin Panel displays full customer and food item information efficiently.


Detailed Code Explanation

The OrderDetails action is designed to provide a deep dive into a specific order using its unique id. Here is how the logic breaks down:

  1. Security Barrier: Before any data is fetched, IsAdmin() verifies the user's role. This prevents unauthorized users from accessing sensitive customer purchase histories.

  2. Complex Data Fetching (Eager Loading): * _context.Orders starts the query.

    • .Include(o => o.User) joins the User table to show who placed the order.

    • .Include(o => o.OrderItems) fetches the list of items belonging to that specific order.

    • .ThenInclude(oi => oi.FoodItem) goes a step deeper to fetch the details (like Name and Price) of each specific food item within those order items.

  3. The FirstOrDefault Filter: This ensures we only retrieve the single order that matches the ID passed through the URL.

  4. Null Handling: If a user manually enters an ID in the URL that doesn't exist, the system gracefully returns a NotFound() (404) page instead of crashing.

  5. View Passing: Finally, the fully populated order object is passed to the Razor View to be rendered into a professional invoice-style layout.


public IActionResult OrderDetails(int id)
{
    if (!IsAdmin()) return RedirectToAction("Index", "Home");

    var order = _context.Orders
        .Include(o => o.User)
        .Include(o => o.OrderItems)
        .ThenInclude(oi => oi.FoodItem)
        .FirstOrDefault(o => o.Id == id);

    if (order == null) return NotFound();

    return View(order);
}

Detailed Code Explanation

This Razor view (OrderDetails.cshtml) is responsible for transforming our Order model into a readable dashboard. Here is the breakdown:

  1. Model Declaration: @model Order tells the view to expect a single Order object, which includes the list of OrderItems and User data we included in our controller.

  2. Itemized Table: We use a @foreach loop to iterate through Model.OrderItems. Inside, we calculate the total for each row by multiplying Quantity * UnitPrice and format it as a currency string ("F2").

  3. Customer & Delivery Cards: By using Bootstrap’s col-md-4 and col-md-8, we create a split-screen layout. The right side houses "quick look" cards for Customer Contact Info and the physical Delivery Address.

  4. Dynamic Status Coloring: We use a C# switch expression directly inside the Razor view to determine the Bootstrap color class (warning, success, danger, etc.) based on the current Model.Status.

  5. Navigation: A "Back to Orders" button is included to ensure smooth UX, allowing the admin to quickly return to the main order management list.


@model Order
@{
    ViewData["Title"] = $"Order #{Model.Id} Details";
}

<div class="container my-4">
    <div class="d-flex justify-content-between align-items-center mb-4">
        <h2><i class="bi bi-receipt"></i> Order #@Model.Id Details</h2>
        <a href="/Admin/Orders" class="btn btn-outline-secondary"><i class="bi bi-arrow-left"></i> Back to Orders</a>
    </div>

    <div class="row">
        <div class="col-md-8">
            <div class="card shadow mb-4">
                <div class="card-header bg-primary text-white">
                    <h5 class="mb-0">Order Items</h5>
                </div>
                <div class="card-body">
                    <table class="table">
                        <thead>
                            <tr>
                                <th>Item</th>
                                <th>Quantity</th>
                                <th>Unit Price</th>
                                <th>Total</th>
                            </tr>
                        </thead>
                        <tbody>
                            @foreach (var item in Model.OrderItems)
                            {
                                <tr>
                                    <td>@item.FoodItem?.Name</td>
                                    <td>@item.Quantity</td>
                                    <td>$@item.UnitPrice.ToString("F2")</td>
                                    <td>$@((item.Quantity * item.UnitPrice).ToString("F2"))</td>
                                </tr>
                            }
                        </tbody>
                        <tfoot>
                            <tr class="table-primary">
                                <td colspan="3" class="text-end"><strong>Total Amount:</strong></td>
                                <td><strong>$@Model.TotalAmount.ToString("F2")</strong></td>
                            </tr>
                        </tfoot>
                    </table>
                </div>
            </div>

            <div class="card shadow">
                <div class="card-header bg-info text-white">
                    <h5 class="mb-0">Update Status</h5>
                </div>
                <div class="card-body">
                    <div class="btn-group w-100" role="group">
                        <!-- Status Update Buttons go here -->
                    </div>
                </div>
            </div>
        </div>

        <div class="col-md-4">
            <div class="card shadow mb-4">
                <div class="card-header bg-secondary text-white">
                    <h5 class="mb-0">Customer Info</h5>
                </div>
                <div class="card-body">
                    <p><strong>Name:</strong> @Model.User?.FullName</p>
                    <p><strong>Email:</strong> @Model.User?.Email</p>
                    <p><strong>Phone:</strong> @Model.PhoneNumber</p>
                </div>
            </div>

            <div class="card shadow mb-4">
                <div class="card-header bg-secondary text-white">
                    <h5 class="mb-0">Delivery Address</h5>
                </div>
                <div class="card-body">
                    <p>@Model.DeliveryAddress</p>
                </div>
            </div>

            <div class="card shadow">
                <div class="card-header bg-dark text-white">
                    <h5 class="mb-0">Order Status</h5>
                </div>
                <div class="card-body text-center">
                    @{
                        var statusColor = Model.Status switch
                        {
                            "Pending" => "warning",
                            "Confirmed" => "info",
                            "Preparing" => "primary",
                            "OutForDelivery" => "secondary",
                            "Delivered" => "success",
                            "Cancelled" => "danger",
                            _ => "light"
                        };
                    }
                    <h3 class="text-@statusColor">@Model.Status</h3>
                    <p class="text-muted mb-0">Order Date: @Model.OrderDate.ToString("MMM dd, yyyy HH:mm")</p>
                </div>
            </div>
        </div>
    </div>
</div>

Detailed Code Explanation

This specific HTML snippet is placed inside the @foreach loop of your Orders.cshtml (the list view). Here is how it works:

  1. Dynamic Routing: The href="/Admin/OrderDetails/@order.Id" uses Razor syntax to inject the specific ID of the current order into the URL. For example, if you click the button for the 5th order, the browser will navigate to /Admin/OrderDetails/5.

  2. Bootstrap Styling: * btn-sm: Makes the button small and compact, perfect for fitting inside a table row.

    • btn-outline-info: Gives the button a professional "info" blue border that fills with color when hovered over.

  3. Bootstrap Icons (bi-eye): Instead of using text like "View," we use a "eye" icon to keep the UI clean and intuitive for administrators.

  4. Accessibility (title): The title="View Details" attribute ensures that when a user hovers over the icon, a small tooltip appears, and screen readers can identify the button's purpose.


Comments