Back to CourseBeginner10 min read
DotNet Web API /

C# Concepts You Need for ASP.NET Core Web API

The C# features ASP.NET Core Web API uses on every endpoint, from records, attributes and lambdas to generics, LINQ, async, nullable types and dependency injection, with links to learn each one.

Bishal Biswas

Bishal Biswas

Self Learner

Published on: September 1, 2026
Last updated: November 1, 2026
Beginner Topic

Why this topic exists

A Web API is a C# program that receives HTTP requests and answers with JSON. Everything you write in this course, from endpoints to services to tests, is ordinary C#. You do not need to know the whole language before you start, but some features show up on almost every line of an API.

This topic lists those features in the order you will meet them. For each one you get:

  • a short explanation in plain words,
  • a small piece of real Web API code that uses it,
  • a link to the matching topic in the C# course for the full story.

The list is split into three groups:

  1. Know before you start. You will see these in the first few topics.
  2. Learn as you go. They appear later, so you can learn them when you get there.
  3. Can wait. Good C#, but this course barely needs it.

Already comfortable with modern C#? Skim the samples and jump to Setting Up the .NET SDK and Tools.

Know before you start

1. Classes, objects and properties

Your entities (what you store in the database) are classes with properties. ASP.NET Core turns their public properties into JSON fields, and back again.

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public string Author { get; set; } = "";
    public decimal Price { get; set; }
}

{ get; set; } creates an auto property. A Book with Id = 1 and Title = "Dune" becomes {"id":1,"title":"Dune",...} in the response. Notice that the JSON names start with a lower case letter: that is the default in ASP.NET Core.

You will create objects with object initializers all the time:

var book = new Book { Id = 1, Title = "Dune", Author = "Frank Herbert", Price = 9.99m };

Learn more: Classes and Objects, Fields and Properties, Variables and Data Types.

2. Records

Records are the most common way to write DTOs (Data Transfer Objects): the shapes your API accepts and returns. One line gives you a type with properties, a constructor and value equality.

public record CreateBookRequest(string Title, string Author, int Year, decimal Price);
public record BookResponse(int Id, string Title, decimal Price);

You will meet records on the very first day, because the project template already contains one. Records must come after the top level code in Program.cs, which is why the template puts them at the bottom.

Learn more: Records. In this course: DTOs and Why You Need Them.

3. Lambda expressions

A lambda is a small inline function, such as b => b.Price < 10. Minimal APIs are built entirely from lambdas: each endpoint is a URL plus a lambda that handles it.

app.MapGet("/books/{id}", (int id) => $"You asked for book {id}");

The parameter id is filled from the URL, and whatever the lambda returns becomes the response. Lambdas also appear when you configure services: builder.Services.AddCors(options => ...).

Learn more: Lambda Expressions, Delegates. In this course: Minimal APIs Introduction.

4. Methods, parameters and return types

In the controller style, every endpoint is a public method called an action. ASP.NET Core fills its parameters from the route, the query string or the JSON body.

// GET /api/books?page=2
[HttpGet]
public IActionResult GetAll(int page = 1) => Ok(page);

You need to read a method signature without effort: return type, name, parameters and default values. The => form is an expression bodied method, a short way to write a one line method.

Learn more: Methods and Functions, Method Parameters and Return Types.

5. Attributes

Attributes are the labels in square brackets. ASP.NET Core reads them to decide routing, where data comes from and what is valid:

[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
    [HttpPost]
    public IActionResult Create([FromBody] CreateBookRequest request) => Ok(request);
}

You will also put validation attributes such as [Required] and [Range] on DTO properties, and [Authorize] on endpoints that need a logged in user.

Learn more: Attributes. In this course: Attribute Routing, Model Validation with Data Annotations.

6. Inheritance and interfaces

API controllers inherit from ControllerBase, which gives you helpers like Ok(), NotFound(), CreatedAtAction() and BadRequest(). Your own services are written behind interfaces, so the controller depends on a promise instead of a concrete class:

public interface IBookService
{
    Task<Book?> FindAsync(int id);
}

This makes testing easy: in a unit test you hand the controller a fake IBookService.

Learn more: Inheritance, Interfaces, Polymorphism.

7. Generics and collections

Generics let one type work with many others. In an API you see them in return types and collections:

public ActionResult<List<BookResponse>> GetAll() => new List<BookResponse>();
public ActionResult<BookResponse> GetById(int id) => NotFound();

ActionResult<T> means “either a T sent back as JSON, or another result such as 404”. List<T>, IEnumerable<T> and Dictionary<TKey, TValue> are everywhere too.

Learn more: Generics, List<T>, Dictionary<TKey, TValue>.

8. Nullable types and the ? mark

