.NET Web API Core

.NET Web API Core

Hey everyone! In this article, I'll walk you through every element of .NET Web API Core, covering everything from the Program.cs file to SwaggerUI integration.

Introduction

.NET Web API Core is a framework for building RESTful services using .NET Core. It allows applications to communicate over HTTP using standard web protocols. It is lightweight, cross-platform, and supports dependency injection, middleware, and routing.

Key Features:

  • Cross-Platform: Runs on Windows, Linux, and macOS.
  • Dependency Injection: Built-in DI for better code maintainability.
  • Routing & Middleware: Uses attribute routing and middleware for handling requests.
  • JSON Support: Uses System.Text.Json for JSON serialization.
  • Versioning & Authentication: Supports API versioning, authentication, and authorization.

Understanding Program.cs in .NET Core

In .NET Core, the Program.cs file is the entry point of the application. It is responsible for configuring and starting the application. The structure of Program.cs has evolved, and in .NET 6 and later, it follows a minimal hosting model.

A typical Program.cs file in a .NET 6+ Web API project looks like this:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();

app.MapControllers();

app.Run();        

Breaking Down the Elements in Program.cs

var builder = WebApplication.CreateBuilder(args);

  • This initializes a web application builder.
  • It sets up configuration, dependency injection (DI), logging, and middleware.
  • args are command-line arguments passed to the application.

builder.Services.AddControllers();

  • Registers MVC controllers in the dependency injection container.
  • Enables API controller support.

builder.Services.AddEndpointsApiExplorer();

  • This service is required for OpenAPI (Swagger) support in minimal APIs.
  • It helps discover endpoints dynamically.

builder.Services.AddSwaggerGen();

  • Registers Swagger (OpenAPI), which generates API documentation.
  • Allows testing API endpoints through a UI.

var app = builder.Build();

  • This builds the application based on the services configured.
  • After this, middleware and the request pipeline can be configured.

Configuring Middleware

Middleware in .NET Core controls the request processing pipeline.

a. if (app.Environment.IsDevelopment()) { ... }

  • Checks if the application is running in the Development environment.
  • If true, Swagger UI is enabled for API documentation and testing.

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}        

b. app.UseHttpsRedirection();

Forces HTTPS redirection, ensuring secure communication.

app.UseHttpsRedirection();        

c. app.UseAuthorization();

  • Enables Authorization middleware.
  • Works with authentication mechanisms to control access to APIs.

app.MapControllers();

  • Maps controller endpoints to the application.
  • Without this, controller-based APIs won’t work.

app.Run();

Starts the application and begins listening for incoming HTTP requests.

Dependency Injection

Dependency Injection (DI) is a design pattern used in .NET Core to achieve loose coupling between components. Instead of creating instances of dependencies manually, they are injected by the framework.

Benefits of Dependency Injection:

  • Reduces tight coupling
  • Enhances testability (Mocking services in unit tests)
  • Improves maintainability and scalability
  • Manages object lifetimes efficiently

Dependency Injection (DI) Scopes

In .NET Core, Dependency Injection (DI) supports three lifetime scopes to control the lifetime of service instances:

Article content

1. Transient (Short-lived)

  • A new instance is created every time it's requested.
  • Best for lightweight, stateless services.
  • Use case: Utility services like formatters or small calculations.

Registration:

builder.Services.AddTransient<IProductService, ProductService>();
        

Example:

public class ProductService : IProductService
{
    public Guid Id { get; } = Guid.NewGuid();
}
        

If you inject ProductService multiple times in a single request, each injection gets a new instance.

2. Scoped (Request-lifetime)

  • A single instance is created per HTTP request.
  • Best for database contexts (e.g., Entity Framework Core DbContext).
  • Use case: Business logic or repositories.

Registration:

builder.Services.AddScoped<IProductService, ProductService>();
        

Example: If ProductService is injected into multiple components within the same request, they all share the same instance.

3. Singleton (Application-wide)

  • One instance for the entire application lifetime.
  • Best for services that don’t change state (e.g., caching, logging).
  • Use case: Configuration settings, logging services.

Registration:

builder.Services.AddSingleton<IProductService, ProductService>();
        

Example: All requests share the same instance throughout the application lifecycle.

Testing DI Scopes

Modify ProductService to track instance lifetime using a Guid:

public class ProductService : IProductService
{
    public Guid Id { get; } = Guid.NewGuid();

