Sitemap
Rajat Awasthi

Rajat Awasthi

Best Practices for High-Performance .NET Web APIs

9 min readSep 17, 2026

--

In today’s software development landscape, building high-performance APIs is essential for providing users with a fast, reliable, and smooth experience.
Whether you’re developing a brand-new web application or fine-tuning an existing one, this blog post will introduce effective strategies in .NET to boost your API performance.

We’ll explore 12 key techniques that can help you make your APIs faster, more scalable, and easier to maintain.

1. Asynchronous Programming with async/await

Asynchronous programming allows your application to avoid blocking threads while waiting for I/O tasks to finish.
These tasks can include reading files, fetching data from a database, calling external APIs, and more.

By leveraging async/await, .NET can handle more requests at the same time without tying up threads, which improves scalability.
Whenever possible, choose asynchronous methods instead of synchronous ones.

[HttpGet("orders")]
public async Task<IActionResult> GetOrdersAsync()
{
var orders = await _orderService.GetOrdersAsync();
return Ok(orders);
}

2. Improve Data Access Patterns for Read Queries (with EF Core or Dapper)

The way your application accesses data has a major effect on API performance. Two widely used libraries for database interaction are EF Core and Dapper.

To optimize data access, focus on improving your queries. This can include adding indexes to your database, fine-tuning query logic, and, when using EF Core, applying AsNoTracking for queries that only read data.

// Read-only query with AsNoTracking
public async Task<List<User>> GetUsersAsync()
{
return await _dbContext.Users
.AsNoTracking()
.Where(u => u.IsActive)
.ToListAsync();
}

If you are using Dapper, ensure parameterized queries, proper indexes, and minimal round trips to the database.

3. Reduce Data Transfer with Projections, Pagination and Filtering

Sending large amounts of data can slow down your API. Instead, return only what the client actually needs.

When fetching data from the database, consider using techniques like:

  • Projection — select only the specific fields you need instead of entire rows.
  • Filtering and sorting — reduce the data returned to relevant records.
  • Pagination — split large datasets into manageable chunks.

For example, with projection in EF Core, you can retrieve just the necessary fields:

var book = await context.Books
.Include(b => b.Author)
.Where(b => b.Id == id)
.Select(b => new BooksPreviewResponse
{
Title = b.Title,
Author = b.Author.Name,
Year = b.Year
})
.FirstOrDefaultAsync(cancellationToken);

To further improve read performance, optimize indexes and carefully design your filtering and sorting logic.

For large datasets, use pagination to avoid overloading the client:

[HttpGet("users")]
public async Task<IActionResult> GetUsersAsync(int pageNumber = 1, int pageSize = 10)
{
var query = _dbContext.Users.AsNoTracking().Where(u => u.IsActive);

var users = await query
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name
})
.ToListAsync();

return Ok(users);
}

This approach ensures your API returns only the necessary data efficiently, improving speed and scalability.

4. Minimize JSON Serialization Overhead

For JSON responses, the built-in System.Text.Json library is generally faster than Newtonsoft.Json.
You can further boost performance by configuring serialization settings to skip unnecessary fields.

Example configuration in .NET:

builder.Services
.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.IgnoreNullValues = true;
});
  • PropertyNamingPolicy = JsonNamingPolicy.CamelCase – ensures property names in JSON follow a consistent camelCase format.
  • IgnoreNullValues = true – omits properties with null values, reducing the size of the response.

While this is a small optimization, it can noticeably improve performance, especially for APIs that return large payloads.

5. Add Caching

Caching is one of the most effective ways to optimize API performance. By storing frequently requested data in memory or using a distributed cache like Redis, you can reduce the number of database queries.

There are two main caching approaches you can use in .NET:

  1. Output Caching
  2. Distributed/Hybrid Caching (e.g., IDistributedCache, Hybrid Cache in .NET 9, or third-party libraries like Fusion Cache)

Output Caching

Output caching saves the entire HTTP response for a specific duration, so subsequent requests can return the cached result without executing the controller or Minimal API method again.

Example setup:

builder.Services.AddOutputCache();

var app = builder.Build();
app.UseOutputCache();

[HttpGet("products")]
[OutputCache(Duration = 60)] // Cache for 60 seconds
public async Task<IActionResult> GetProductsAsync()
{
var products = await _productService.GetProductsAsync();
return Ok(products);
}
  • This approach is simple to implement and requires minimal code.
  • You can invalidate cached responses using tags.
  • Output caching also works with Redis.

Hybrid/Distributed Caching

For more granular control, use caching libraries like Hybrid Cache in .NET 9, which combines in-memory and distributed caching.
This approach helps prevent the cache stampede problem, where multiple requests trigger a database hit at the same time when a cache entry expires.

Example with Hybrid Cache:

