Introduction
Building an e-commerce application involves more than displaying products on a web page. The application needs to handle product catalogs, customer accounts, orders, payments, inventory, and potentially thousands of concurrent requests.
The challenge becomes more significant during high-traffic events such as Black Friday or seasonal sales. The application needs to remain responsive while maintaining security, reliability, and scalability.
.NET provides a strong foundation for this type of application. ASP.NET Core can be used to build the backend APIs, while technologies such as Entity Framework Core, Redis, SignalR, Docker, and Kubernetes can be introduced as the application's requirements grow.
In this article, we will use an e-commerce application as a real-world example to understand how these technologies can work together.
E-Commerce Application Architecture
Consider an online store where customers can:
Browse products
Search for products
Register and log in
Add products to a shopping cart
Place orders
Track order status
Receive real-time notifications
A simplified architecture could look like this:
Customer
|
Angular / React
|
HTTPS
|
ASP.NET Core Web API
|
+---------------+----------------+
| | |
Products Orders Identity
| | |
+---------------+----------------+
|
Application Layer
|
+---------------+----------------+
| | |
EF Core Redis SignalR
| | |
SQL Server Cache Layer Real-Time Events
For larger deployments, the API can run inside containers and be scaled horizontally using a container orchestration platform.
Step 1: Create an ASP.NET Core Web API
The backend can start as a standard ASP.NET Core Web API project.
Using the .NET CLI:
dotnet new webapi -n EcommerceApi
cd EcommerceApi
dotnet run
The API can expose endpoints for products, customers, carts, and orders.
For example:
GET /api/products
GET /api/products/{id}
POST /api/orders
GET /api/orders/{id}
The frontend application can communicate with these endpoints using HTTP.
Step 2: Create a Product Model
An e-commerce application needs a model to represent products.
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int StockQuantity { get; set; }
public string Category { get; set; } = string.Empty;
}
This model contains basic product information such as the product name, price, category, and available stock.
Step 3: Configure Entity Framework Core
Entity Framework Core can be used to communicate with a relational database.
For example, a DbContext can be created as follows:
using Microsoft.EntityFrameworkCore;
public class EcommerceDbContext : DbContext
{
public EcommerceDbContext(
DbContextOptions<EcommerceDbContext> options)
: base(options)
{
}
public DbSet<Product> Products => Set<Product>();
}
Register the context in Program.cs:
builder.Services.AddDbContext<EcommerceDbContext>(
options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString(
"EcommerceDatabase")));
A connection string can be stored in configuration:
{
"ConnectionStrings": {
"EcommerceDatabase": "Server=localhost;Database=EcommerceDb;Trusted_Connection=True;TrustServerCertificate=True"
}
}
In a production environment, connection strings and other secrets should be managed through an appropriate secret-management solution rather than committed to source control.
Step 4: Create a Product API
The API can expose product information to the frontend.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly EcommerceDbContext _context;
public ProductsController(EcommerceDbContext context)
{
_context = context;
}
[HttpGet]
public async Task<ActionResult<List<Product>>> GetProducts()
{
var products = await _context.Products
.AsNoTracking()
.ToListAsync();
return Ok(products);
}
[HttpGet("{id:int}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
var product = await _context.Products
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == id);
if (product == null)
return NotFound();
return Ok(product);
}
}
AsNoTracking() is useful for read-only queries because the returned entities do not need to be tracked for updates.
A request such as:
GET /api/products
could return:
[
{
"id": 1,
"name": "Laptop",
"price": 65000,
"stockQuantity": 25,
"category": "Electronics"
},
{
"id": 2,
"name": "Wireless Mouse",
"price": 1200,
"stockQuantity": 100,
"category": "Accessories"
}
]
Step 5: Handle Orders
Orders are another important part of an e-commerce application.
A simplified order model might look like this:
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public decimal TotalAmount { get; set; }
public string Status { get; set; } = "Pending";
public DateTime CreatedAt { get; set; }
}
A basic order endpoint could be:
[HttpPost]
public async Task<IActionResult> CreateOrder(Order order)
{
order.CreatedAt = DateTime.UtcNow;
order.Status = "Pending";
_context.Orders.Add(order);
await _context.SaveChangesAsync();
return CreatedAtAction(
nameof(GetOrder),
new { id = order.Id },
order);
}
In a production system, order creation should involve additional validation, inventory checks, transaction handling, payment processing, and business rules. The simplified example demonstrates only the API flow.
Step 6: Improve Performance with Redis
During a large sale, popular products may be requested thousands of times.
Without caching, every request could result in another database query:
Customer
↓
API
↓
Database
A cache can reduce unnecessary database access:
Customer
↓
API
↓
Redis Cache
↓
Database only when required
A simplified caching service could look like this:
public interface IProductCache
{
Task<Product?> GetAsync(int productId);
Task SetAsync(Product product);
}
The application can first check Redis for the requested product. If the product is not available in the cache, it can retrieve the data from the database and then cache it.
Conceptually:
var product = await cache.GetAsync(productId);
if (product == null)
{
product = await database.GetProductAsync(productId);
if (product != null)
{
await cache.SetAsync(product);
}
}
The exact implementation depends on the Redis client and caching approach used by the application.
Caching should be designed carefully. Frequently changing information such as inventory requires an appropriate expiration and invalidation strategy to avoid serving stale data.
Step 7: Add Authentication and Authorization
Customer accounts require authentication and authorization.
ASP.NET Core provides authentication and authorization infrastructure that can be integrated with identity systems.
For example, an API endpoint can require an authenticated user:
[Authorize]
[HttpGet("my-orders")]
public async Task<IActionResult> GetMyOrders()
{
// Retrieve orders for the authenticated customer.
return Ok();
}
Role-based authorization can also be applied:
[Authorize(Roles = "Admin")]
[HttpDelete("{id:int}")]
public async Task<IActionResult> DeleteProduct(int id)
{
// Delete or deactivate the product.
return NoContent();
}
For token-based APIs, JWT bearer authentication can be configured when appropriate.
Authentication alone is not enough for an e-commerce application. Authorization, input validation, secure secret storage, HTTPS, logging, dependency updates, and protection of sensitive business operations are also important.

Join the conversation! Your thoughts help the community grow.