Cloud Quality Cloud Pricing

Mastering ASP.NET Core 8 Minimal APIs — 10 Questions Every Web API Developer Asks

1) How do I use Postman with ASP.NET Core Minimal API for testing?

Concept — why you should start with Postman

Postman is the fastest human feedback loop for HTTP APIs. Before you write integration tests or wire up a client, reproduce requests manually: confirm route shapes, input JSON, headers (especially Content-Type and Authorization), and inspect response status and body. Postman isolates the network layer so you can know whether the problem is in the server or the client.

Before the code — how to think about test cases

  • Happy path: valid request → 200/201 and expected JSON.
  • Validation paths: missing/invalid fields → 400 with clear message.
  • Auth paths: missing/invalid token → 401 or 403.
  • Error paths: unexpected server error → 500 with logs (don’t leak sensitive data).
    Save these as Postman requests in a Collection and use Environments (e.g., {{baseUrl}}) so switching between dev/staging is a click.

Code — Minimal API endpoints (example)

// Program.cs (minimal API example)
// — This demonstrates two endpoints you will call from Postman.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer(); // needed for Swagger later
// imagine ApplicationDbContext registered elsewhere (EF Core)
var app = builder.Build();

// GET /api/products
app.MapGet("/api/products", async (ApplicationDbContext db) =>
{
    // Fetch items from DB and return 200 + JSON array
    var products = await db.Products.ToListAsync();
    return Results.Ok(products);
});

// POST /api/products
app.MapPost("/api/products", async (ProductDto dto, ApplicationDbContext db) =>
{
    // Model binding will deserialize JSON body into ProductDto
    // Basic server-side validation:
    if (string.IsNullOrWhiteSpace(dto.Name))
        return Results.BadRequest(new { message = "Name is required" });

    var product = new Product { Name = dto.Name, Price = dto.Price };
    db.Products.Add(product);
    await db.SaveChangesAsync();

    // Return 201 Created with location header
    return Results.Created($"/api/products/{product.Id}", product);
});

app.Run();

Code comments inside explain what each piece does: model binding, validation, DB add/save, response codes.

After the code — what you achieved and how to test in Postman

  • Start the API: dotnet run (note the printed URLs).
  • In Postman, create two requests:
    • GET {{baseUrl}}/api/products → Expect 200 OK and JSON array.
    • POST {{baseUrl}}/api/products → Body (raw JSON):
json 

{ "name": "Coffee Mug", "price": 6.99 }

Expect 201 Created and returned JSON with Id.

Pitfalls / tips

  • Ensure Content-Type: application/json for POSTs.
  • If you hit SSL/TLS errors during dev, see the HTTPS section below.
  • Use Postman Collection Runner or Newman for automated smoke tests in CI.

Resources

2) How do I consume a Web API in ASP.NET Core?

Concept — use IHttpClientFactory

HttpClient is the client-side primitive to make HTTP calls. Instantiating raw new HttpClient() repeatedly is dangerous because of handler/socket lifetimes (socket exhaustion). IHttpClientFactory was introduced to centralize HttpClient creation, manage handler pooling, and provide configuration per named/typed client.

Before the code — design choices

  • Named vs typed client: use named for simple cases (multiple configuration sets), typed when you want a dedicated wrapper class and DI support.
  • Resilience: Add retry/circuit-breaker/timeouts with Polly.
  • Async & cancellation: Always await and accept CancellationToken where appropriate.

Code — typed client example with comments

// Define a typed client to consume the external API
public class ProductsClient
{
    private readonly HttpClient _http;
    public ProductsClient(HttpClient http) => _http = http;

    // Fetch products list
    public Task<List<Product>?> GetProductsAsync(CancellationToken ct = default) =>
        _http.GetFromJsonAsync<List<Product>>("products", ct);

    // Create product
    public Task<HttpResponseMessage> CreateProductAsync(CreateProductDto dto, CancellationToken ct = default) =>
        _http.PostAsJsonAsync("products", dto, ct);
}