builder.Services.AddHybridCache();

[HttpGet("orders/{id}")]
public async Task<IActionResult> GetOrderAsync(int id, [FromServices] IHybridCache cache)
{
string cacheKey = $"Order_{id}";
var order = await cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
using var context = new AppDbContext();
return await context.Orders.FindAsync(id);
});

if (order is null) return NotFound();
return Ok(order);
}
  • Use distributed caching like Redis when multiple applications need shared access or when you want the cache to survive application restarts.

6. Enable Response Compression

Compressing API responses using Brotli or Gzip can greatly reduce the size of the data sent to clients.
Smaller payloads lead to faster transfers and a smoother user experience.

Both Brotli and Gzip can compress JSON, HTML, or static files. By enabling compression early in the request pipeline, you ensure all outgoing responses are optimized.

Example configuration in .NET:

builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});

builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = System.IO.Compression.CompressionLevel.Fastest;
});

builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = System.IO.Compression.CompressionLevel.Fastest;
});

var app = builder.Build();
app.UseResponseCompression();
  • EnableForHttps = true – ensures compression works over secure connections.
  • Setting the CompressionLevel to Fastest prioritizes speed over maximum compression.

Using response compression is a simple but effective way to improve API performance and reduce bandwidth usage.

7. Optimize the Middleware Pipeline

The order in which middleware is executed can have a big impact on performance. Some middleware should run early in the pipeline, while others — such as routing, authentication, and authorization — should follow a logical sequence.

For example, if you’re using response compression, it should be added before serving static files so that the payloads are compressed correctly:

var app = builder.Build();

// Correct middleware sequence
app.UseResponseCompression(); // Run early for smaller payloads
app.UseStaticFiles(); // Serve static content

app.UseRouting();

app.UseAuthentication(); // Authenticate before authorizing
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});

Properly ordering middleware ensures your application runs efficiently and that features like compression, authentication, and routing work as intended.

8. Enable HTTP/2 and HTTP/3 Protocols

Both HTTP/2 and HTTP/3 offer major performance benefits over HTTP/1, such as multiplexing and reduced latency. Enabling these protocols in Kestrel is simple and can significantly improve your API’s speed and efficiency.

Get Rajat Awasthi’s stories in your inbox

Join Medium for free to get updates from this writer.

To configure Kestrel to support multiple protocols, add the following in Program.cs:

builder.WebHost.ConfigureKestrel(options =>
{
// HTTP/1.1 for standard connection
options.ListenAnyIP(5000);

// HTTPS with support for HTTP/1.1, HTTP/2, and HTTP/3
options.ListenAnyIP(5001, listenOptions =>
{
listenOptions.UseHttps();
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
});
});

var app = builder.Build();
// Continue with other configurations
  • Clients that support newer protocols (like HTTP/2 and HTTP/3) will automatically use them.
  • Older clients can still fall back to HTTP/1.1.

By enabling these newer protocols, you can improve connection efficiency and lower latency, offering a faster experience for your users.

9. Use Content Delivery Networks (CDNs)

A Content Delivery Network (CDN) is a network of geographically distributed servers that store and serve content closer to users, improving load times and reducing latency.

For example, if a user in Europe accesses a website hosted on a server in New York, the data has to travel a long distance, causing delays. With a CDN (like Cloudflare), the content is cached on servers closer to the user, such as in Europe, allowing for faster delivery without the need to query the original server.

By offloading static content delivery to a CDN, you reduce the strain on your own servers and boost global performance.

Key benefits of using a CDN include:

  • Fewer Round Trips: Content is served from edge servers closer to the user, which speeds up load times.
  • Reduced Server Load: Your application servers can focus on dynamic content and core API tasks, while the CDN handles static files.
  • Enhanced Security: Many CDNs offer additional features like protection against DDoS attacks, spam, and bot traffic, further improving the reliability and security of your application.

10. Implement Rate Limiting and Throttling

Rate limiting helps control the number of requests made to your API, preventing abuse and overloading. By restricting the number of requests a client can make within a specific time frame, you protect your API from excessive traffic and mitigate DDoS attacks.

Additionally, you can implement rate limiting based on user subscriptions, allowing different request quotas for free and premium users. This is similar to how ChatGPT limits usage based on user plans.

Here’s how you can set up rate limiting in ASP.NET Core using the built-in features:

builder.Services.AddRateLimiter(options =>
{
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, IPAddress>(context =>
{
var ipAddress = context.Connection.RemoteIpAddress;
return RateLimitPartition.GetFixedWindowLimiter(ipAddress,
_ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100, // Allow 100 requests per minute
Window = TimeSpan.FromMinutes(1), // Within a 1-minute window
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 0
});
});
});

var app = builder.Build();
app.UseRateLimiter();

