ShoppingCart.

An ASP.NET Core MVC e-commerce site — product catalog, session-based cart, and an admin area for product CRUD, gated by ASP.NET Identity.

2022
ShoppingCart

Overview

A small e-commerce site built on ASP.NET Core MVC. Public visitors browse a seeded product catalog by category, add items to a session-scoped cart, and adjust quantities; an admin area sitting behind ASP.NET Identity lets a signed-in admin create, edit, and delete products against a SQL Server database via Entity Framework Core. Originally deployed to Azure App Service against an Azure SQL backend.

Why I built it

This was a learning project — the goal was to walk the entire ASP.NET Core MVC happy path end-to-end rather than read about it. Routing, model binding, EF Core migrations, Identity scaffolding, Razor view composition, Areas, partial views, session state, deployment to Azure: each one is straightforward in isolation, but the friction lives in how they fit together. Building a CRUD app where every layer is touched was the fastest way to make that fit muscle-memory.

How it's put together

  1. Two route templates are registered in Program.cs. The Areas route ({area:exists}/{controller=Products}/{action=Index}/{id?}) routes /Admin/Products/... to the admin product CRUD controller; the default route handles the public site. Areas keep the admin views, layout, and notifications partial isolated from the storefront.

  2. ProductsController queries DataContext.Products through EF Core, eager-loading the Category navigation for the listing view. The DbContext is registered with UseSqlServer against a connection string read from configuration; on startup, SeedData.SeedDatabase populates categories and products if the tables are empty.

  3. CartController stores a List<CartItem> in HttpContext.Session via a small JSON serialization extension. Add, decrease, and remove actions mutate the list and write it back; the small-cart partial in the layout reads the same key on every render. No cart row ever touches SQL — the cart is per-session and disappears when it expires.

  4. Areas/Admin/Controllers/ProductsController covers create, edit, and delete. Identity is wired up in Program.cs with AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores so the user store, role store, and login flow all live in the same SQL Server database as the catalog.

Stack and shape

The deliberate choices are mostly framework defaults, which is the point of the exercise. SQL Server because it's the path of least resistance with EF Core on .NET; Azure App Service + Azure SQL because the deployment story is a few clicks from Visual Studio. Bootstrap and jQuery on the client because the MVC scaffolding ships with them and the project wasn't an excuse to relitigate frontend stacks. Razor views compose through _Layout.cshtml and a handful of partials — _NotificationPartial renders TempData["Success"] flashes after every cart mutation, the small-cart partial in the layout shows the current item count.

The awkward parts were the ones that always are. Session-stored carts need a JSON helper because ISession only stores byte arrays and strings. Identity password rules had to be relaxed in Program.cs so the demo seed user could sign in with a short password. The seeded product images live under wwwroot/media/products and the upload path on the admin form had to resolve to that same folder so newly created products picked up valid image URLs.

What I'd change

Picking this up four years on, the changes I'd make are about boundaries — the original was correct for the framework but loose about what should and shouldn't share a database.

  • Move the cart out of session, or commit to it. Session storage is fine for a demo but loses the cart on idle timeout (20 minutes here). Either persist carts per-user once they sign in, or be explicit in the UI about the impermanence.
  • Validate inputs on the server, not just via Bootstrap. The current admin form leans on client-side validation; the controller should re-check ModelState and return 400-shaped responses rather than relying on the view to gate bad data.
  • Add a real test layer. Even one xUnit test per controller that exercises the happy path through WebApplicationFactory would catch the class of bug where a route or DI registration silently breaks.
  • Treat the seeded admin credentials as a secret. They were hard-coded into the seed for demo convenience — fine for a learning project, not fine for anything else.

Repo

Source on GitHub. MIT.