ASP.NET Core Guide: Cross-Platform .NET Web Applications
ASP.NET Core is Microsoft’s cross-platform, high-performance framework for building modern web applications. It runs on Windows, macOS, and Linux, and it is the foundation for all .NET web development.
This guide covers MVC architecture, Razor Pages, Entity Framework Core, authentication, minimal APIs, and production deployment.
MVC Architecture
ASP.NET Core follows the Model-View-Controller (MVC) pattern. Models represent data, Views render the UI, and Controllers handle requests:
public class ProductController : Controller
{
private readonly ApplicationDbContext _context;
public ProductController(ApplicationDbContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
var products = await _context.Products.ToListAsync();
return View(products);
}
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Product product)
{
if (ModelState.IsValid)
{
_context.Add(product);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(product);
}
---Routing
ASP.NET Core uses attribute routing and conventional routing. Attribute routing is preferred for Web APIs:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetAll() { /* ... */ }
[HttpGet("{id}")]
public IActionResult GetById(int id) { /* ... */ }
[HttpPost]
public IActionResult Create([FromBody] Product product) { /* ... */ }
---Minimal APIs
Minimal APIs provide a simplified way to build HTTP APIs with less ceremony than MVC controllers:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", async (AppDbContext db) =>
await db.Products.ToListAsync());
app.MapGet("/products/{id}", async (int id, AppDbContext db) =>
await db.Products.FindAsync(id) is Product product
? Results.Ok(product)
: Results.NotFound());
app.MapPost("/products", async (Product product, AppDbContext db) =>
{
db.Products.Add(product);
await db.SaveChangesAsync();
return Results.Created($"/products/{product.Id}", product);
---);
app.Run();Minimal APIs are ideal for small microservices, health checks, and simple HTTP APIs. Use MVC controllers for larger, more complex applications with multiple views and extensive middleware.
Razor Pages
Razor Pages provide a page-focused approach where each page has its own handler:
public class IndexModel : PageModel
{
private readonly AppDbContext _context;
public IndexModel(AppDbContext context)
{
_context = context;
}
public IList<Product> Products { get; set; }
public async Task OnGetAsync()
{
Products = await _context.Products.ToListAsync();
}
---Razor Pages are ideal for form-heavy applications where each page represents a distinct feature. They follow the convention-over-configuration pattern and reduce boilerplate compared to MVC.
Middleware Pipeline
ASP.NET Core’s request pipeline is composed of middleware components. Each middleware can inspect, modify, or short-circuit the request. Order matters — authentication must run before authorization, and error handling should be early to catch downstream exceptions:
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
---
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
---
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();Dependency Injection
ASP.NET Core has built-in dependency injection with three service lifetimes:
- Singleton — Created once, shared across all requests
- Scoped — Created once per request (typically per HTTP request)
- Transient — Created each time they are requested
Register services in Program.cs:
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddTransient<IEmailService, SmtpEmailService>();Entity Framework Core
EF Core is the object-relational mapper for .NET. Define models and let EF Core generate migrations:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
---
public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
---Authentication and Authorization
ASP.NET Core Identity provides built-in authentication with user registration, login, and role management. For API authentication, use JWT bearer tokens:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});Summary
ASP.NET Core provides a comprehensive framework for building web applications. Use MVC controllers for complex APIs, Minimal APIs for simple services, and Razor Pages for form-based UIs. EF Core simplifies database access, built-in DI keeps code testable, and middleware composes the request pipeline.
FAQ
What is the difference between ASP.NET Core and ASP.NET Framework? ASP.NET Core is cross-platform, open-source, and designed for modern cloud applications. ASP.NET Framework is Windows-only and legacy. All new .NET development should use ASP.NET Core.
Should I use MVC controllers or Minimal APIs? Use Minimal APIs for small services, health checks, and prototypes. Use MVC for complex applications with multiple related endpoints, model binding, and validation attributes.
How do I deploy ASP.NET Core? Build with dotnet publish, then deploy the output folder. Host on IIS (Windows), NGINX (Linux), or as a Docker container. Azure App Service provides managed hosting.
What ORM should I use with ASP.NET Core? Entity Framework Core is the default and most popular. Dapper is a lightweight alternative for high-performance scenarios where you write raw SQL.
Middleware Pipeline
ASP.NET Core’s middleware pipeline is the foundation of request processing. Each middleware component can inspect, modify, or short-circuit the request/response:
var app = builder.Build();
// Order matters — execute in the order added
app.UseExceptionHandler("/error"); // 1. Global error handling
app.UseHttpsRedirection(); // 2. Redirect HTTP to HTTPS
app.UseStaticFiles(); // 3. Serve static files
app.UseRouting(); // 4. Route matching
app.UseAuthentication(); // 5. Authenticate user
app.UseAuthorization(); // 6. Authorize user
app.MapControllers(); // 7. Execute controller actionCustom middleware can be written as a class with InvokeAsync method:
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
_logger.LogInformation("Request {Method} {Path}", context.Request.Method, context.Request.Path);
await _next(context);
sw.Stop();
_logger.LogInformation("Response {StatusCode} in {Elapsed}ms", context.Response.StatusCode, sw.ElapsedMilliseconds);
}
---Minimal APIs vs Controllers
ASP.NET Core 6 introduced Minimal APIs as an alternative to the traditional Controller-based approach:
// Minimal API — concise, no boilerplate
var app = builder.Build();
app.MapGet("/api/users/{id}", async (int id, UserRepository repo) =>
{
var user = await repo.FindById(id);
return user is not null ? Results.Ok(user) : Results.NotFound();
---);
// Controller — structured, separates concerns
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var user = await _repo.FindById(id);
return user is not null ? Ok(user) : NotFound();
}
---Use Minimal APIs for simple endpoints with few dependencies. Use Controllers for complex endpoints requiring filters, model binding attributes, or multiple action methods sharing dependencies. Both approaches work together in the same project.
Configuration Management
ASP.NET Core’s configuration system is hierarchical and supports multiple sources evaluated in order:
{
"Database": {
"ConnectionString": "Server=localhost;Database=mydb",
"Timeout": 30
},
"Logging": {
"Level": "Information"
}
---Configuration sources are evaluated in order — later sources override earlier ones:
appsettings.jsonappsettings.{Environment}.json- Environment variables (using
:or__as separators:Database__ConnectionString) - Command-line arguments
- User secrets (development only)
- Azure Key Vault / AWS Secrets Manager
Use the Options pattern for strongly typed configuration access:
public class DatabaseOptions
{
public string ConnectionString { get; set; } = "";
public int Timeout { get; set; } = 30;
---
// In Startup
services.Configure<DatabaseOptions>(Configuration.GetSection("Database"));This provides compile-time type checking, IntelliSense for configuration keys, and reload support without restarting the application.
Entity Framework Core
EF Core is the primary ORM for ASP.NET Core applications. Use the Code First approach to define your database schema in C#:
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>(entity =>
{
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId);
});
}
---Use migrations to evolve the database schema as the application changes:
dotnet ef migrations add AddBlogRating
dotnet ef database updateSignalR for Real-Time Features
SignalR is ASP.NET Core’s real-time communication library. It abstracts WebSocket, Server-Sent Events, and long polling behind a consistent API:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
---
// Client-side JavaScript
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.build();
connection.on("ReceiveMessage", (user, message) => { /* update UI */ });
await connection.start();SignalR handles connection management, reconnection, and scaling out with Redis backplane for multi-server deployments.
ASP.NET Core Performance Optimization
Performance tuning in ASP.NET Core focuses on several key areas:
Response compression — Enable Gzip or Brotli compression for API responses. Add services.AddResponseCompression() and app.UseResponseCompression() in the pipeline. This reduces response size by 60-80% for text-based responses.
Output caching — Cache responses for GET endpoints with [ResponseCache(Duration = 60)]. Use distributed caching (Redis, SQL Server) for load-balanced deployments. Cache public, idempotent endpoints aggressively.
Minimal API performance — Minimal APIs have lower overhead than Controllers because they skip model binding, filters, and controller activation. For high-throughput endpoints, prefer Minimal APIs over Controllers.
Native AOT compilation — .NET 8 supports native AOT compilation, producing a standalone executable with faster startup time and lower memory usage. Not all ASP.NET Core features work with AOT (no dynamic code generation), but for performance-critical services, it provides significant benefits.
ASP.NET Core Security
Security is built into ASP.NET Core at multiple levels:
- Data Protection API — Cryptographic APIs for encrypting sensitive data. Used for cookies, authentication tokens, and anti-CSRF tokens.
- Anti-forgery tokens — Auto-generated tokens on forms prevent CSRF attacks. Enabled by default with
[AutoValidateAntiforgeryToken]. - CORS — Configure cross-origin requests explicitly with
AddCors()andUseCors(). Restrict allowed origins, methods, and headers to the minimum required. - Rate limiting — .NET 7+ has built-in rate limiting with multiple policies (fixed window, sliding window, token bucket, concurrency). Apply rate limits to authentication endpoints and high-cost operations.
Related: Spring Boot Guide | Backend Framework Career Guide