Do you still need ConfigureAwait(false)?
In ASP.NET Core application code, no. In libraries, yes. Here's why the answer depends on where your code runs.
Older .NET codebases have .ConfigureAwait(false) on almost every await. Newer ones often have none. Both can be right, depending on what the code is.
What it actually does
When you await a task, the code after the await resumes on the captured synchronization context, if there is one. In a WinForms or WPF app, that's the UI thread. In classic ASP.NET on .NET Framework, it's the request context.
ConfigureAwait(false) says "I don't need to come back to that context; resume on any thread pool thread." That avoids the cost of switching back, and it avoids a classic deadlock: code that blocks on an async method with .Result while holding the context the method is waiting to resume on.
ASP.NET Core has no synchronization context
ASP.NET Core doesn't install a SynchronizationContext. Continuations already run on the thread pool, so ConfigureAwait(false) changes nothing there. The same is true for console apps, worker services and Azure Functions.
So in application code that only runs in those hosts, such as your controllers, handlers and services, you can leave it out. It's noise.
Libraries still need it
If you write a library, such as a NuGet package or a shared client used across teams, you don't know where it will run. Someone might call it from a WPF app, or from an old ASP.NET application that blocks on .Result. Using ConfigureAwait(false) on every await in the library protects those callers from deadlocks and avoids needless context switches.
public async Task<Customer?> GetCustomerAsync(string id, CancellationToken ct = default)
{
using var response = await _http.GetAsync($"customers/{id}", ct).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.NotFound) return null;
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Customer>(ct).ConfigureAwait(false);
}
The analyzer rule CA2007 flags missing ConfigureAwait calls. Turn it on in library projects only.
The real fix for deadlocks
ConfigureAwait(false) in a library protects callers who block on async code. The better fix is not to block at all: async all the way up, no .Result and no .Wait(). In ASP.NET Core, blocking doesn't deadlock, but it still ties up thread pool threads and hurts throughput under load.
A newer option
Since .NET 8, Task.ConfigureAwait also accepts ConfigureAwaitOptions. For example, ConfigureAwaitOptions.SuppressThrowing lets you await a task purely to wait for it to finish, without rethrowing its exception. That's handy when shutting down background work.
Takeaway
In ASP.NET Core, worker and Functions application code, skip ConfigureAwait(false). In reusable libraries, keep using it on every await. Either way, don't block on async code with .Result or .Wait().