// Program.cs registration and endpoint usage
var builder = WebApplication.CreateBuilder(args);

// Register typed client with a base address
builder.Services.AddHttpClient<ProductsClient>(client =>
{
    client.BaseAddress = new Uri("https://api.thirdparty.com/");
    client.Timeout = TimeSpan.FromSeconds(10); // sensible default
});

var app = builder.Build();

// Use typed client in a minimal API handler
app.MapGet("/proxy/products", async (ProductsClient productsClient, CancellationToken ct) =>
{
    var products = await productsClient.GetProductsAsync(ct);
    return Results.Ok(products);
});

What the code does (inline comments):

  • Registers ProductsClient so DI provides a configured HttpClient.
  • GetFromJsonAsync deserializes JSON directly to List<Product>.
  • PostAsJsonAsync handles JSON serialization of DTOs automatically.

After the code — what you achieved

  • Your application can call external APIs cleanly and testably.
  • ProductsClient encapsulates API-specific behavior (base URL, headers) and makes code easier to mock in tests.
  • You avoided new HttpClient() pitfalls and made it easier to add resilience (Polly policies later).

Pitfalls / tips

  • Watch for DNS changes — typed clients via IHttpClientFactory handle handler lifetime correctly.
  • Use HttpClient.BaseAddress for cleaner relative URI calls.
  • Add logging for request/response cycles when debugging.

Resources

3) How do I disable HTTPS in ASP.NET Core Web API (for local dev)?

Concept — Only for local convenience

HTTPS protects data in transit. Disabling it is fine on your local machine for development convenience (e.g., mobile emulator issues), but never in staging/production.

Two things control HTTPS locally:

  1. Kestrel/listen endpoints (launchSettings or --urls) — which hosts are bound.
  2. Middleware (app.UseHttpsRedirection()) — redirects HTTP requests to HTTPS.

Before the code — plan safe toggles

  • Use environment-specific launch profiles: one for Development (HTTP allowed) and one for Production (HTTPS required).
  • Never commit code that forcibly disables HTTPS globally.

Code — how to disable redirect (and run HTTP-only)

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

// For development debugging only: remove HTTPS redirect
// DO NOT do this in production.
#if DEBUG
// app.UseHttpsRedirection();  // comment out for local HTTP testing
#endif

app.MapGet("/", () => "Hello over HTTP");
app.Run();

Alternative: Edit Properties/launchSettings.json and remove the https://localhost:5001 entry from applicationUrl, leaving only http://localhost:5000. Or run:

bash

dotnet run --urls "http://localhost:5000"

After the code — what you achieved

  • You can test APIs with Postman or emulators that don’t like self-signed certs.
  • You reduced TLS friction for local debugging.

Pitfalls / tips

  • Reset settings before merging/deploying.
  • Prefer using developer certs (via dotnet dev-certs https –trust) if you want HTTPS locally without friction.
  • Some auth flows (social login, OAuth) require HTTPS; disabling may break them.

4) How do I integrate Swagger with ASP.NET Core Minimal API?

Concept — why Swagger matters

Swagger (OpenAPI) gives a machine-readable contract and an interactive UI for exploring endpoints. It reduces friction between backend and frontend teams and makes debugging and onboarding faster.

For Minimal APIs, AddEndpointsApiExplorer() is important so the generator knows about minimal endpoints.

Before the code — things to think about

  • Which environment(s) should expose Swagger? (Usually Development only.)
  • Will you show auth-protected docs? If yes, integrate auth into Swagger UI.
  • Add XML comments for richer documentation.

Code — basic Swashbuckle setup with comments

// Program.cs
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// Required for minimal APIs to be discovered
builder.Services.AddEndpointsApiExplorer();

// Register Swagger generator
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "My Minimal API", Version = "v1" });
    // Optional: include XML comments for richer method summaries
    // var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    // c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFile));
});

