Part 18: Step-by-Step Order Tracking Timeline
Step 1: Implementing the TrackOrder Search Controller Action
This action method acts as a dual-purpose controller: it securely serves the initial empty search tracking page, and dynamically processes order number lookups from a single endpoint.
public async Task<IActionResult> TrackOrder(string? orderNumber)
{
if (string.IsNullOrWhiteSpace(orderNumber))
{
return View();
}
var order = await _orderService.GetOrderByNumberAsync(orderNumber);
if (order == null)
{
TempData["Error"] = "Order not found.";
return View();
}
return View(order);
}
Core Method Mechanics & Architectural Breakdown
The Safe Initial Landing Guard (string.IsNullOrWhiteSpace):
This is a clever and efficient design pattern. When a user first clicks on the "Track Order" link in your website's header or footer, the orderNumber query string parameter will be completely blank or null. Instead of throwing an error or executing a useless database query, the method catches this state instantly and serves the clean, empty search form view layout.
String Key Identity Verification (GetOrderByNumberAsync):
Unlike your prior account dashboard action which queried records using internal integer database IDs (int id), this method utilizes the public string identifier (string orderNumber). This is an industry-standard practice for public-facing utilities. It prevents malicious users from easily guessing sequential database primary keys ("Id enumeration attacks") to spy on other shoppers' delivery records.
Non-Crashing Error Handling Bounds:
If a customer accidentally types a wrong tracking string code or introduces typos, the application catches the null result from the database service. Instead of redirecting to an entirely different screen or crashing with an unhandled server error, it drops a user-friendly diagnostic alert via TempData and returns them directly back to the tracking interface to try again.
Dynamic Fluid Data Bound Return:
If the tracking query verification matches cleanly, the system directly binds the loaded, eager-loaded order entity object data straight down to the view layer, paving the way to compile your multi-step visual milestone timeline interface.
Step 2: Explaining the "Track Order" Dashboard UI
This interface serves as a public-facing page, combining interactive forms, relational loops, and conditional feedback states using Bootstrap 5 cards and alerts.
@model Order
@{
ViewData["Title"] = "Track Order";
}
<div class="container mt-5 mb-5">
<div class="row justify-content-center">
<div class="col-md-8">
<h2 class="mb-4">Track Your Order</h2>
@if (TempData["Error"] != null)
{
<div class="alert alert-danger alert-dismissible fade show" role="alert">
@TempData["Error"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
<div class="card shadow-sm mb-4">
<div class="card-body">
<form asp-action="TrackOrder" method="get">
<div class="input-group mb-3">
<input type="text" name="orderNumber" class="form-control form-control-lg"
placeholder="Enter Order Number (e.g., ORD-20260531-ABC12345)"
value="@Context.Request.Query["orderNumber"]" required />
<button class="btn btn-primary btn-lg" type="submit">Track Order</button>
</div>
<div class="form-text text-muted">
Enter your order number to check the status of your order.
</div>
</form>
</div>
</div>
@if (Model != null)
{
<div class="card shadow">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">Order #@Model.OrderNumber</h5>
<span class="badge bg-light text-dark fs-6">@Model.Status</span>
</div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-6">
<h6 class="text-muted">Order Information</h6>
<p class="mb-1"><strong>Order Date:</strong> @Model.OrderDate.ToString("MMM dd, yyyy HH:mm")</p>
<p class="mb-1"><strong>Payment Method:</strong> @Model.PaymentMethod</p>
<p class="mb-1">
<strong>Payment Status:</strong>
<span class="badge @(Model.PaymentStatus == PaymentStatus.Paid ? "bg-success" : "bg-warning")">
@Model.PaymentStatus
</span>
</p>
</div>
<div class="col-md-6">
<h6 class="text-muted">Shipping Information</h6>
<p class="mb-1">@Model.ShippingAddress</p>
<p class="mb-1">@Model.ShippingCity, @Model.ShippingPostalCode</p>
<p class="mb-1">@Model.ShippingCountry</p>
@if (!string.IsNullOrEmpty(Model.ShippingPhone))
{
<p class="mb-1"><strong>Phone:</strong> @Model.ShippingPhone</p>
}
</div>
</div>
<h6 class="text-muted mb-3">Order Items</h6>
<div class="table-responsive">
<table class="table table-bordered">
<thead class="table-light">
<tr>
<th>Product</th>
<th class="text-center">Qty</th>
<th class="text-end">Unit Price</th>
<th class="text-end">Total</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.OrderItems)
{
<tr>
<td>
@if (item.Product != null)
{
<div class="d-flex align-items-center">
@if (!string.IsNullOrEmpty(item.Product.MainImageUrl))
{
<img src="@item.Product.MainImageUrl" alt="@item.Product.Name"
style="width: 50px; height: 50px; object-fit: cover;" class="me-2 rounded" />
}
<div>
<div class="fw-bold">@item.Product.Name</div>
<small class="text-muted">@item.Product.Model</small>
</div>
</div>
}
else
{
<span>Product #@item.ProductId</span>
}
</td>
<td class="text-center">@item.Quantity</td>
<td class="text-end">RS @item.UnitPrice.ToString("N2")</td>
<td class="text-end fw-bold">RS @item.TotalPrice.ToString("N2")</td>
</tr>
}
</tbody>
<tfoot class="table-group-divider">
<tr>
<td colspan="3" class="text-end"><strong>Subtotal:</strong></td>
<td class="text-end">RS @Model.Subtotal.ToString("N2")</td>
</tr>
<tr>
<td colspan="3" class="text-end"><strong>Tax:</strong></td>
<td class="text-end">RS @Model.TaxAmount.ToString("N2")</td>
</tr>
<tr>
<td colspan="3" class="text-end"><strong>Shipping:</strong></td>
<td class="text-end">RS @Model.ShippingCost.ToString("N2")</td>
</tr>
@if (Model.DiscountAmount > 0)
{
<tr>
<td colspan="3" class="text-end text-success"><strong>Discount:</strong></td>
<td class="text-end text-success">-RS @Model.DiscountAmount.ToString("N2")</td>
</tr>
}
<tr class="table-primary">
<td colspan="3" class="text-end"><strong>Total Amount:</strong></td>
<td class="text-end"><strong class="fs-5">RS @Model.TotalAmount.ToString("N2")</strong></td>
</tr>
</tfoot>
</table>
</div>
@if (!string.IsNullOrEmpty(Model.Notes))
{
<div class="alert alert-info mt-3">
<strong>Order Notes:</strong> @Model.Notes
</div>
}
@if (Model.ShippedDate.HasValue)
{
<div class="alert alert-success mt-3">
<i class="fas fa-truck me-2"></i>
<strong>Shipped on:</strong> @Model.ShippedDate.Value.ToString("MMM dd, yyyy")
@if (Model.DeliveredDate.HasValue)
{
<br />
<strong>Delivered on:</strong> @Model.DeliveredDate.Value.ToString("MMM dd, yyyy")
}
</div>
}
</div>
</div>
}
</div>
</div>
</div>
Core UI Mechanics & Razor Design Breakdown
Preserving Form State via Global Request Context:
This is an exceptional user experience (UX) touch. When the user hits search, the page reloads to show their results. By binding the input's default value straight to the active HTTP GET query collection, the long order number string remains typed inside the input box. The customer doesn't have to keep copy-pasting it if they want to refresh the page.
Smart Model Nullability Splitting (@if (Model != null)):
This single conditional check controls the view lifecycle. If a user visits the tracking page for the first time, the model is completely blank, displaying only the search card. As soon as a verified order object passes through from your Step 1 controller method, this block dynamically shifts into place, unpacking the layout details below it.
Relational Direct Image & Data Fallbacks:
This block handles structural data protection. If a phone model is completely purged from your catalog by a database administrator, the system automatically detects the empty reference link and falls back to listing the raw product code number—preventing an application crash while maintaining readable historical tracking records.
Dynamic Promotional Discount Alerts:
Instead of displaying empty, confusing zero-balance tracking lines to regular customers, the table footer adapts dynamically. The discount summary row only prints if a coupon or special pricing logic was actually applied to the transaction.
Comments
Post a Comment