Skip to main content

ASP.NET Core REST API Tutorial 2026: Build a Production API

Learn how to build a production-ready ASP.NET Core REST API in 2026. Step-by-step C# tutorial with CRUD, EF Core, and best practices. Start coding now!

If you searched for an ASP.NET Core REST API tutorial, you want more than a "Hello World" endpoint — you want to ship something real. In this 2026 guide you will build a production-ready ASP.NET Core Web API from scratch: clean CRUD endpoints, Entity Framework Core persistence, validation, versioning, authentication, and the small architectural decisions that separate a demo from a deployable service. Every code sample is runnable on .NET 9, and by the end you will understand not just how each piece works, but why it belongs in a professional API.

This tutorial targets three readers at once: beginners learning how to build a REST API in C#, intermediate developers looking for ASP.NET Core best practices, and seniors who want the advanced patterns. Skim to your level, or read straight through.

Why ASP.NET Core for REST APIs in 2026?

ASP.NET Core remains one of the fastest managed web frameworks in the world, routinely near the top of the TechEmpower benchmarks. It is cross-platform (Windows, Linux, macOS, containers), has first-class dependency injection built in, and ships with everything you need for a modern REST API: routing, model binding, validation, OpenAPI support, and middleware. For teams already invested in C#, it means one language across the stack and a mature tooling story with Visual Studio, VS Code, and the `dotnet` CLI.

REST (Representational State Transfer) is still the default architectural style for web APIs because it maps cleanly to HTTP: resources are nouns (URLs), and HTTP verbs (GET, POST, PUT, PATCH, DELETE) are the actions. Understanding this mapping is the foundation of every good API design.

Setting Up Your ASP.NET Core Web API Project

Make sure you have the .NET 9 SDK installed (run dotnet --version). Then scaffold and run a new Web API in three commands:

// Terminal commands
dotnet new webapi -n ProductApi --use-controllers
cd ProductApi
dotnet run

The --use-controllers flag gives you controller-based routing, which scales better than the minimal-API style for larger applications with many endpoints and shared conventions. Your project already includes OpenAPI support, so you get machine-readable API documentation out of the box.

The Program.cs entry point

In modern .NET, Program.cs uses top-level statements to wire up services and middleware. Here is a clean starting point:

var builder = WebApplication.CreateBuilder(args);

// Register services (the DI container)
builder.Services.AddControllers();
builder.Services.AddOpenApi();
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite(builder.Configuration.GetConnectionString("Default")));

var app = builder.Build();

// Configure the HTTP request pipeline (order matters!)
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

Why order matters: middleware runs top-to-bottom on the way in and bottom-to-top on the way out. Authentication must come before authorization, and both must precede MapControllers, or your security checks silently do nothing.

Designing the Data Model and EF Core Context

A REST API represents resources. Ours is a Product. Keep entities focused and use data annotations for validation that the framework enforces automatically.

using System.ComponentModel.DataAnnotations;

public class Product
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; } = string.Empty;

    [Range(0.01, 100000)]
    public decimal Price { get; set; }

    public int StockQuantity { get; set; }
    public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
}

The Entity Framework Core DbContext is your gateway to the database. It tracks changes and translates LINQ into SQL:

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }

    public DbSet<Product> Products => Set<Product>();
}

Create and apply a migration so EF Core builds the schema for you:

dotnet ef migrations add InitialCreate
dotnet ef database update

Building CRUD Endpoints the Right Way

Here is the heart of the tutorial: a controller exposing full CRUD operations. Notice the use of DTOs, correct HTTP status codes, async database calls, and CreatedAtAction for proper REST semantics.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/v1/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly AppDbContext _db;
    public ProductsController(AppDbContext db) => _db = db;

    // GET api/v1/products?page=1&pageSize=20
    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetAll(
        int page = 1, int pageSize = 20)
    {
        var items = await _db.Products
            .OrderBy(p => p.Id)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();
        return Ok(items);
    }

    // GET api/v1/products/5
    [HttpGet("{id:int}")]
    public async Task<ActionResult<Product>> GetById(int id)
    {
        var product = await _db.Products.FindAsync(id);
        return product is null ? NotFound() : Ok(product);
    }

    // POST api/v1/products
    [HttpPost]
    public async Task<ActionResult<Product>> Create(ProductCreateDto dto)
    {
        var product = new Product { Name = dto.Name, Price = dto.Price };
        _db.Products.Add(product);
        await _db.SaveChangesAsync();
        return CreatedAtAction(nameof(GetById),
            new { id = product.Id }, product);
    }

    // PUT api/v1/products/5
    [HttpPut("{id:int}")]
    public async Task<IActionResult> Update(int id, ProductCreateDto dto)
    {
        var product = await _db.Products.FindAsync(id);
        if (product is null) return NotFound();

        product.Name = dto.Name;
        product.Price = dto.Price;
        await _db.SaveChangesAsync();
        return NoContent();
    }

    // DELETE api/v1/products/5
    [HttpDelete("{id:int}")]
    public async Task<IActionResult> Delete(int id)
    {
        var product = await _db.Products.FindAsync(id);
        if (product is null) return NotFound();

        _db.Products.Remove(product);
        await _db.SaveChangesAsync();
        return NoContent();
    }
}

