ASP.NET Core Distributed Tracing with OpenTelemetry

October 21, 2025 · 7 min

In modern microservice architectures, a single user request can trigger a cascade of calls across numerous services. When something goes wrong, pinpointing the source of failure or a performance bottleneck becomes a significant challenge. This is where distributed tracing comes in, and OpenTelemetry has emerged as the industry standard for implementing it.

This post will guide you through integrating OpenTelemetry for distributed tracing into your ASP.NET Core applications. We’ll cover the core concepts, set up a basic project, and visualize traces in a backend like Jaeger.

What is Distributed Tracing?

Distributed tracing allows you to follow a single request’s journey from start to finish as it moves through different services. It stitches together individual operations into a unified view, helping you understand the flow, identify latency issues, and debug errors effectively.

Key concepts include:

  • Trace: Represents the entire end-to-end path of a request. A trace is a collection of spans.
  • Span: Represents a single unit of work or operation within a trace, like an HTTP call or a database query. Each span has a start time, duration, and associated metadata (tags).
  • Trace Context: A set of unique identifiers (like a TraceId and SpanId) that are passed between services with each request. This context is what allows the system to link all related spans into a single trace.

Think of it like tracking a package. The trace is the entire journey from the warehouse to …

...

Read more

Trigger Background Jobs from ASP.NET Core Middleware

October 20, 2025 · 7 min

ASP.NET Core middleware is a powerful component of the request pipeline, designed to handle HTTP requests and responses efficiently. But what happens when a request needs to kick off a task that shouldn’t block the response? Think of tasks like sending a confirmation email, generating a detailed audit log, or calling a slow third-party service. Running these directly within the middleware can severely impact your application’s performance and responsiveness.

The solution is to decouple these long-running operations from the request pipeline by offloading them to a background service. In this post, we’ll walk through the robust and recommended way to trigger background jobs from your middleware using IHostedService and an in-memory queue.

The Problem: Blocking the Pipeline

The request pipeline in ASP.NET Core is synchronous at its core, even with async/await. Each middleware component must complete its work before passing control to the next one. If your middleware performs a long-running operation, the client is left waiting until that task finishes.

For example, imagine a piece of middleware that logs detailed request information to a slow, remote data store:

// DO NOT DO THIS
app.Use(async (context, next) =>
{
    // This blocks the pipeline until the log is written
    await LogRequestDetailsToSlowStoreAsync(context.Request); 

    await next(context);
});

This approach creates a bottleneck. As traffic increases, requests will pile up waiting for these …

...

Read more

ASP.NET Core Middleware for API Audit Logging

October 19, 2025 · 7 min

In modern application development, especially with APIs, knowing what’s happening under the hood isn’t just a “nice-to-have,” it’s a necessity. Audit logging provides a detailed record of every significant action, which is invaluable for security, compliance (like GDPR or HIPAA), and debugging complex issues.

While you could add logging calls in every controller action, that approach is repetitive and error-prone. A much cleaner, more powerful solution is to use ASP.NET Core’s middleware. In this post, we’ll build a piece of custom middleware from scratch to create a robust audit logging system for any API.

What is Middleware, Really?

Think of the ASP.NET Core request pipeline as an assembly line. When an HTTP request comes in, it passes through a series of components, or “middleware,” before it reaches your API controller. Each piece of middleware has a chance to inspect the request, modify it, or even short-circuit it. After the controller generates a response, it travels back down the same line.

This structure makes middleware the perfect place for cross-cutting concerns like authentication, caching, exception handling, and, of course, logging.

Designing Our Audit Logging Middleware

Before writing code, let’s define what information we want to capture for each API call. A good audit log should be comprehensive.

Key Data Points to Log:

  • Request Info: HTTP Method, Path, Query String, Headers, and the Request Body. …
...

Read more

Consistent API Errors with ProblemDetails in ASP.NET Core

October 18, 2025 · 6 min

In the world of API development, consistency is king. One of the most common areas where consistency breaks down is error handling. Different endpoints might return different JSON structures for a 404 Not Found versus a 500 Internal Server Error, forcing API consumers to write complex, brittle parsing logic.