This example sets a limit of 100 requests per minute per IP address using a Fixed Window Rate Limiter.

By implementing rate limiting, you can prevent abuse, ensure fair usage, and maintain optimal performance for all users.

11. Replace Controllers with Minimal APIs

Minimal APIs in .NET remove much of the overhead that comes with the traditional MVC pipeline, resulting in faster performance.

In .NET 9, Minimal APIs received significant performance improvements, processing 15% more requests per second than in .NET 8. They also use 93% less memory compared to previous versions.

Minimal APIs are lightweight, easy to write, and still support core features like dependency injection, filters, and other common ASP.NET Core capabilities.

Here’s an example of how to create a Minimal API endpoint:

var app = builder.Build();

// Minimal API endpoint
app.MapGet("/customers/{id}", async (int id) =>
{
var customer = await _dbContext.Customers.FindAsync(id);

return customer is not null
? Results.Ok(customer)
: Results.NotFound();
});

app.Run();

You can build everything from simple to complex functionality with Minimal APIs, just like with Controllers. Microsoft has been investing heavily in improving Minimal APIs with each new .NET release, making them the preferred choice for high-performance applications.

12. Use GraphQL

GraphQL can enhance performance by allowing clients to request exactly the data they need in a single query, rather than having to make multiple requests for different resources, as is the case with traditional REST APIs.

One of the most popular libraries for implementing GraphQL in .NET is Hot Chocolate.

Consider the following example models:

public record Product(
int Id,
string Name,
decimal Price,
bool IsAvailable
);

public record Review(
int Id,
int ProductId,
string Content,
int Rating
);

public record Recommendation(
int Id,
string Name,
decimal Price
);

Here’s an example of a GraphQL query:

public class Query
{
[UseOffsetPaging(MaxPageSize = 100, IncludeTotalCount = true)]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Product> GetProducts(AppDbContext context)
{
return context.Products;
}

public Task<Product?> GetProductByIdAsync(AppDbContext context, int id)
{
return await context.Products.FirstOrDefaultAsync(p => p.Id == id);
}

public async Task<Recommendation> GetProductRecommendationsByIdAsync(
AppDbContext context,
IRecommendationService service,
int productId,
int recommendationsCount)
{
var product = await context.Products.FindAsync(productId);
if (product is null)
{
return [];
}

var recommendations = await service.GetRecommendationsForProduct(product.Name, recommendationsCount)
.ToListAsync();

return recommendations;
}

public async Task<Review> GetReviewsByProductIdAsync(
AppDbContext context, int productId)
{
var reviews = await context.Reviews
.AsNoTracking()
.Where(r => r.ProductId == productId)
.ToListAsync();

return reviews;
}
}

In this example, the GetProducts method returns an IQueryable hooked into the GraphQL engine with built-in support for:

  • Filtering
  • Sorting
  • Pagination

Other methods are typical of Web API endpoints you’d use in Controllers or Minimal APIs.

Here’s an example of how you can use GraphQL in your client-side queries:

query {
getProducts(
where: { name: { contains: "Phone" }, price: { eq: 1000 } }
order: [{ name: ASC }]
skip: 10
take: 20
) {
id
name
price
isAvailable
}
}

With Hot Chocolate, you get full support for filtering, sorting, and pagination with minimal code — everything is handled automatically.

Where GraphQL really shines compared to REST APIs is in its ability to fetch multiple related pieces of data in a single request. For example:

query {
getProductById(id: 1) {
id
name
price
isAvailable

getReviewsByProductId(productId: 1) {
id
content
rating
}

getProductRecommendationsById(productId: 1) {
id
name
price
}
}
}

In this case, you’re retrieving the product, its reviews, and recommendations all in one HTTP request.

In contrast, with REST APIs, you’d need to send three separate requests or build your own API aggregator to gather the data together. GraphQL simplifies this by allowing you to request everything in one go.

Summary

To dramatically increase your API performance and enhance user experience, consider applying the following strategies:

  • Utilize async/await for non-blocking operations
  • Optimize database queries for faster data retrieval
  • Implement projections, pagination, and filtering to reduce payloads
  • Reduce JSON serialization overhead for quicker responses
  • Implement caching (e.g., Output Cache, In-memory, Redis) to minimize redundant data retrieval
  • Enable response compression to reduce data size
  • Order middleware efficiently to improve request processing
  • Enable HTTP/2 and HTTP/3 for faster data transmission
  • Serve static content via CDNs for quicker access from any location
  • Implement rate limiting and throttling to protect against overloads
  • Switch to Minimal APIs in .NET 9 for better performance
  • Integrate GraphQL for more efficient querying

Adopt these practices gradually, measure the performance gains with tools like OpenTelemetry, Benchmarks, or cloud services like Application Insights, and continue refining your .NET web applications for optimal results.

--

--