public record ProductCreateDto(string Name, decimal Price);

Why DTOs? Never bind requests directly to your EF entities. A Data Transfer Object controls exactly which fields a client can set, preventing "over-posting" attacks where a malicious user sets fields like Id or an admin flag. It also decouples your public contract from your database schema, so internal changes don't break API consumers.

Why CreatedAtAction? A correct POST returns HTTP 201 Created plus a Location header pointing at the new resource. This is proper REST semantics and helps clients discover the URL of what they just created.

Why async everywhere? Database and network calls are I/O-bound. Async methods release the request thread while waiting, so your server handles far more concurrent requests — critical for throughput under load.

Validation and Global Error Handling

Because the controller is decorated with [ApiController], model validation runs automatically: if ProductCreateDto violates a data annotation, the framework returns a structured 400 response before your code even executes. Don't fight this — lean on it.

For unexpected exceptions, a production API should never leak stack traces. Use the built-in problem-details handler to return consistent, RFC 7807 error responses:

builder.Services.AddProblemDetails();

// In the pipeline:
app.UseExceptionHandler();
app.UseStatusCodePages();

This gives every error a predictable JSON shape (type, title, status, detail), which client developers will thank you for.

Securing Your API with JWT Authentication

Most production APIs need authentication. The industry standard for stateless REST APIs is the JWT (JSON Web Token) bearer scheme. Register it once:

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new()
        {
            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"]!))
        };
    });

Then protect any endpoint or controller with a single attribute:

[Authorize]
[HttpDelete("{id:int}")]
public async Task<IActionResult> Delete(int id) { /* ... */ }

Why JWT? Tokens are self-contained and verified with a signing key, so your API stays stateless — no server-side session store, which makes horizontal scaling across containers trivial. Keep the signing key in a secret manager (Azure Key Vault, AWS Secrets Manager, or user-secrets in development), never in source control.

ASP.NET Core Best Practices and Common Pitfalls

These are the lessons that turn a working API into a production-ready one:

  • Version your API from day one. The api/v1/ prefix (or a versioning library) lets you evolve without breaking existing clients.
  • Always paginate collection endpoints. Returning every row is a performance and memory disaster once your table grows — the pagination shown above is non-negotiable.
  • Use AsNoTracking() for read-only queries. It skips EF Core's change tracker and measurably speeds up GET requests.
  • Return DTOs, not entities, from GETs too. This prevents accidentally serializing navigation properties and leaking internal data or causing circular-reference errors.
  • Don't call .Result or .Wait() on async code. This blocks threads and can deadlock — always await.
  • Enable HTTPS and CORS deliberately. Configure a specific CORS policy rather than AllowAnyOrigin in production.
  • Add health checks and structured logging. app.MapHealthChecks("/health") plus Serilog gives you the observability real deployments need.
  • Avoid the "fat controller." As logic grows, move business rules into services injected via DI, keeping controllers thin and testable.

Testing and Deploying Your REST API

Use the generated OpenAPI document (or a tool like Scalar or the .http files in Visual Studio) to exercise every endpoint. For automated confidence, WebApplicationFactory<T> lets you write fast integration tests that spin up the whole pipeline in memory. When you're ready to ship, containerize with a simple Dockerfile and deploy to Azure App Service, AWS, or any Kubernetes cluster — ASP.NET Core runs identically everywhere.

Conclusion: Key Takeaways

You've completed a full ASP.NET Core REST API tutorial and built a service with real production concerns baked in. Here's what to remember:

  • Map resources to HTTP verbs and return correct status codes (200, 201, 204, 400, 404) — that's the essence of REST.
  • Use DTOs to protect your data model and control your public contract.
  • Go async end-to-end with Entity Framework Core for scalable, high-throughput I/O.
  • Secure with JWT, validate automatically via [ApiController], and return consistent problem-details errors.
  • Apply the best practices — versioning, pagination, no-tracking reads, and thin controllers — to keep the API maintainable as it grows.

Start with the CRUD controller above, then layer in authentication and observability as your ASP.NET Core Web API matures. Bookmark this guide, fire up dotnet run, and ship your production-ready REST API today.

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

Comments

Popular posts from this blog

.NET MAUI Tutorial 2026: Build Cross-Platform Apps in C#

Learn .NET MAUI in 2026 to build iOS, Android, Windows & Mac apps from one C# codebase. Start this cross-platform tutorial with code examples today. .NET MAUI (Multi-platform App UI) is Microsoft's framework for building native iOS, Android, Windows, and macOS apps from a single C# codebase . If you've ever wanted to ship a mobile app without learning Swift, Kotlin, and Win32 separately, this .NET MAUI tutorial for 2026 is your starting point. In this guide you'll learn what .NET MAUI is, why it matters for cross-platform app development in C#, and how to build your first working app — with runnable code examples and the best practices senior engineers actually use in production. What Is .NET MAUI and Why Use It in 2026? .NET MAUI is the evolution of Xamarin.Forms, fully integrated into the modern .NET runtime. With one project and one language — C# — you target four platforms. The framework compiles to native UI controls on each device, so a button on iOS...

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...