ASP.NET Core provides a clean, built-in solution to this problem: the ProblemDetails middleware. By adopting this standard, you can ensure all your API’s error responses have a predictable, machine-readable shape.

What is Problem Details?

ProblemDetails is a specification defined in RFC 7807 that establishes a standard format for returning error information from an HTTP API. It’s essentially a contract for what an error response should look like.

A standard ProblemDetails object includes these key fields:

  • type: A URI that identifies the problem type. This can link to documentation explaining the error.
  • title: A short, human-readable summary of the problem.
  • status: The HTTP status code generated by the server for this occurrence of the problem.
  • detail: A human-readable explanation specific to this occurrence of the problem.
  • instance: A URI that identifies the specific occurrence of the problem.

Here’s an example of what a ProblemDetails response looks like in JSON:

{
  "type": "[https://tools.ietf.org/html/rfc7231#section-6.5.4](https://tools.ietf.org/html/rfc7231#section-6.5.4)",
  "title": "Not Found", …
...

Read more

Debug Multi-Tenant ASP.NET APIs with Custom Middleware

October 17, 2025 · 7 min

Multi-tenant architecture is a powerful way to serve multiple customers from a single, shared application instance. It’s efficient and scalable, but it introduces a significant challenge: when a bug occurs for just one tenant, how do you debug it without disrupting everyone else? Reproducing the issue can be a nightmare of sifting through logs and guessing at tenant-specific configurations.

This is where ASP.NET Core’s middleware pipeline becomes your secret weapon. By creating a small, targeted piece of custom middleware, you can build a powerful debugging toolkit that provides deep visibility into tenant-specific requests on demand. In this post, we’ll walk through how to build a middleware component that helps you zero in on those elusive, tenant-specific bugs.

The Unique Challenge of Debugging Multi-Tenant APIs

In a standard single-tenant application, if a bug exists, it usually affects all users in the same way. In a multi-tenant world, the landscape is far more complex:

  • Configuration Differences: Tenant A might have a feature flag enabled that Tenant B doesn’t.
  • Data Isolation: The exact data being processed for Tenant A could trigger an edge case that Tenant B’s data never will.
  • Custom Workflows: Some tenants might have unique integrations or settings that alter the application’s behavior.
  • Logging Noise: A centralized logging system can make it difficult to isolate the sequence of events for a single tenant’s problematic …
...

Read more

Dynamic Connection Strings for Multi-Tenant ASP.NET Apps

October 16, 2025 · 6 min

Building a multi-tenant SaaS application presents unique challenges, especially when it comes to data isolation. One of the most common and robust approaches is the “database-per-tenant” model, where each customer’s data resides in its own dedicated database. This ensures strong security and simplifies scaling.

The core problem, however, is telling your application which database to connect to for any given HTTP request. A static connection string in appsettings.json won’t work. We need a dynamic, request-aware mechanism. This is where ASP.NET Core middleware shines. By intercepting requests early, we can identify the tenant, retrieve their specific connection string, and configure services for the remainder of the request pipeline.

In this post, we’ll walk through building a middleware-based solution to manage tenant-aware connection strings in an ASP.NET Core application.

The Challenge with Static Configuration

In a typical single-tenant application, you register your DbContext like this:

// Program.cs - The traditional way
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));

This works because the connection string is known at startup and never changes. In a multi-tenant world, the connection string depends on who is making the request, a detail that is only available once the HTTP …

...

Read more

API Versioning in ASP.NET Core with Custom Middleware

October 15, 2025 · 7 min

As APIs evolve, the need to introduce breaking changes becomes inevitable. The key challenge is to roll out these updates without disrupting existing clients who depend on the current contract. This is where API versioning comes in. While feature-rich libraries like Asp.Versioning.Http are excellent, understanding how to build your own versioning scheme with middleware gives you maximum control and a deeper appreciation for the ASP.NET Core pipeline.