Projects have nullable reference types turned on. Book? means “this might be null”, and the compiler warns you if you use it without checking. In an API, a missing item usually becomes a 404:

Book? book = await _books.FindAsync(id);
return book is null ? NotFound() : Ok(book);

The ? : here is the conditional operator: “if this, then that, otherwise the other”.

Learn more: Variables and Data Types, Operators in C#, Conditional Statements.

9. LINQ

LINQ methods such as Where, OrderBy, Select, Skip and Take filter and reshape data. With Entity Framework Core the same code becomes SQL. In an API you will use Select constantly to turn entities into DTOs:

var page = await _db.Books
    .OrderBy(b => b.Title)
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize)
    .Select(b => new BookResponse(b.Id, b.Title, b.Price))
    .ToListAsync();

Learn more: LINQ Fundamentals, LINQ Method Syntax. In this course: Pagination, Filtering and Sorting.

10. async and await

APIs spend most of their time waiting: for the database, for another API, for a file. async and await let one server handle thousands of requests while it waits.

[HttpGet("{id}")]
public async Task<ActionResult<Book>> GetById(int id)
{
    var book = await _db.Books.FindAsync(id);
    return book is null ? NotFound() : book;
}

Remember three things: mark the method async, wrap the return type in Task<...>, and await every method that ends in Async. Never block with .Result or .Wait().

Learn more: Async and Await. In this course: Asynchronous Controllers.

Learn as you go

11. Constructors and dependency injection

ASP.NET Core creates your controllers and services, and passes in what they need through the constructor. This is dependency injection. You will see the classic form and the shorter primary constructor form:

public class BooksController(IBookService books) : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id) =>
        await books.FindAsync(id) is { } book ? Ok(book) : NotFound();
}

In Minimal APIs, services are injected straight into the lambda’s parameters instead: app.MapGet("/books", (IBookService books) => ...).

Learn more: Constructors and Destructor, Dependency Injection Fundamentals. In this course: Dependency Injection in ASP.NET Core.

12. Program.cs, extension methods and static classes

Program.cs uses top level statements: no Main method, the code just runs from top to bottom. Calls like builder.Services.AddControllers() and app.MapGet(...) are extension methods. You will write your own too, for example to keep mapping code in one place:

public static class BookMappings
{
    public static BookResponse ToResponse(this Book book) =>
        new(book.Id, book.Title, book.Price);
}

// Now any Book has a ToResponse() method
BookResponse dto = book.ToResponse();

Learn more: Your First C# Program, LINQ Method Syntax (explains how extension methods work). In this course: Object Mapping with AutoMapper.

13. Pattern matching and switch expressions

Modern API code uses patterns to pick a result in one expression. You saw is null and is { } book above. switch expressions are handy for things like sort orders or mapping errors to status codes:

int status = error switch
{
    NotFoundException    => 404,
    ValidationException  => 400,
    _                    => 500
};

Learn more: Switch Statements and Expressions, Advanced Pattern Matching.

14. Exceptions

An unhandled exception becomes a 500 error. You will learn to catch the ones you expect, and to let one central handler turn the rest into a clean error response.

try
{
    await _db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
    return Conflict();
}

Learn more: Exception Handling. In this course: Global Exception Handling, Problem Details and Error Responses.

15. Enums, strings and interpolation

Enums give names to fixed choices, such as an order status, and they show up in JSON as numbers or strings. String interpolation builds URLs and log messages:

public enum OrderStatus { Pending, Paid, Shipped }

_logger.LogInformation("Book {BookId} was created", book.Id);   // structured logging
string location = $"/api/books/{book.Id}";

Note that logging uses {BookId} placeholders, not $"...", so tools can search by the value.

Learn more: Variables and Data Types, String Fundamentals. In this course: Logging with Serilog.

16. IDisposable, using and CancellationToken

Streams for file uploads are disposable, so wrap them in using. HttpClient should come from DI (IHttpClientFactory) rather than new plus using. A CancellationToken parameter lets your action stop work when the caller gives up:

[HttpGet]
public async Task<List<Book>> GetAll(CancellationToken ct) =>
    await _db.Books.ToListAsync(ct);

Learn more: IDisposable and Resource Management, Task Parallel Library. In this course: Calling Other APIs with HttpClient, File Uploads and Downloads.

Can wait

You can finish this course without these:

  • Events. Web APIs rarely use C# events. See Events when you need them.
  • Threads and locks. ASP.NET Core manages threads. Use async instead of Thread, and use a thread safe type such as ConcurrentDictionary if you share data. Synchronization and Thread Safety explains why later.
  • Reflection, source generators, Span<T>, unsafe code and pointers. Useful for library authors and performance tuning, not for building endpoints.

Your checklist

C# concept Where Web API uses it C# topic
Classes and properties Entities, JSON fields Fields and Properties
Records Request and response DTOs Records
Lambdas Minimal API endpoints, configuration Lambda Expressions
Methods and parameters Controller actions Method Parameters and Return Types
Attributes Routing, binding, validation, auth Attributes
Inheritance and interfaces ControllerBase, services Interfaces
Generics ActionResult<T>, lists Generics
Nullable ? Not found becomes 404 Variables and Data Types
LINQ Queries and DTO mapping LINQ Method Syntax
async and await Every database call Async and Await
Constructors and DI Getting services Dependency Injection Fundamentals

If you can follow the code panel on this page, you are ready for this course. Building web pages or a desktop app instead? The MVC and WPF courses have their own version of this list.

Quick recap

  • A Web API is plain C#, and a small set of features covers nearly every endpoint.
  • Before you start, know classes, records, lambdas, methods, attributes, inheritance, interfaces, generics, nullable types, LINQ and async/await.
  • Learn dependency injection, extension methods, pattern matching, exceptions, enums and cancellation tokens as the course reaches them.
  • Events, threads, reflection and low level performance features can wait.
  • Each concept links to a full topic in the C# course when you want to go deeper.
Program.cs

// Program.cs
// A tiny BookStore API that uses every "know before you start" C# feature.
// The numbers match the sections of this topic.
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);          // 12. top level statements
builder.Services.AddControllers();                         // 12. extension method
builder.Services.AddSingleton<IBookService, BookService>(); // 6 + 11. interface and DI
var app = builder.Build();
app.MapControllers();
// 3. Minimal API endpoint: a URL plus a lambda. IBookService is injected into the lambda.
app.MapGet("/books/cheap", async (IBookService books, decimal max = 10m) =>
    (await books.GetAllAsync())
        .Where(b => b.Price <= max)                         // 9. LINQ
        .Select(b => b.ToResponse()));                      // 12. our own extension method
app.Run();
// 1. Entity: a class with auto properties
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public string Author { get; set; } = "";
    public decimal Price { get; set; }
}
// 2. DTOs as records, with 5. validation attributes
public record CreateBookRequest(
    [Required, StringLength(100)] string Title,
    [Required] string Author,
    [Range(0.01, 1000)] decimal Price);
public record BookResponse(int Id, string Title, decimal Price);
// 12. Extension method: every Book gets a ToResponse() method
public static class BookMappings
{
    public static BookResponse ToResponse(this Book book) => new(book.Id, book.Title, book.Price);
}
// 6. The interface (the promise) and 7 + 8 + 10. generics, nullable and Task
public interface IBookService
{
    Task<List<Book>> GetAllAsync();
    Task<Book?> FindAsync(int id);
    Task<Book> AddAsync(CreateBookRequest request);
}
public class BookService : IBookService
{
    private readonly List<Book> _books = new()
    {
        new Book { Id = 1, Title = "Dune", Author = "Frank Herbert", Price = 9.99m },  // 1. object initializer
        new Book { Id = 2, Title = "Emma", Author = "Jane Austen", Price = 4.99m }
    };
    public Task<List<Book>> GetAllAsync() => Task.FromResult(_books);
    public Task<Book?> FindAsync(int id) => Task.FromResult(_books.FirstOrDefault(b => b.Id == id));
    public Task<Book> AddAsync(CreateBookRequest request)
    {
        var book = new Book
        {
            Id = _books.Max(b => b.Id) + 1,
            Title = request.Title,
            Author = request.Author,
            Price = request.Price
        };
        _books.Add(book);
        return Task.FromResult(book);
    }
}
// 4 + 5 + 6. Controller style: attributes, inheritance from ControllerBase, action methods
[ApiController]
[Route("api/[controller]")]
public class BooksController(IBookService books) : ControllerBase   // 11. primary constructor
{
    // GET /api/books
    [HttpGet]
    public async Task<ActionResult<List<BookResponse>>> GetAll()    // 7 + 10. generics and async
    {
        var all = await books.GetAllAsync();
        return all.OrderBy(b => b.Title).Select(b => b.ToResponse()).ToList();
    }
    // GET /api/books/1
    [HttpGet("{id:int}")]
    public async Task<ActionResult<BookResponse>> GetById(int id)
    {
        Book? book = await books.FindAsync(id);                      // 8. may be null
        return book is null ? NotFound() : book.ToResponse();
    }
    // POST /api/books  with a JSON body
    [HttpPost]
    public async Task<ActionResult<BookResponse>> Create(CreateBookRequest request)
    {
        Book book = await books.AddAsync(request);
        return CreatedAtAction(nameof(GetById), new { id = book.Id }, book.ToResponse());
    }
}

Was this explanation helpful?

Your feedback helps us improve the content for everyone.