    public string GetServiceId() => Id.ToString();
}
        

Inject into a controller:

[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet("service-id")]
    public IActionResult GetServiceId()
    {
        return Ok(_productService.GetServiceId());
    }
}
        

Test via API Calls

  • Transient: Different Guid in every request and even within the same request.
  • Scoped: Same Guid within a request, different for a new request.
  • Singleton: Same Guid for all requests.

Choosing the Right DI Scope

Article content

Routing & Middleware

Routing in .NET Core defines how HTTP requests are mapped to controller actions. It helps navigate different API endpoints based on the URL pattern.

Types of Routing in .NET Core

  1. Attribute-based Routing (Recommended)
  2. Convention-based Routing

1. Attribute Routing

Uses attributes on controllers and actions to define routes.

Example:

[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
    [HttpGet] // Matches "GET api/products"
    public IActionResult GetAll() 
    {
        return Ok(new List<string> { "Laptop", "Phone" });
    }

    [HttpGet("{id}")] // Matches "GET api/products/{id}"
    public IActionResult GetById(int id) 
    {
        return Ok($"Product {id}");
    }

    [HttpPost] // Matches "POST api/products"
    public IActionResult AddProduct(string name) 
    {
        return Created("api/products", name);
    }
}
        

Advanced Attribute Routing

[HttpGet("category/{category}/page/{page}")]
public IActionResult GetByCategory(string category, int page)
{
    return Ok($"Category: {category}, Page: {page}");
}
        

Matches: GET api/products/category/laptops/page/2

2. Convention-based Routing

Defines routes globally inside Program.cs.

Example:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
        

Matches: /Home/Index or /Home/Index/5

2. Middleware in .NET Core

Middleware is a pipeline of components that handle HTTP requests and responses.

How Middleware Works

Each middleware processes requests before passing them to the next component.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseMiddleware<CustomMiddleware>(); // Custom middleware
app.UseHttpsRedirection();  // Redirects HTTP to HTTPS
app.UseAuthorization();  // Handles authentication
app.MapControllers();  // Maps API controllers

app.Run();
        

Common Built-in Middleware in .NET Core


Article content

Custom Middleware Example

To create a custom middleware, implement the InvokeAsync() method.

1. Create a Custom Middleware Class

public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        Console.WriteLine($"Request URL: {context.Request.Path}");
        await _next(context); // Call the next middleware
    }
}
        

2. Register Middleware in Program.cs

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseMiddleware<CustomMiddleware>(); // Register custom middleware
app.MapControllers();

app.Run();
        

Logs each request URL in the console before passing control to the next middleware.

API Versioning & Authentication

1. API Versioning

API versioning allows maintaining multiple versions of an API to ensure backward compatibility.

Why Use API Versioning?

  • Prevents breaking changes for existing users.
  • Allows gradual upgrades to new versions.
  • Helps manage long-term API maintenance.

Step 1: Install API Versioning Package

Run the following command:

dotnet add package Microsoft.AspNetCore.Mvc.Versioning        

Step 2: Configure API Versioning in Program.cs

Add API versioning support in the DI container:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddApiVersioning(options =>
{
    options.ReportApiVersions = true; // Adds API version headers in responses
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0); // Default API version
});        

Step 3: Implement API Versioning in Controllers

There are three ways to specify API versions:

1. URL-based Versioning

[ApiController]
[Route("api/v{version:apiVersion}/products")]
[ApiVersion("1.0")]
public class ProductsV1Controller : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Ok(new string[] { "Laptop", "Phone" });
}        

Access via: GET /api/v1/products

2. Query String Versioning

[ApiController]
[Route("api/products")]
[ApiVersion("2.0")]
public class ProductsV2Controller : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Ok(new string[] { "Tablet", "Smartwatch" });
}        

Access via: GET /api/products?api-version=2.0

3. Header-based Versioning

Add versioning using HTTP headers.

options.ApiVersionReader = new HeaderApiVersionReader("X-API-Version");        

Access via:

GET /api/products  
Headers: X-API-Version: 2.0        

Step 4: Test API Versioning

Now, you can call different API versions using URLs, query strings, or headers.

2. Authentication

Authentication ensures that only authorized users can access protected resources.

Common Authentication Methods:

  • JWT (JSON Web Token) Authentication (Most used for Web APIs)
  • OAuth 2.0 with IdentityServer
  • Cookie-based Authentication (For web applications)
  • Basic Authentication (Less secure)