In this post, we’ll explore a clean, middleware-based approach to API versioning using a custom request header. We’ll build a solution that inspects a header and transparently rewrites the request path to route to the correct endpoint implementation.

Why a Middleware Approach?

Dedicated versioning libraries are powerful, offering features like API explorer integration, version advertisement, and conventions. However, a custom middleware solution offers its own set of advantages:

  • Full Control: You define the exact logic for how a version is resolved. You can read it from a header, a claim, a query string, or even a combination of factors.
  • Lightweight: You add only the logic you need, without any extra overhead.
  • Decoupled: The versioning logic lives in one place (the middleware) rather than being scattered across controllers with attributes. This can lead to cleaner endpoint definitions.
  • Great for Learning: Building it yourself is a fantastic way to understand how the ASP.NET Core request pipeline and routing work …
...

Read more

ASP.NET Core Custom Request Validation Middleware

October 14, 2025 · 7 min

In modern ASP.NET Core development, validation is a critical, non-negotiable part of building robust APIs. The default approach often involves using data annotations and checking ModelState within controller actions or using filters. While this works, it can lead to scattered validation logic and boilerplate code, especially in larger applications.

A more powerful and centralized approach is to use custom middleware for request validation. This strategy intercepts requests early in the pipeline, validates them, and rejects them before they even reach your API endpoints. This not only cleans up your endpoint logic but also creates a single, consistent validation gate for your entire application.

In this post, we’ll build a custom validation middleware from scratch, integrating the popular FluentValidation library to create a clean, efficient, and scalable validation system that completely bypasses ModelState.

The Problem with Traditional Validation

The standard [ApiController] attribute in ASP.NET Core automatically triggers model validation and returns a 400 Bad Request if ModelState is invalid. This is convenient but has a few drawbacks:

  • Coupling: The validation logic is tightly coupled to the MVC/API Controller framework. It’s less straightforward to apply the same automatic behavior consistently in Minimal APIs without extra boilerplate.
  • Scattered Logic: Validation rules defined via attributes can become unwieldy and are spread across numerous DTOs.
  • Repetitive …
...

Read more

Dynamic Feature Toggles in ASP.NET Core Middleware

October 13, 2025 · 6 min

Deploying new features can be stressful. What if a bug slips through? What if you want to release a feature only to internal testers first? The traditional “deploy to release” model couples your deployment schedule directly with your feature launch, creating a high-stakes event. Feature flags, also known as feature toggles, offer a better way.

Feature flags allow you to decouple deployment from release. You can deploy new, unfinished, or experimental features to production but keep them “turned off.” This lets you enable them on-the-fly for specific users or a percentage of your user base, or quickly disable a feature that’s causing problems—all without a single redeployment.

In this post, we’ll explore how to implement feature flags in an ASP.NET Core application using the Microsoft.FeatureManagement library and custom middleware to control access to entire endpoints dynamically.

What Are Feature Flags?

At its core, a feature flag is a decision point in your code that can be toggled via configuration. Think of it as a dynamic if statement that you can control from outside your application’s code.

The benefits are significant:

  • Test in Production Safely: Deploy features to production and enable them only for your QA team.
  • Canary Releases & A/B Testing: Gradually roll out a new feature to a small percentage of users to monitor its performance and impact.
  • Kill Switch: Instantly disable a faulty feature in production if it’s …
...

Read more

ASP.NET Core Cache: Middleware vs. Controller Showdown

October 12, 2025 · 8 min

Caching is one of the most effective strategies for boosting your ASP.NET Core application’s performance. By storing and reusing frequently accessed data, you can significantly reduce server load, decrease latency, and create a snappier user experience. But when it comes to implementation, ASP.NET Core offers a couple of primary approaches: caching at the middleware level and caching at the controller level.

Choosing the right strategy depends on your specific needs. Do you need a broad, application-wide caching rule, or do you require precise control over individual endpoints? Let’s break down the differences to help you make an informed decision.

Understanding the Core Concepts

