Skip to content

Aspnet Web Api

DodaTech 4 min read

title: ASP.NET Core Web API — Complete Guide to Building RESTful APIs description: 'Learn building ASP.NET Core Web APIs: controllers, actions, JSON responses, status codes, model binding, validation, versioning, and OpenAPI/Swagger documentation.' date: 2026-06-28 lastmod: 2026-06-28 weight: 26 tags: [backend, aspnet]


ASP.NET Core Web API provides a framework for building RESTful HTTP APIs with controllers, attribute routing, JSON serialization, model validation, and OpenAPI documentation.

## What You'll Learn

By the end of this tutorial, you'll create Web API controllers, handle CRUD operations, configure JSON serialization, implement API versioning, document with Swagger/OpenAPI, and follow RESTful conventions.

## Real-World Use

A SaaS platform exposes a RESTful API for managing customers, invoices, and payments. Mobile apps and third-party integrations consume the API via JSON over HTTPS.

## Web API Learning Path

```mermaid
flowchart LR
  A[JWT] --> B[Web API]
  B --> C[Minimal API]
  C --> D[SignalR]
  D --> E[Testing]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

API Controller

[ApiController]
[Route("api/v{version:apiVersion}/products")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _service;
    public ProductsController(IProductService service) => _service = service;
    
    [HttpGet]
    public async Task<ActionResult<IEnumerable<ProductDto>>> GetAll(
        [FromQuery] int page = 1, [FromQuery] int size = 10)
    {
        var products = await _service.GetAllAsync(page, size);
        return Ok(products);
    }
    [HttpGet("{id:int}")]
    public async Task<ActionResult<ProductDto>> GetById(int id)
    {
        var product = await _service.GetByIdAsync(id);
        if (product == null) return NotFound();
        return Ok(product);
    }
    [HttpPost]
    public async Task<ActionResult<ProductDto>> Create(CreateProductDto dto)
    {
        var product = await _service.CreateAsync(dto);
        return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
    }
    [HttpPut("{id:int}")]
    public async Task<IActionResult> Update(int id, UpdateProductDto dto)
    {
        await _service.UpdateAsync(id, dto);
        return NoContent();
    }
    [HttpDelete("{id:int}")]
    public async Task<IActionResult> Delete(int id)
    {
        await _service.DeleteAsync(id);
        return NoContent();
    }
}

Swagger/OpenAPI

dotnet add package Swashbuckle.AspNetCore
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "My API",
        Version = "v1",
        Description = "API for managing products"
    });
    // Add JWT auth to Swagger
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

API Versioning

dotnet add package Microsoft.AspNetCore.Mvc.Versioning
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = new UrlSegmentApiVersionReader();
});

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/products")]
public class ProductsV1Controller : ControllerBase { ... }

[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/products")]
public class ProductsV2Controller : ControllerBase { ... }

Error Handling

// Global exception middleware
app.UseExceptionHandler(handler => handler.Run(async context =>
{
    var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
    var response = new ApiError("Internal server error", 500);
    context.Response.StatusCode = 500;
    context.Response.ContentType = "application/json";
    await context.Response.WriteAsJsonAsync(response);
}));

// ProblemDetails (automatic with [ApiController])
app.UseStatusCodePages();
// Customizes 4xx responses to RFC 7807 ProblemDetails format

Common Mistakes

1. Not Using [ApiController]

Without [ApiController], you lose automatic model validation, binding source inference, and ProblemDetails responses.

2. Returning Domain Models

Domain entities expose internal details. Return DTOs shaped for the API consumer.

3. Ignoring Pagination

Returning all rows without pagination overwhelms clients and databases. Always implement Skip/Take.

4. Not Using ProblemDetails

Standard error format (RFC 7807) helps clients parse errors consistently.

5. Missing API Versioning

Without versioning, breaking changes break existing clients. Version from day one.

Practice Questions

1. What does [ApiController] provide?

Automatic model validation, binding source inference for [FromBody], and ProblemDetails error responses.

2. How do you implement pagination in an API?

Accept page and size query parameters. Use .Skip((page-1)*size).Take(size) in queries.

3. What is the difference between Created and CreatedAtAction?

CreatedAtAction generates a Location header with the URL to retrieve the created resource.

4. How do you version an API?

Use URL segment versioning (/api/v1/products) or header/query parameter versioning.

5. Challenge: Create a complete CRUD API for orders with pagination and Swagger docs.

[ApiController]
[Route("api/v1/orders")]
public class OrdersController : ControllerBase
{
    [HttpGet] public async Task<ActionResult<PagedResult<OrderDto>>> GetAll(int page=1, int size=10) { ... }
    [HttpGet("{id}")] public async Task<ActionResult<OrderDto>> GetById(int id) { ... }
    [HttpPost] public async Task<ActionResult<OrderDto>> Create(CreateOrderDto dto) { ... }
    [HttpPut("{id}")] public async Task<IActionResult> Update(int id, UpdateOrderDto dto) { ... }
    [HttpDelete("{id}")] public async Task<IActionResult> Delete(int id) { ... }
}

FAQ

{{< faq "Can I return XML from a Web API?" "Yes. Add options => options.FormatterMappings.SetMediaTypeMappingForFormat(\"xml\", \"application/xml\") and request with Accept: application/xml." >}}

What is the best way to handle file uploads in Web API?

Use IFormFile parameter with [FromForm]. For large files, use streaming with multipart/form-data.

How do I handle large JSON payloads?

Configure MaxRequestBodySize in Kestrel options. Use streaming for very large payloads.

What is the difference between ActionResult and IActionResult?

ActionResult provides better Swagger docs and type safety. IActionResult is more flexible.

How do I customize JSON serialization?

Configure JsonSerializerOptions: builder.Services.AddControllers().AddJsonOptions(o => o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase);

Mini Project: Product API

Build a complete REST API for products with Swagger and pagination.

[ApiController]
[Route("api/v1/products")]
public class ProductsController : ControllerBase
{
    [HttpGet] public ActionResult<PagedResult<ProductDto>> GetAll(int p=1, int s=10) => Ok(new PagedResult<ProductDto>(items, total, p, s));
    [HttpGet("{id}")] public ActionResult<ProductDto> GetById(int id) => Ok(new ProductDto(id, "Laptop", 999.99m));
    [HttpPost] public ActionResult<ProductDto> Create(CreateProductDto dto) => CreatedAtAction(nameof(GetById), new { id = 1 }, new ProductDto(1, dto.Name, dto.Price));
}

What's Next

ASP.NET Core Minimal API ASP.NET Core SignalR

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro