HTTP is a stateless protocol. It has amnesia — a server does not remember you from request 1 to request 2. To build applications with shopping carts, multi-step wizards, and user preferences, we must manage "State". Understanding the technical difference between Cookies and Session State is crucial for application architecture and security.
Data is stored entirely on the user's browser as text strings. Sent back to the server with every HTTP request.
Data is stored on the server's memory or database. The server sends a single, tiny encrypted Cookie to the browser containing just a "SessionID".
Cookies do not require any middleware setup. You access them directly via the HttpContext.
public class PreferencesController : Controller
{
public IActionResult SetTheme()
{
// 1. Create secure options
var options = new CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddYears(1), // Persistent cookie
HttpOnly = false, // True blocks JS access. False allows JS (like UI theme switchers)
Secure = true, // Only transmit over HTTPS
SameSite = SameSiteMode.Strict // Prevents CSRF leak
};
// 2. Append to response
Response.Cookies.Append("UserTheme", "Dark", options);
return Content("Theme Saved");
}
public IActionResult ReadTheme()
{
// 3. Read from incoming request
string theme = Request.Cookies["UserTheme"] ?? "Light";
return Content($"Current theme is {theme}");
}
public IActionResult DeleteTheme()
{
// 4. Browsers delete cookies when you set an expired date
Response.Cookies.Delete("UserTheme");
return Content("Cookie deleted");
}
}
Session requires configuring the server to allocate memory and tracking identifiers.
// 1. Add Distributed Memory Cache (Session backing store)
builder.Services.AddDistributedMemoryCache();
// 2. Add Session Services
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20); // Absolute expiration upon inactivity
options.Cookie.HttpOnly = true; // Highly secure, no JS access
options.Cookie.IsEssential = true; // Bypasses GDPR consent for essential auth flows
options.Cookie.Name = ".MyApp.Session";
});
var app = builder.Build();
// 3. IMPORTANT: UseSession MUST be between UseRouting and UseEndpoints/MapControllers
app.UseRouting();
app.UseSession();
app.UseAuthorization();
public class ShoppingCartController : Controller
{
public IActionResult AddItem()
{
// ISession only supports Int32 and String natively
HttpContext.Session.SetInt32("CartCount", 1);
HttpContext.Session.SetString("LastItem", "Laptop");
return Ok();
}
public IActionResult ViewCart()
{
int count = HttpContext.Session.GetInt32("CartCount") ?? 0;
string item = HttpContext.Session.GetString("LastItem");
// Remove item
HttpContext.Session.Remove("LastItem");
// Destroy entire session
HttpContext.Session.Clear();
return Ok();
}
}
Because ISession only accepts strings, you must serialize complex C# objects (like a full Cart) into JSON strings, and deserialize them upon retrieval.
// Extensions/SessionExtensions.cs
using System.Text.Json;
using Microsoft.AspNetCore.Http;
public static class SessionExtensions
{
public static void SetObjectAsJson<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T GetObjectFromJson<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default : JsonSerializer.Deserialize<T>(value);
}
}
// Controller Usage
public IActionResult Checkout()
{
var myCart = new ShoppingCart { Items = 3, Total = 450.50m };
// Save Complex Object
HttpContext.Session.SetObjectAsJson("ActiveCart", myCart);
// Retrieve Complex Object
var retrievedCart = HttpContext.Session.GetObjectFromJson<ShoppingCart>("ActiveCart");
return Ok();
}
builder.Services.AddDistributedMemoryCache() stores session data in the Local RAM of the web server. If your app is balanced across 3 servers (Server A, B, C), a user might save their cart on Server A. If their next click routes to Server B, their cart is utterly empty.
In enterprise production, you MUST use a distributed cache like Redis or SQL Server so all web servers access the identical session state.
// Program.cs — Enterprise Redis Registration
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("RedisUrl");
options.InstanceName = "MyAppSession_";
});
builder.Services.AddSession(); // Automatically uses the registered Redis cache!
Q: "If you store sensitive user preferences, would you use Session or Cookies?"
Architect Answer: "If the data is genuinely sensitive (like an authentication token, financial context, or PII), I strictly use Session State backed by Redis. Cookies live as plain text files on the client's physical hardware, vulnerable to cross-site scripting (XSS), physical machine compromise, or network sniffing if TLS drops. If I must store identifiers in cookies, they are encrypted, digitally signed by ASP.NET Core Data Protection, and marked strictly with HttpOnly and Secure flags to prevent JavaScript access."