Before we compare, it’s crucial to understand the two main players in this context: Response Caching Middleware and the [ResponseCache] attribute.

  • Response Caching Middleware: This is a component you register in your application’s request processing pipeline. It inspects incoming requests and outgoing responses to decide whether to serve a response from its cache or to cache a new response based on HTTP headers. It’s the engine that performs the actual server-side caching.
  • [ResponseCache] Attribute: This is a filter attribute you apply to your controller actions or globally. Its primary job is to set the appropriate HTTP Cache-Control header on the response. These headers instruct clients (like browsers) and intermediate proxies on how they should cache the response.

A …

...

Read more

Stream Responses in ASP.NET Core with Middleware

October 11, 2025 · 6 min

When building APIs that handle large datasets, memory consumption can quickly become a critical bottleneck. The default behavior in ASP.NET Core is to buffer the entire response in memory before sending it to the client. For a small JSON object, this is fine. For a 500 MB CSV export, it’s a recipe for disaster, potentially leading to high memory usage and even OutOfMemoryException.

The solution is response streaming. Instead of building the entire payload at once, we write it to the client in chunks as it’s generated. This drastically reduces the server’s memory footprint and improves the time to first byte (TTFB), making your application feel more responsive.

In this post, we’ll explore how to build a clean, reusable pattern for enabling response streaming using custom ASP.NET Core middleware.


The Problem with Response Buffering

Let’s visualize the default behavior. Imagine an endpoint that generates a large report:

  1. A request comes in for /api/reports/annual-sales.
  2. Your application logic queries a database and processes a massive amount of data.
  3. It serializes this data into a single, large string or byte array in memory. Let’s say it’s 200 MB.
  4. Only after the entire 200 MB payload is ready does ASP.NET Core begin sending the response to the client.

If 10 users request this report simultaneously, your server’s memory usage spikes by 2 GB just for these responses. This doesn’t scale well. Streaming sends the data piece by …

...

Read more

Inject Request Data into Services via ASP.NET Core Middleware

October 10, 2025 · 6 min

A common challenge in building complex ASP.NET Core applications, especially multi-tenant systems, is accessing request-specific data from deep within your service layer. How does a data repository or a business logic service know the current user’s ID, their permissions, or the tenant they belong to?

A naive approach is “prop-drilling”: passing the HttpContext or specific data points down through every method call. This clutters your method signatures and creates a maintenance nightmare. A slightly better approach might involve injecting IHttpContextAccessor, but this tightly couples your services to the HTTP infrastructure, making them difficult to test.

There is a cleaner, more powerful pattern: using middleware to populate a scoped service. This approach decouples your application logic from the web layer, improves testability, and keeps your code clean.


The Core Concept: Scoped Services as Request State

Dependency Injection in ASP.NET Core has three main lifetimes:

  • Singleton: One instance for the entire application lifetime.
  • Transient: A new instance is created every time it’s requested.
  • Scoped: A single instance is created for each client request (scope).

The scoped lifetime is the key to our solution. We can create a service, register it as scoped, and treat it as a container for all the data relevant to the current HTTP request. A middleware component, which runs at the start of the request, will be responsible for populating this container. …

...

Read more

ASP.NET Core Logging with Correlation IDs via Middleware

October 9, 2025 · 6 min

In modern distributed systems, a single user action can trigger a cascade of requests across multiple microservices. When something goes wrong, tracing that single action through a sea of log entries can feel like finding a needle in a haystack. This is where correlation IDs become an indispensable tool for observability.

A correlation ID is a unique identifier that follows a request from start to finish, even as it hops between services. By including this ID in every log message related to that request, you can easily filter and piece together the entire transaction.

The most effective way to implement this in ASP.NET Core is with custom middleware. Let’s dive into how to build it.


Why Middleware is the Perfect Solution

The ASP.NET Core request pipeline is a series of components, or middleware, that process an HTTP request. By creating our own middleware and placing it early in the pipeline, we can ensure that:

  1. Every request is intercepted: No request gets processed without a correlation ID.
  2. Logic is centralized: We avoid cluttering our controllers or services with repetitive logging code.
  3. It’s set up once: Once the middleware is registered, the correlation ID is available for the entire scope of the request.

This approach is clean, efficient, and aligns perfectly with the ASP.NET Core design philosophy.

Building the Correlation ID Middleware

First, we’ll create the middleware component itself. This class will inspect incoming request headers for an …

...

Read more

Authentication vs Authorization in ASP.NET Core Middleware

October 7, 2025 · 6 min

In the world of web application development, particularly when building secure and robust APIs with ASP.NET Core, the terms authentication and authorization are often used interchangeably. However, they represent two distinct and crucial security concepts. Understanding their differences and how they are implemented within the ASP.NET Core middleware pipeline is fundamental for any developer serious about security.

This article will demystify authentication and authorization, explore their implementation in ASP.NET Core, and highlight where middleware fits into this security puzzle.

Authentication vs. Authorization: A Clear Distinction

At its core, the difference between authentication and authorization is straightforward:

  • Authentication is about identity. It’s the process of verifying that a user is who they claim to be. This is typically achieved by validating credentials like a username and password, a biometric scan, or a security token. In essence, authentication answers the question: “Who are you?”

  • Authorization is about permissions. Once a user’s identity has been authenticated, authorization determines what actions they are allowed to perform. It’s the process of granting or denying access to specific resources or functionalities. Authorization answers the question: “What are you allowed to do?”

Think of it like attending a conference. When you arrive, you show your ticket and a photo ID to the registration desk. This is …

...

Read more

ASP.NET Middleware Order Pitfalls That Break APIs

October 6, 2025 · 7 min

In ASP.NET Core, middleware components are the building blocks of your application’s request pipeline. Think of them as a series of checkpoints on an assembly line. Each request passes through these checkpoints in a specific order, and each one has a chance to inspect, modify, or even short-circuit the request.

While this architecture is incredibly powerful, it has a critical dependency: order matters. A lot. Arranging your middleware in the wrong sequence can lead to security vulnerabilities, broken functionality, and hours of frustrating debugging. Let’s dive into the most common ordering pitfalls that can silently break your APIs and how to fix them.

The Request Pipeline: A Two-Way Street

Before we get to the pitfalls, it’s essential to understand how the pipeline works. When an HTTP request arrives, it travels “down” the pipeline, passing through each middleware component in the order they were registered in Program.cs.

Once the request hits the end (usually your API endpoint), the response is generated and travels back “up” the pipeline in the reverse order. This two-way flow is why some middleware can act on both the incoming request and the outgoing response.

The golden rule is simple: Middleware registered earlier runs earlier on the request and later on the response.

Common Middleware Ordering Pitfalls

Let’s look at some classic mistakes. We’ll use the modern minimal API style with top-level statements for our …

...

Read more

Per-Request Tenant Resolution in ASP.NET Core Middleware

October 5, 2025 · 6 min

Building multi-tenant applications, a common pattern for Software as a Service (SaaS) products, introduces a fundamental challenge: how do you know which tenant is making the current request? Every incoming call to your API or web app must be correctly associated with a specific tenant to ensure data isolation and deliver a customized experience. The answer lies in a clean, powerful pattern: per-request tenant resolution using custom middleware.

In this guide, we’ll walk through creating a robust tenant resolution strategy in ASP.NET Core. We’ll build custom middleware that identifies the tenant from the incoming request and makes that information available to the rest of the application through dependency injection.


The Goal: What is Tenant Resolution?

At its core, tenant resolution is the process of inspecting an incoming HttpContext to extract a unique tenant identifier. Once you have this identifier, you can use it to look up the tenant’s details (like their database connection string, theme information, or feature flags) and load them into a request-scoped context.

There are several common strategies for identifying a tenant:

  • Hostname/Subdomain: tenant1.yourapp.com, tenant2.yourapp.com
  • URL Path: yourapp.com/tenants/tenant1/
  • HTTP Header: X-Tenant-Id: tenant1
  • Query String: yourapp.com/products?tenantId=tenant1