var app = builder.Build();

// Only enable in Development
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();       // exposes /swagger/v1/swagger.json
    app.UseSwaggerUI();     // interactive UI at /swagger
}

// Your endpoints...
app.MapGet("/hello", () => "Hello, Swagger!");
app.Run();

After the code — what you achieved

  • You have an interactive API contract at /swagger.
  • Frontend/dev teams can examine endpoints, try requests, and generate client code if needed.

Pitfalls / tips

  • Minimal endpoints won’t show up unless AddEndpointsApiExplorer() is called.
  • Protect Swagger in public apps or expose it internally only.
  • Use SwaggerGen options to add security definitions for JWT.

Resources

5) How do I call a Web API from another ASP.NET Core app using C#?

Concept — microservice-to-microservice calls

Calling another API from your server is like any HTTP client call, but in distributed systems you must consider: timeouts, retries, correlation IDs, authentication between services, and observability (tracing/metrics).

Before the code — design decisions

  • Use named or typed clients for clarity.
  • Have a correlation (request) ID header for tracing across services.
  • Use OAuth2 client credentials or mTLS for service-to-service security.

Code — named client with correlation header and cancellation

// Program.cs
builder.Services.AddHttpClient("ProductsApi", client =>
{
    client.BaseAddress = new Uri("https://products.internal/");
    client.Timeout = TimeSpan.FromSeconds(8);
});

app.MapGet("/aggregate/products", async (IHttpClientFactory httpFactory, HttpContext httpContext, CancellationToken ct) =>
{
    var client = httpFactory.CreateClient("ProductsApi");
    // Propagate correlation ID from incoming request, if present
    if (httpContext.Request.Headers.TryGetValue("X-Correlation-ID", out var corr))
        client.DefaultRequestHeaders.TryAddWithoutValidation("X-Correlation-ID", (string)corr);

    var resp = await client.GetAsync("api/v1/products", ct);
    resp.EnsureSuccessStatusCode();
    var list = await resp.Content.ReadFromJsonAsync<List<Product>>(cancellationToken: ct);
    return Results.Ok(list);
});

What the code achieves

  • Central configuration for outbound requests (base URL, timeout).
  • Propagates correlation ID to the downstream service for traceability.
  • Uses CancellationToken to cancel requests when the caller disconnects.

Pitfalls / tips

  • Use Polly policies for retries and circuit-breakers to avoid cascading failures.
  • Avoid synchronous blocking calls.

Resources

Polly + IHttpClientFactory patterns: https://github.com/App-vNext/Polly

6) How do I implement Redis cache in ASP.NET Core 8 Web API?

Concept — cache-aside and distributed cache

Distributed caching (Redis) lets multiple app instances share cache data. The cache-aside pattern is simple and robust: on a read, check cache first; on a miss, fetch from DB and set cache; on writes, invalidate or update the cache.

Before the code — operational decisions

  • Choose sensible TTLs (sliding vs absolute).
  • Decide cache key naming convention: include resource name and id, e.g., customers:{id}.
  • Consider cache stampede (thundering herd) mitigations (locks or randomized short TTLs).

Code — register and use IDistributedCache

// Program.cs
builder.Services.AddStackExchangeRedisCache(opts =>
{
    opts.Configuration = builder.Configuration["Redis:Connection"]; // e.g., "localhost:6379"
    opts.InstanceName = "myapp_";
});

app.MapGet("/customers/{id}", async (int id, IDistributedCache cache, ApplicationDbContext db) =>
{
    var key = $"customer:{id}";
    var cached = await cache.GetStringAsync(key);
    if (!string.IsNullOrEmpty(cached))
    {
        // Cache hit: return cached JSON directly
        return Results.Content(cached, "application/json");
    }

    // Cache miss: load from DB
    var customer = await db.Customers.FindAsync(id);
    if (customer == null) return Results.NotFound();

    var json = JsonSerializer.Serialize(customer);
    await cache.SetStringAsync(key, json, new DistributedCacheEntryOptions
    {
        AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
    });

    return Results.Content(json, "application/json");
});