Implementing JWT Authentication in .NET Core

Step 1: Install Required Packages

Run:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer        

Step 2: Configure JWT Authentication in Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "https://yourdomain.com",
            ValidAudience = "https://yourdomain.com",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("YourSecretKeyHere"))
        };
    });

builder.Services.AddAuthorization();

var app = builder.Build();

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

Step 3: Protect API Endpoints with [Authorize]

[ApiController]
[Route("api/secure")]
public class SecureController : ControllerBase
{
    [HttpGet]
    [Authorize]  // Only authenticated users can access this
    public IActionResult GetSecureData()
    {
        return Ok("This is a protected API endpoint!");
    }
}        

Step 4: Generate a JWT Token for Users

Create an endpoint to generate JWT tokens.

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    [HttpPost("login")]
    public IActionResult Login(string username, string password)
    {
        if (username == "admin" && password == "password")
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes("YourSecretKeyHere");

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username) }),
                Expires = DateTime.UtcNow.AddHours(1),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };

            var token = tokenHandler.CreateToken(tokenDescriptor);
            return Ok(new { token = tokenHandler.WriteToken(token) });
        }
        return Unauthorized();
    }
}        

Step 5: Test Authentication

  1. Login to get a JWT token:

POST /api/auth/login
Content-Type: application/json
{
    "username": "admin",
    "password": "password"
}        

Response:

{ "token": "eyJhbGciOi..." }        

2. Access a protected API with the token:

GET /api/secure
Authorization: Bearer eyJhbGciOi...        

Swagger UI Integration

Swagger (OpenAPI) is a specification for documenting APIs.

It provides:

  • Interactive API documentation
  • Testing endpoints without a separate tool
  • Generating client SDKs for APIs

Step 1: Install Swagger Package

Run the following command in your terminal or package manager console:

dotnet add package Swashbuckle.AspNetCore        

Step 2: Configure Swagger in Program.cs

Modify Program.cs to register Swagger services:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

// Add Swagger services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Enable Swagger in development mode
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

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

Step 3: Run the Application

Start your application and navigate to:

https://localhost:<port>/swagger        

You will see an interactive Swagger UI listing all API endpoints.

Step 4: Customize Swagger UI

Modify Swagger configuration to include metadata like API title, description, and version:

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "My API",
        Version = "v1",
        Description = "A simple ASP.NET Core Web API with Swagger",
        Contact = new OpenApiContact
        {
            Name = "Your Name",
            Email = "your@email.com",
            Url = new Uri("https://yourwebsite.com")
        }
    });
});        

Now, Swagger UI will display detailed API information.

Step 5: Enable Authorization in Swagger UI

If your API uses JWT authentication, modify SwaggerGen to include a security definition:

using Microsoft.OpenApi.Models;

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "My API",
        Version = "v1"
    });

    // Add JWT Authentication
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Description = "Enter 'Bearer {token}'"
    });

    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});        

Now, Swagger UI will have an "Authorize" button to enter a JWT token for protected APIs.

Step 6: Test Swagger UI

  1. Run the application (dotnet run).
  2. Open Swagger UI at: https://localhost:<port>/swagger
  3. Try calling APIs interactively.

Conclusion:

This article covered the essential elements of building a .NET Core Web API, including routing, middleware, dependency injection, authentication, API versioning, and Swagger integration. Each component plays a crucial role in creating scalable, secure, and maintainable APIs.

By following these principles, developers can:

  • Structure APIs efficiently using routing and controllers.
  • Secure APIs with JWT authentication and authorization.
  • Ensure backward compatibility using API versioning.
  • Simplify API testing & documentation with Swagger UI.

With these foundations, you can confidently develop robust RESTful APIs in .NET Core.



Muhammad Uzair

Asp.Net Core 8 | Angular | .Net Web API | PHP | Laravel C,C+,C# | Backend Architect | Team Lead | Delivering Tailored Business Solutions Across Multiple Industries

4mo

very detailed and easily understable. Thank You for sharing

Yash Reddy

Sharing Ad-Hoc startup Anecdotes. 💥

4mo

Great insights on .NET Web API Core! Looking forward to learning more.

Clear and easily understandable... Thank you for sharing...

Detailed and well written.

Quick information thank you for sharing Ramu Kunja

To view or add a comment, sign in

Others also viewed

Explore topics