The beauty of the middleware approach is that it centralizes this logic, making your application cleaner and easier to maintain, regardless of …

...

Read more

ASP.NET Core Centralized Security Headers Middleware Guide

October 4, 2025 · 6 min

In modern web development, security isn’t an afterthought; it’s a foundational requirement. One of the most effective and straightforward ways to harden your ASP.NET Core application is by using HTTP security headers. These headers instruct the browser on how to behave, mitigating common attacks like Cross-Site Scripting (XSS), clickjacking, and protocol downgrade attacks.

While you can add these headers in various places, the most robust and maintainable approach is to create a single, centralized middleware. This ensures every response from your application is consistently protected.

In this post, we’ll build a configurable security headers middleware from scratch that manages Content-Security-Policy (CSP), HTTP Strict-Transport-Security (HSTS), and other essential headers.


Why a Centralized Middleware?

You might be tempted to sprinkle Response.Headers.Add(...) in your controllers or use separate app.Use...() calls for each header in Program.cs. However, a centralized approach offers significant advantages:

  • Consistency: Every single endpoint gets the same baseline protection without fail. You eliminate the risk of forgetting to secure a new API or page.
  • Single Point of Configuration: All your security header policies live in one place. Need to tighten your CSP? You only have one file to edit.
  • Maintainability: As security standards evolve, updating your policies is trivial. You don’t have to hunt down configurations scattered across the project. …
...

Read more

Dynamic CORS in ASP.NET Core for Multi-Tenant Apps

October 3, 2025 · 7 min

In modern web development, Cross-Origin Resource Sharing (CORS) is a fundamental security mechanism. For standard, single-client applications, configuring a CORS policy in ASP.NET Core is straightforward: you define a set of allowed origins in your Program.cs. But what happens when you’re building a multi-tenant application where each tenant needs a different set of allowed origins?

A static, hardcoded list of origins quickly becomes a bottleneck. Adding a new tenant or updating a tenant’s domain would require a code change and a redeployment. This is not scalable or secure. The solution is to create a dynamic, per-tenant CORS policy that resolves the correct origins at runtime.

In this post, we’ll build a custom ASP.NET Core middleware to achieve exactly that. We’ll create a flexible system that looks up a tenant’s specific CORS configuration on the fly for each incoming request.

The Problem with Static CORS in Multi-Tenant Architectures

Let’s quickly review the standard approach. In a typical Program.cs, you might see this:

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy.WithOrigins("[https://client-app-one.com](https://client-app-one.com)", "[https://client-app-two.com](https://client-app-two.com)")
              .AllowAnyHeader()
              .AllowAnyMethod();
    });
});

// ... in the pipeline configuration
app.UseCors();

This works perfectly for a predictable …

...

Read more

Writing a Minimal Custom Middleware in ASP.NET Core

October 1, 2025 · 5 min

ASP.NET Core’s request pipeline is a powerful concept built entirely around middleware. Think of it as an assembly line for your HTTP requests. Each station on the line is a piece of middleware that can inspect, modify, or act upon the request before passing it to the next station.

While ASP.NET Core provides a rich set of built-in middleware for things like routing, authentication, and static files, there will inevitably come a time when you need to create your own. Whether it’s for custom logging, header manipulation, or a unique authentication scheme, writing custom middleware is a fundamental skill.

In this guide, we’ll break down the process into its simplest form, showing you two minimal ways to create and use your own middleware.


Approach 1: The Convention-Based Class

The most common and structured way to create middleware is by defining a class that follows a specific convention. This approach is clean, reusable, and testable.

A middleware class needs two things:

  1. A constructor that accepts a RequestDelegate parameter. This delegate represents the next piece of middleware in the pipeline.
  2. A public method named InvokeAsync (or Invoke) that accepts an HttpContext as its first parameter. This is the method that gets executed.

Let’s create a simple middleware that logs the incoming request path and the outgoing response status code.

Step 1: Create the Middleware Class

Create a new file named SimpleLoggingMiddleware.cs. We’ll use a primary …

...

Read more