After the code — what you achieved

  • Reduced DB hits for repeated reads. First request populates cache; subsequent requests get faster responses from Redis.
  • Cache TTL controls freshness; write operations must update/invalidate keys.

Pitfalls / tips

  • Monitor Redis memory; metric alerts for evictions.
  • Consider using IConnectionMultiplexer (StackExchange) for advanced patterns (pub/sub, locks).
  • For very hot writes, consider background cache rebuilds.

Resources

7) How do I secure my ASP.NET Core Web API (JWT)?

Concept — stateless auth with claims

JWTs (JSON Web Tokens) grant stateless authentication: tokens hold claims and are signed. The API validates the signature and claims; no DB lookup is required per request (unless you implement revocation).

Before the code — decisions

  • Who issues tokens? (Your Identity Server or third-party provider.)
  • Use short-lived access tokens and refresh tokens if needed.
  • Store signing keys in a secret store (Key Vault).

Code — JwtBearer registration and protect an endpoint

// Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidateAudience = true,
            ValidAudience = builder.Configuration["Jwt:Audience"],
            ValidateLifetime = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
            ValidateIssuerSigningKey = true
        };
    });

builder.Services.AddAuthorization();
var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/profile", [Authorize] (ClaimsPrincipal user) =>
{
    // This handler is accessible only with a valid JWT
    var name = user.Identity?.Name ?? "anonymous";
    return Results.Ok(new { name });
});

After the code — what you achieved

  • The /profile endpoint now demands a valid bearer token in Authorization: Bearer <token>.
  • The server verifies signature, issuer, audience, and expiration.

Pitfalls / tips

  • Allow a small clock skew when validating exp if your systems might have minor time differences.
  • Rotate signing keys carefully and support key rollover.
  • Consider token revocation strategies (short lifetime + refresh tokens, or a revocation list).

Resources

8) How do I call Web API with parameters in ASP.NET C#?

Concept — route vs query vs body

  • Route parameters are part of the path and are great for identifying a resource (e.g., /items/{id}).
  • Query parameters are optional modifiers (e.g., /search?q=term&page=2).
  • Body carries complex data (POST/PUT JSON).

Code — examples for sending and receiving

Client side (HttpClient):

// Route parameter
var item = await client.GetFromJsonAsync<Item>($"items/{id}");

// Query parameters safely
var url = QueryHelpers.AddQueryString("items", new Dictionary<string,string>{{"q","coffee"},{"page","2"}});
var page = await client.GetFromJsonAsync<PageResult<Item>>(url);

// Body parameter (POST)
var dto = new CreateItemDto { Name = "X", Price = 9.99M };
var resp = await client.PostAsJsonAsync("items", dto);

Server side (Minimal API binding):

app.MapGet("/items/{id}", (int id) => /* id from route */ );
app.MapGet("/search", (string q, int page = 1) => /* q from query */ );
app.MapPost("/items", (CreateItemDto dto) => /* dto from JSON body */ );

After the code — what you achieved

  • You can build strongly-typed handlers with explicit parameters; the framework infers binding sources.
  • Clients can send data via path/query/body depending on semantics.

Pitfalls / tips

  • Use FromHeader, FromQuery, FromRoute, FromBody attributes when you need explicit control.
  • Validate inputs and return 400 Bad Request for invalid values.

9) How do I insert data into a database using Web API in ASP.NET Core?

Concept — DTOs, validation, and DB context lifetimes

  • Use DTOs (not EF entities) for input to avoid over-posting.
  • Register DbContext with AddDbContext (scoped).
  • Use async DB operations and handle common exceptions (unique constraints).

Code — EF Core insert with validation and error handling

