DEV Community

Adrián Bailador
Adrián Bailador

Posted on

Optimising ASP.NET Core APIs for Speed and Efficiency. Practical techniques to reduce latency, CPU usage, and memory allocations

Image description

Building a working API is one thing — building a fast and efficient API is another. As your application scales or handles more users, performance becomes a critical concern.

In this article, we’ll walk through practical techniques to optimise ASP.NET Core APIs for speed and efficiency, covering compression, caching, efficient serialisation, memory pressure, streaming, and monitoring.


🚀 1. Enable Response Compression

ASP.NET Core does not enable compression by default. Enabling it can drastically reduce payload sizes — especially for JSON-heavy responses.

📉 Example: a 45KB JSON payload can be reduced to 8KB with Gzip compression.

builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<GzipCompressionProvider>();
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/json" });
});

builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
    options.Level = CompressionLevel.Fastest;
});
Enter fullscreen mode Exit fullscreen mode


`

📖 Response compression in ASP.NET Core


🧠 2. Use Caching Strategically

Caching reduces redundant work and improves throughput dramatically.

🔸 a) Response Caching

Response caching stores entire responses and serves them directly, saving CPU cycles and database hits.

`csharp
builder.Services.AddResponseCaching();
app.UseResponseCaching();

[HttpGet]
[ResponseCache(Duration = 60)]
public IActionResult GetCachedData()
{
return Ok(new { message = "Cached response", time = DateTime.UtcNow });
}
`

🔸 b) In-Memory or Distributed Caching

Use IMemoryCache or IDistributedCache to cache database or computation-heavy results.

`csharp
public class MyService
{
private readonly IMemoryCache _cache;

public MyService(IMemoryCache cache) => _cache = cache;

public async Task<string> GetDataAsync()
{
    return await _cache.GetOrCreateAsync("data-key", entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
        return FetchFromDatabaseAsync();
    });
}
Enter fullscreen mode Exit fullscreen mode

}
`

📖 Caching in ASP.NET Core


🧩 3. Use Efficient JSON Serialisation

ASP.NET Core uses System.Text.Json by default — it's fast, but you can still tweak it for better results.

✅ Recommended options:

csharp
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});

⚡ Advanced: Source Generators

Improves serialisation speed and reduces allocations.

csharp
[JsonSerializable(typeof(MyResponseModel))]
internal partial class MyJsonContext : JsonSerializerContext { }

csharp
var json = JsonSerializer.Serialize(myData, MyJsonContext.Default.MyResponseModel);

📖 System.Text.Json overview


♻️ 4. Reduce Allocations and GC Pressure

Excessive allocations → frequent garbage collections → latency spikes. Use low-allocation patterns whenever possible.

✅ Tips:

  • Reuse buffers with ArrayPool<T>:

csharp
var buffer = ArrayPool<byte>.Shared.Rent(1024);
// use buffer...
ArrayPool<byte>.Shared.Return(buffer);

  • Prefer readonly struct when the object is immutable and passed by reference.

  • Avoid unnecessary .ToList(), .Select() and LINQ operations in hot paths.

  • Don’t return IEnumerable<T> if the data is already materialised.

📖 Performance best practices for .NET


🌊 5. Stream Data When Possible

When returning large datasets, avoid loading the full result into memory.

✅ Use IAsyncEnumerable<T>:

csharp
[HttpGet]
public async IAsyncEnumerable<MyItem> StreamItems()
{
await foreach (var item in _repository.GetItemsAsync())
{
yield return item;
}
}

This supports efficient streaming over HTTP/2, reducing memory pressure and improving time-to-first-byte.


⚙️ 6. Use Dependency Injection Efficiently

Registering your services properly avoids unnecessary instantiations and memory waste.

✅ Guidelines:

  • Singleton: for stateless and thread-safe services.
  • Scoped: for per-request lifetime.
  • Avoid injecting large services if only used conditionally.
  • Consider lazy loading via Lazy<T> or factory methods.

csharp
services.AddSingleton<HttpClient>();
services.AddScoped<IMyService, MyService>();

📖 Dependency injection in ASP.NET Core


📈 7. Measure and Monitor

Don't optimise blindly. Always profile first.

✅ Tools:


✅ Performance Checklist

  • [ ] Is response compression enabled?
  • [ ] Are you using System.Text.Json with proper settings?
  • [ ] Are you caching expensive operations or results?
  • [ ] Have you measured and reduced memory allocations?
  • [ ] Are you streaming large data instead of loading all at once?
  • [ ] Is your dependency injection properly scoped?
  • [ ] Are you monitoring real performance metrics in production?

🔚 Conclusion

Efficient APIs aren’t just about fast code — they’re about thoughtful design, strategic trade-offs, and the right tools. These techniques help reduce latency, CPU load, and memory usage, making your APIs ready for real-world scale.

Optimise smartly. Profile often. And ship fast.


Top comments (4)

Collapse
 
michael_liang_0208 profile image
Michael Liang

Nice post

Collapse
 
dotallio profile image
Dotallio

Bookmarking these tips!

Collapse
 
stevsharp profile image
Spyros Ponaris

Thanks for sharing !

Collapse
 
tingwei628 profile image
Tingwei

Thanks for sharing ! It would be better to do experiments with these techniques.