Read time: 10 minutes

The .NET Saturday is brought to you by:

Good news for VS Code users: You can now enjoy all the benefits of JetBrains ReSharper!

Debugging distributed systems is a nightmare.

You know the drill. Everything works fine in isolation, but put all the pieces together, and suddenly your microservices start misbehaving in ways that make no sense.

The logs are scattered. The database shows the data is there. But somehow, somewhere in that web of HTTP calls and cache layers, something is going wrong.

I’ve been there more times than I care to admit. Spending hours trying to piece together what’s happening across multiple services.

That’s where .NET Aspire’s observability features become a game-changer.

Instead of hunting through log files, you get a clear, visual representation of exactly what’s happening. You can trace requests across services and spot issues that would take hours to diagnose.

Today, I’ll walk you through a real-world debugging scenario that shows you exactly how powerful this approach can be.

Let’s dive in.

Something is broken

Let’s say we have recently enabled a new feature on our website that allows customers to submit reviews for the games they have purchased from our store.

However, just after launching a new game, the Product team reaches out to us, Engineering, with a concerning complaint:

Reviews are broken. Customers can’t see their reviews after submitting. That’s broken, right?

The UX folks investigate a bit and can confirm that the front-end is making the correct REST call to our backend API, and getting a successful response:

However, when the front-end sends a GET request to retrieve reviews, the new review won’t show up:

So now it’s up to us, the back-end team, to investigate the issue, with the main concern being losing the valuable customer reviews, which should be safely stored in the database.

We do a quick check on the Production database, and can confirm that all the latest reviews are there.

Great!

However, our back-end API won’t show the latest ones, or at least not immediately after they are created.

What’s going on?

Visualizing the back-end system

Fortunately, we recently onboarded to .NET Aspire, so standing up the entire back-end system in our local box is pretty straightforward.

After opening the repo and running the Aspire application, we land on the dashboard, where we can get a quick view of all the pieces involved in our .NET backend:

As you can see there, the graph also helps us visualize that the Reviews API has a direct connection with 3 services:

  • A PostgreSQL database
  • A Redis cache
  • A RabbitMQ message broker

Great, now let’s try to get a local repro.

Get a local repro

We’ll start by getting all the reviews for a specific game we just created in our local environment.

Let’s grab the Reviews API endpoint from the resources table:

And send a GET request:

No reviews, as expected. Now let’s create our first review:

Great, review created. Now let’s query all reviews again:

No reviews.

The good news is that we have our local repro. A huge win!

The bad news is that we still don’t know what’s going on.

Let’s investigate further.

Tracing the issue

Since we know our API is connected to a few external components, let’s get a better view of how they collaborate when processing our requests.

For this, we can use the Dashboard’s Trace view:

We see 3 requests there:

  1. The initial GET for all reviews
  2. The POST to create the new review
  3. The final GET for all reviews

What calls my attention right away is how Redis shows up right before we reach the PostgreSQL database on the GET requests.

In fact, Redis is the only thing the API talks to in our final GET request.

However, I don’t see Redis involved at all during the POST request trace.

Opening up that first GET request, we can see this:

So seems like it goes like this:

  1. We try to get the reviews from Redis
  2. If we can’t, we get the data from PostgreSQL
  3. We set whatever we found in Redis

Now, for the POST request, we see this:

In this case:

  1. We connect to RabbitMQ
  2. We save the review in the PostgreSQL DB
  3. We send a message to RabbitMQ

That sounds fairly logical. However, it looks like we do nothing about Redis during this POST call.

If that’s the case, when will the Redis cache get updated with our brand new game?

I think we are into something. Time to dive into the code.

Understanding the root cause

Let’s open up our C# code and inspect first the logic behind our GET endpoint:

As expected, we check the Redis cache first, query the DB if we find nothing, and finally set whatever we got as our new cache entry, which will expire in 15 minutes.

Now for the POST endpoint:

Again, just as we saw in the dashboard trace, we store the review in the DB, publish a message, and return.

But here’s the issue:

We are never invalidating that cache after creating new reviews, so the GET endpoint will keep retrieving old cache entries for at least 15 minutes.

Let’s fix this.

The fix

Now that we understand what’s going on, the fix is fairly straightforward:

Now, after saving the review to the DB and publishing that message, we will also invalidate the cache of reviews for the specified game.

This should result in the GET endpoint returning all reviews for a game right after it receives any new review.

Let’s confirm the fix.

Verifying the fix

Let’s start our .NET Aspire app again and run our repro steps:

  1. Get all reviews
  2. Create a new review
  3. Get all reviews again

Success!

And we can even confirm things look good on the tracing side:

Mission accomplished!

Wrapping Up

This is why I love .NET Aspire’s observability features.

What could have been hours of log diving turned into a 15-minute debugging session. The visual tracing made it immediately obvious where the issue was.

Cache invalidation bugs are notoriously tricky to spot in distributed systems. Without proper observability, you’re flying blind.

The best part? This level of observability comes out of the box with .NET Aspire. No complex setup required.

Just run your app and start debugging like a pro.

Containers & .NET Aspire Course launches this Tuesday!

My new Containers and .NET Aspire course drops this Tuesday, June 17, as part of the bootcamp!

End “works on my machine” problems forever.

Here’s what you’ll master:

  • Consistent environments that eliminate deployment disasters
  • An F5 experience that actually works in production
  • Bulletproof deployments using containers and Azure
  • Modern .NET Aspire workflows with the observability features you just saw in action

The course includes 9 modules, 61 lessons, and 8 hours of hands-on content covering containerizing .NET applications, Azure Container Registry, Azure Container Apps, health checks, and the full story on .NET Aspire.

Plus: updated Blazor and React front-ends now powered by .NET Aspire for easy deployments to Azure as containers.

Join the waitlist here to get notified the moment it goes live.

Let’s end deployment disasters forever!



Whenever you’re ready, there are 3 ways I can help you:

  1. ​Containers & .NET Aspire:​ Build production-ready .NET apps that actually run when teammates clone them, and deploy to Azure without fighting scripts.

  2. Patreon Community: Get the full working code from this newsletter, exclusive course discounts, and access to a private community for .NET developers.

  3. Promote your business to 25,000+ developers by sponsoring this newsletter.