app.MapPost("/products", async (CreateProductDto dto, AppDb db) =>
{
    // Validate simple rules
    if (dto.Price < 0) return Results.BadRequest(new { message = "Price cannot be negative" });

    var product = new Product { Name = dto.Name, Price = dto.Price };
    db.Products.Add(product);

    try
    {
        await db.SaveChangesAsync(); // writes to DB and sets product.Id
    }
    catch (DbUpdateException ex)
    {
        // handle constraint violation or other DB issue
        return Results.Conflict(new { message = "Could not insert product", detail = ex.Message });
    }

    return Results.Created($"/products/{product.Id}", product);
});

After the code — what you achieved

  • You accept a client request, validate, persist to DB, and return 201 Created with a location header.
  • You handled DB errors and responded with meaningful HTTP codes.

Pitfalls / tips

  • If multiple writes are involved, use transactions (await db.Database.BeginTransactionAsync()), commit/rollback accordingly.
  • Use migrations (dotnet ef migrations add) for schema updates.
  • For large or heavy inserts, offload to a background worker and return an accepted job id (202 Accepted).

Resources

10) How do I configure Swagger in ASP.NET Web API (advanced)?

Concept — beyond “it works”

Customization makes Swagger useful: add metadata (title/description/version), include XML comments, add security schemes (JWT) so testers can enter tokens in the UI, and use filters to modify operations.

Code — advanced AddSwaggerGen config

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1", Description = "Detailed API for X" });
    // JWT bearer input in UI:
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT Authorization header using Bearer scheme. Example: 'Bearer {token}'",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.Http,
        Scheme = "bearer"
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement {
        {
            new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } },
            Array.Empty<string>()
        }
    });

    // Optionally add DocumentFilter / OperationFilter for tags or example responses
});

UI customization snippets

app.UseSwaggerUI(c =>
{
    c.RoutePrefix = "docs"; // serve at /docs
    c.DocumentTitle = "My API Docs";
    c.InjectStylesheet("/swagger-ui/custom.css"); // custom theme
    c.DefaultModelsExpandDepth(-1); // hide schema expansion by default
});

After the code — what you achieved

  • Useful, secure, and branded documentation.
  • Swagger UI supports entering a JWT so you can try authenticated endpoints.

Pitfalls / tips

  • Include XML docs for method summaries — valuable for clients.
  • Don’t leak sensitive endpoints or data in production (protect with auth or host internally).

Final Thoughts

You’ve read a lot. Great job. Here’s the pragmatic checklist to take away:

  • Use Postman to reproduce and isolate problems before writing automated tests.
  • Prefer IHttpClientFactory and typed clients for consuming APIs.
  • Never leave HTTPS disabled in non-dev environments — manage dev certs instead.
  • Add Swagger early, protect it as needed, and enrich it with XML comments.
  • Use Redis for distributed caching but plan invalidation and TTLs.
  • Use JWT for stateless auth — keep keys safe and lifetimes reasonable.
  • Use DTOs and validation for DB writes; handle exceptions and return appropriate HTTP codes.
  • Parameter binding in Minimal APIs is powerful — leverage it and be explicit when ambiguous.

You don’t need to implement everything at once. Pick one small surface to harden (auth or caching), add monitoring, and expand iteratively. If you’d like, I can:

  • Provide a GitHub-ready Minimal API starter repo with all these features wired up and documented.
  • Produce the post as a downloadable Word/PDF with diagrams and code samples formatted for publishing.
  • Generate the three enterprise-style images (hero + flow + security) with your brand colors and text.

Useful links (again)

Genadi Petkov

Genadi Petkov has been an active force in the web hosting industry since 2008. As CTO at FreshRoastedHosting.com for over a decade, and founder of TheHost.BG through his company IT Factory, he combines hands-on technical expertise with strategic insight. Genadi has also collaborated with Wall Street venture capital-backed hosting ventures, bringing innovative solutions to life. Loves breaking down technical challenges into clear, practical insights—and when he's offline, you might find him tinkering with new tech or dreaming up the next big hosting innovation.