In Part 8 of our ASP.NET Core MVC Mobile Shop series, we are building a fully functional Shopping Cart System. You will learn how to configure and use Session State in .NET, create custom JSON session extension methods, and handle cart updates asynchronously using AJAX. This step-by-step tutorial ensures a seamless, modern user experience (UX) without frustrating page reloads! Perfect for .NET 8 / .NET 9 web development.
Step 1: Designing the Shopping Cart ViewModels
When building a shopping cart, we need to track two layers of information:
The Cart Summary: The overall costs like tax, shipping, and the grand total.
The Line Items: The individual rows of items inside the cart (e.g., 2x Samsung Galaxy, 1x iPhone).
The Code: ViewModels/ShoppingCartViewModel.cs
Detailed Step-by-Step Explanation
1. Breaking Down CartItemViewModel (The Individual Items)
This class represents a single item row inside the user's cart.
ProductId & CartItemId: Crucial for tracking backend actions. When a user clicks "Remove" or changes quantities, we use these IDs to pinpoint exactly which product to modify.
ProductName & ProductImage: We store these strings directly in the viewmodel so the UI can display the phone's name and thumbnail quickly without forcing extra database queries during rendering.
Expression-Bodied Property (
TotalPrice => UnitPrice * Quantity): This is a brilliant C# feature. Instead of manually computing and saving the total row price, this property uses a read-only lambda expression. Every time the item quantity changes, the row total auto-calculates itself dynamically.StockQuantity: Essential for client-side safety. It allows us to prevent a user from increasing their cart quantity beyond what is physically available in your warehouse.
2. Breaking Down ShoppingCartViewModel (The Parent Container)
This class manages the collective data of the entire shopping state.
List<CartItemViewModel>: This instantiates a collection to bundle all items together. It binds the items directly to the summary wrapper.
Data Annotations (
[Display(Name = "...")]): This tells ASP.NET Core exactly how to render labels cleanly when using HTML Tag Helpers (<label asp-for="GrandTotal"></label>).The Calculated Grand Total: Just like the item rows,
GrandTotal => CartTotal + Tax + Shipping;is an automated expression property. It guarantees that the checkout total is mathematically precise at all times, removing the risk of synchronization bugs.
Step 2: Creating the ShoppingCart Service Layer
An interface ensures our code stays decoupled, making it incredibly easy to inject this service into our MVC Controllers or API endpoints later.
The Code: Services/IShoppingCartService.cs
1. The Session Strategy (GetCartId)
How it works: This helper method is the core anchor of our tracking logic. It looks into the current user's HTTP Session for a key named
"CartId".The Magic: If it's a guest user who just arrived, it uses a Globally Unique Identifier
Guid.NewGuid().ToString()to instantly generate a distinct shopping key. This key is stored in the user's browser session, identifying their cart items without forcing them to register an account first.
2. Using IHttpContextAccessor for Session Access
Because services live outside the normal scope of a standard controller, they cannot access HttpContext by default. By injecting IHttpContextAccessor, we can safely read and write session cookies directly from within our database service layer.
3. Intelligent Increment Logic (AddToCartAsync)
Instead of blindly appending rows to your ShoppingCartItems table, this action executes a smart check:
If the user adds an iPhone, and then clicks "Add to Cart" again for the same phone, the code runs an optimization step: it simply increases the
Quantitycounter inside the database row (cartItem.Quantity += quantity) rather than generating a duplicated line item.
4. Dynamic Eager Loading (GetCartAsync)
We use the
.Include(c => c.Product)LINQ method to fetch product imagery, names, and stock data simultaneously, avoiding dangerous N+1 query performance problems.Business Logic Realism: Your tax logic applies an 18% calculation dynamically (
* 0.18m). Additionally, your shipping counter handles pricing structures automatically: if a user's purchase total crosses RS 50,000, it dynamically drops the shipping fee to 0, providing an instant incentive for customers to check out!
5. Safe Item Updates & Housekeeping
UpdateCartItemAsync: If a user modifies their quantity to0or fewer items in their checkout field, the service automatically flags it as an item removal rather than letting an invalid negative integer save into the database.ClearCartAsync: Uses.RemoveRange(cartItems)to cleanly purge all items tied to that uniqueCartIdin one quick transaction block once a customer completes an order.
Step 3: Registering the Shopping Cart Service in Program.cs
To make our shopping cart operational across the entire web application, we must add it to the built-in IoC (Inversion of Control) container. We also need to ensure that Session State and HTTP context utilities are enabled.
The Code: Program.cs Configuration
Open your Program.cs file and add the service registration alongside your other custom services:
Detailed Technical Breakdown
1. Why do we use AddScoped?
We register IShoppingCartService with a Scoped Lifetime (AddScoped).
The Lifetime Logic: This means a single instance of the service is created per HTTP request. When a user clicks "Add to Cart", a request opens, the service talks to the database, updates the cart, and gracefully disposes of itself when the page finishes loading. This prevents database connection leaks and memory overhead.
2. The Role of AddHttpContextAccessor()
As discussed in Step 2, our service requires access to the user's browser session via IHttpContextAccessor. By default, ASP.NET Core does not register this utility. Adding this line allows the framework to securely pass the active network request context into our constructor.
3. Configuring Session Options (Security & UX)
To make sessions work, we configure three essential security and performance boundaries:
IdleTimeout: Keeps the shopper's data alive in memory for 30 minutes. If they leave their tab open and come back within 30 minutes, their phones are still in the cart.HttpOnly = true: A major security best practice for security engineers. It blocks client-side JavaScript from accessing the session cookie, safeguarding the cart against Cross-Site Scripting (XSS) token hijacking.IsEssential = true: Tells the hosting framework that this cookie is structurally required for the site to function, allowing the cart to remain active even if visitors reject marketing tracking banners.
4. Ordering the Middleware Layer (app.UseSession())
In ASP.NET Core, the order of code inside the HTTP pipeline matters completely. You must place app.UseSession() after routing (app.UseRouting()) but before authorization parameters (app.UseAuthorization()). If it is misplaced, the session data will not populate before your controller actions trigger!
Detailed Step-by-Step Explanation
1. Dependency Injection Constructor
Instead of instantiating the service using new, we pass IShoppingCartService straight into the constructor. Because we registered it as AddScoped back in Step 3, ASP.NET Core dynamically hands over the correct context instance for every unique web request.
2. Index Action (The Cart View)
The Index action loads the checkout summary. It pulls the overall cart model using GetCartAsync(), saves the total product count into a temporary ViewBag.CartItemCount (useful for keeping navbar layouts updated), and forwards the model to the frontend view.
3. The Crucial AJAX Check (XMLHttpRequest)
This is the most critical logic block to explain to your students:
if (Request.Headers.XRequestedWith == "XMLHttpRequest")
Traditional Web Forms: If a standard browser form sends a POST request, this condition evaluates to false. The controller processes the change and executes a
RedirectToAction(nameof(Index)), refreshing the entire viewport.Modern JavaScript (AJAX): If a jQuery or vanilla JS fetch operation triggers the click handler, the browser automatically injects an
X-Requested-Withheader set toXMLHttpRequest. The controller interceptor senses this header, shortcuts the page reload sequence, and returns a raw, fast JSON response wrapper (return Json(...)).
4. Real-time Total Synchronization (UpdateQuantity / RemoveItem)
When a buyer updates quantities using front-end counters or clicks the trash bin icon, the AJAX route sends back updated data parameters (cartTotal, grandTotal, tax, shipping). This gives your upcoming JavaScript handlers the exact numbers they need to re-render the pricing sidebar on the fly without blinking the screen layout!
5. UX Feedback Layer (TempData)
For non-AJAX fallback scenarios, the controller sets strings like TempData["Success"] = "Product added to cart!";. This values persist across redirects, letting you display beautiful temporary Toast notifications or Bootstrap alert boxes to the customer once the page redraw completes.
Step 5: Implementing the Shopping Cart User Interface
Create a new file named Index.cshtml under the Views/ShoppingCart/ folder and insert the Razor engine code structure.
The Layout View: Views/ShoppingCart/Index.cshtml
Detailed Step-by-Step UI Breakdown
1. Explicit Strong Typing & Namespaces
@using MobileShop.ViewModels
@model ShoppingCartViewModel
By explicitly declaring our container model at the top of the file, IntelliSense becomes fully active. When writing frontend fields, typing @Model. will instantly populate autocomplete parameters for collections, keeping development fast and error-free.
2. Conditional Empty-State Handler
@if (Model.CartItems.Count == 0) { ... } else { ... }
An exceptional user experience requires graceful fallbacks. If a customer visits /ShoppingCart without picking a phone, the structural conditional completely hides the billing grids. Instead, it serves an elegant empty-state notice utilizing the bi-cart-x display asset alongside an intuitive "Continue Shopping" layout redirection route.
3. Split-Grid Architecture (Two-Column Flow)
We split the layout into a modern asymmetrical responsive design format:
.col-lg-8(Left Content Side): Renders loops over the active cart line items using an optimized@foreachstatement. Each smartphone item generates its corresponding thumbnail photo, absolute flat prices (UnitPrice), explicit quantity inputs, and custom trash bins..col-lg-4(Right Content Side): Remains pinned as a neat static widget layout containing the dynamic breakdown for taxes (18% GST), conditional shipping charges, a visual voucher interface field, and a massive primary execution gateway button targeting your upcomingCheckoutController.
4. Currency Formatting Strings ("N0")
Using @item.UnitPrice.ToString("N0") formats numeric digits beautifully into local standards with comma thousand-separators (e.g., displaying raw decimals as RS 45,000 instead of standard flat text 45000).
Asynchronous JavaScript Operations (The Scripts Section)
The @section Scripts module handles the client-side user operations smoothly:
$.post('@Url.Action("UpdateQuantity", "ShoppingCart")', { cartItemId: cartItemId, quantity: newQty }, function(data) {
if (data.success) {
location.reload();
}
});
@Url.Action(...)Parsing: Razor automatically compiles this helper target statement directly into clean URL destination paths (/ShoppingCart/UpdateQuantity) during rendering.Safety Counter Boundaries: Before your script triggers a server post request, it evaluates bounds checked by local properties (
if (newQty < 1 || newQty > 10) return;). This prevents a web browser from submitting a zero or negative transaction packet, ensuring client-side input parameters are strict.location.reload()Strategy: When your server validates the AJAX operation successfully and returns a true confirmation flag, this script invokes a clean, swift document refresh. This ensures all quantities, line items, and the pricing summary column sync identically with the database changes instantly.
Step 6: Adding the Cart Badge to the Global Layout
Open your shared layout file located at Views/Shared/_Layout.cshtml and place this code within your navigation menu (<ul class="navbar-nav">):
The Code: Views/Shared/_Layout.cshtml
Detailed UI & Functional Breakdown
1. The Layout Architecture (Absolute Positioning)
To make a notification bubble sit perfectly on the top-right corner of an icon, you need absolute positioning. Bootstrap handles this cleanly without custom CSS:
position-relative: Added to the parent link (<a>). This acts as an anchor boundary. It tells the browser, "Any absolute-positioned item inside this link must calculate its position relative to this icon, not the entire webpage."position-absolute top-0 start-100 translate-middle: This moves the badge precisely to the top-right corner of the cart icon.top-0pushes it to the top edge,start-100shifts it all the way to the right edge, andtranslate-middlecenters the badge directly over that top-right intersection point.
2. Razor Null-Coalescing Operator (??)
@(ViewBag.CartItemCount ?? 0)
The Logic: When a user first lands on the home page, the
HomeControllerhasn't explicitly set aViewBag.CartItemCountvalue. If we try to render a null value, the page could break or look completely empty.The Fix: The C# null-coalescing operator (
??) acts as a smart safety fallback. It checks ifViewBag.CartItemCountis null. If it is null, it instantly renders a0. If it contains a number (like 3 items), it displays that number instead.
3. Semantic Targeting (id="cartCount")
The badge includes id="cartCount". This is highly strategic for a technical content creator. Even though Part 8 handles page updates via a standard refresh, this ID gives you the exact hook you need for future AJAX scripts. In upcoming tutorials, your JavaScript can target $('#cartCount').text(data.itemCount) to smoothly change the count dynamically without forcing a full page reload!
Step 7: Implementing Add to Cart on the Product Details View
This code creates an inline, compact form containing a dynamic quantity spinner and an intelligent "Add to Cart" submission action block.
The Code: Views/Products/Details.cshtml Integration
Detailed Step-by-Step Functional Breakdown
1. The Hidden Parameter Pattern (productId)
<input type="hidden" name="productId" value="@Model.Product.Id" />
How it works: The shopper doesn't need to see the raw database primary key ID of the phone they are buying. Using
type="hidden"ensures the browser safely bundles theproductIdbehind the scenes.Model Binding Magic: The
name="productId"attribute matches the input parameter signature of yourAddToCart(int productId, int quantity)controller action from Step 4 perfectly, allowing ASP.NET Core to automatically map the value on form submission.
2. Pure JavaScript Quantity Spinner Controls
Instead of relying on heavy third-party UI plugins, this code leverages native web APIs inside utility button clicks:
this.parentNode.querySelector('input').stepDown()this.parentNode.querySelector('input').stepUp()Why this is great: It looks at the parent container (
input-group), locates the sibling<input type="number" />, and safely triggers the HTML5 bounds incrementers. Because the input element definesmin="1"andmax="10", these JavaScript methods respect those boundaries, stopping users from typing or clicking their way to an invalid quantity of0or11.
3. Smart Inventory Validation & UX Safety
@(Model.Product.StockQuantity <= 0 ? "disabled" : "")
The Business Logic: This inline Razor evaluation acts as an instant preventative safeguard. If a mobile device out of stock (
StockQuantity <= 0), the server injects the native HTMLdisabledattribute directly into the submission button.The Result: The button turns gray, blocks click events entirely, and stops users from attempting to order backlogged inventory, keeping your customer UX highly realistic and reliable.
4. Flexbox Alignment Layout (gap-3, gap-2)
Using Bootstrap 5 utility classes like d-flex coupled with standard structural gutters (gap-2, gap-3) aligns the minus button, quantity text window, plus button, and submission trigger onto a clean, unified horizontal row without writing arbitrary CSS positioning code.
🌟 Part 8 Milestone Achieved!
Congratulations on wrapping up the entire operational pipeline for the Mobile Shop Shopping Cart System! You've successfully built:
Strong ViewModels for cart summaries.
A database-backed Hybrid Session Service layer.
A pipeline integration in
Program.cs.An AJAX-ready dual Controller endpoint.
A gorgeous responsive Bootstrap 5 Grid Checkout interface.
A global dynamic header layout widget badge.

Comments
Post a Comment