ASP.NET Core 10

Minimal APIs, Blazor United, AOT compilation, and the patterns that make ASP.NET Core 10 the fastest and most productive web framework Microsoft has ever shipped.

S
Sai Kiran Pandrala

Why ASP.NET Core still wins in 2026

Every year someone declares ASP.NET Core dead. Every year the TechEmpower benchmarks come out and ASP.NET Core is top three. Every year the hiring boards for enterprise .NET roles stay hot. 2026 is no different, with .NET 10 LTS shipping last November and ASP.NET Core 10 now battle-tested at Microsoft's own cloud scale.

What changed in 10: Blazor United matured into the default for new web apps, Native AOT became practical for most server workloads, HTTP/3 is on by default in Kestrel, OpenAPI v3.1 generation is built-in (killed Swashbuckle), and Request Delegate Generator (RDG) makes Minimal APIs trim-safe.

This guide is for builders. I'll skip the "here's a class" tutorial stuff and hit the patterns that matter for production.

Minimal APIs, the default, and why

Controllers aren't deprecated, but Minimal APIs are now the ship-it choice for new projects. Less ceremony, faster cold start, first-class AOT support, and the endpoint filter pipeline is cleaner than MVC's filter stack.

var app = WebApplication.CreateSlimBuilder(args).Build();

app.MapGet("/orders/{id:int}", async (int id, IOrderService svc, CancellationToken ct) =>
{
    var order = await svc.GetAsync(id, ct);
    return order is null ? Results.NotFound() : Results.Ok(order);
})
.WithName("GetOrder")
.WithOpenApi();

Patterns to adopt on day one

  • Typed results. Results.Ok(value)TypedResults.Ok(value) gives you an explicit return type for OpenAPI.
  • Endpoint filters. Replace middleware for per-endpoint concerns (validation, rate limits, caching).
  • Route groups. Apply common requirements to a set of endpoints without repeating.
  • Result types. Return Results<Ok<T>, NotFound, ValidationProblem> for clean OpenAPI surfaces.
  • Auth everywhere. .RequireAuthorization("policy") at the group level.
ConcernMVC ControllersMinimal APIs
Cold start~140ms~35ms (AOT: 8ms)
Req/sec (JSON)~250K~440K
Binary size trimmed~45 MB~9 MB (AOT)
Learning curveModerateGentle
Deep customisationBestGetting there

Blazor United, one framework, all rendering modes

Blazor United (.NET 8+) is Microsoft's unification of Server, WebAssembly, and Static SSG into a single component model. In .NET 10 the developer experience caught up: pre-rendering, streaming rendering, and per-component render mode selection all work in one project.

Render-mode cheat sheet

ModeRuns whereBest for
Static SSRServer, pre-renderedMarketing, content pages, SEO
Streaming SSRServer, streamed HTMLDashboards with slow data sources
Server InteractiveServer + SignalRIntranet apps, low-latency networks
WebAssemblyBrowserOffline-capable apps, high interactivity
Auto (2024+)Server first, WASM after prefetchBest-of-both default

Default your apps to Auto mode. Add @rendermode="InteractiveAuto" to components that need interactivity, leave the rest as static SSR. Your TTFB stays fast and interactivity kicks in once the WASM runtime has loaded in the background.

Production tipThe biggest Blazor performance mistake in 2026 is still sending too much state across the wire. Use PersistentComponentState to hydrate WASM from server-rendered state instead of re-fetching on the client. Cut your data calls in half.

Native AOT, ready for most server workloads

Native AOT compiles your ASP.NET Core app into a single native binary, no JIT, no runtime. Cold start drops from seconds to milliseconds. Memory footprint drops by 3-5×. Binary size is 10× smaller. Perfect for serverless (Azure Functions, Container Apps scale-to-zero) and for container density.

What works in AOT today

  • Minimal APIs, Kestrel, Authentication, Authorization, OpenAPI generation, Health Checks.
  • Entity Framework Core 10 (limited, queries pre-compiled, migrations need separate host).
  • gRPC.
  • System.Text.Json with source generators.
  • Serilog, OpenTelemetry, Polly.

What doesn't work (2026)

  • Legacy reflection-heavy libraries (older AutoMapper versions, some Newtonsoft.Json paths).
  • Runtime code generation (Roslyn scripts, dynamic LINQ).
  • Full MVC with Razor compilation (partial support).
  • Some third-party DI containers. Stick with the built-in DI.

When to bother

AOT is worth the constraints for (1) serverless functions where cold start matters, (2) high-density microservices, (3) ML inference sidecars. For big monoliths with a rich ecosystem dependency graph, stay on JIT, the engineering cost isn't worth the benefit.

// .csproj
<PropertyGroup>
  <PublishAot>true</PublishAot>
  <InvariantGlobalization>true</InvariantGlobalization>
  <StripSymbols>true</StripSymbols>
</PropertyGroup>

OpenAPI v3.1, finally first-class

Swashbuckle is out. In .NET 9 Microsoft shipped Microsoft.AspNetCore.OpenApi as the official OpenAPI generator; in .NET 10 it supports v3.1, JSON Schema 2020-12, discriminated unions, and examples from XML doc comments.

builder.Services.AddOpenApi();
app.MapOpenApi();                   // /openapi/v1.json
app.MapScalarApiReference();        // nice UI (Scalar, now in the template)

Integrate with Refit for client generation or Kiota (Microsoft's SDK generator) for typed clients in 9 languages. Publish your OpenAPI spec to APIM (Azure API Management) via CI/CD for gated public release.

Kestrel + YARP + HTTP/3, your stack already has a CDN

Kestrel in 2026 serves HTTP/1.1, HTTP/2, HTTP/3 (QUIC), and WebSockets natively. For reverse proxying, YARP (Yet Another Reverse Proxy) is Microsoft's open-source L7 proxy that outperforms nginx on .NET hot paths while giving you full programmability in C#.

A common production shape

  1. Azure Front Door at the edge (global anycast, WAF, caching).
  2. YARP in front of your microservices (header rewrites, A/B, canary).
  3. Kestrel behind YARP, HTTP/2 or HTTP/3 inside the cluster.
  4. Observability via OpenTelemetry into Azure Monitor / Grafana.

YARP examples worth stealing:

// Programmatic routes
routes.MapReverseProxy(proxyPipeline => {
    proxyPipeline.Use(async (ctx, next) => {
        ctx.Request.Headers["X-Traffic-Version"] = "blue";
        await next();
    });
});

OpenTelemetry, the only observability story now

.NET 10 ships with first-class OpenTelemetry: metrics, traces, logs, all via a single AddOpenTelemetry() call. Microsoft's .NET Aspire orchestration layer makes local multi-service observability a one-command experience.

builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService("orders-api"))
    .WithMetrics(m => m.AddAspNetCoreInstrumentation().AddRuntimeInstrumentation().AddOtlpExporter())
    .WithTracing(t => t.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation().AddEntityFrameworkCoreInstrumentation().AddOtlpExporter())
    .WithLogging();

Ship to Azure Monitor (Application Insights) via the Azure Monitor exporter, or to Grafana Tempo/Loki/Prometheus via OTLP. The same instrumentation works for both targets, no code changes to migrate.

.NET Aspire, your dev inner loop just got better

.NET Aspire is Microsoft's answer to "your app has 6 services, a Postgres, a Redis, and a queue and I'm tired of managing docker-compose." Define your app as C# code; Aspire spins up local dependencies, wires service discovery, injects connection strings, and exposes a dashboard with traces, logs, and metrics from every service.

var builder = DistributedApplication.CreateBuilder(args);

var cache   = builder.AddRedis("cache");
var pg      = builder.AddPostgres("db").AddDatabase("orders");
var api     = builder.AddProject<Projects.OrdersApi>("api")
                     .WithReference(cache).WithReference(pg);
var web     = builder.AddProject<Projects.Web>("web").WithReference(api);

builder.Build().Run();

Aspire integrates with Azure Deployment (azd up) to provision the same topology in Azure Container Apps, Azure Functions, or Azure Kubernetes Service. One command from laptop to cloud.

EF Core 10, compiled queries and JSON-first

EF Core 10 continues its journey toward AOT compatibility. Key 2026 features:

  • Compiled models. Pre-compile your DbContext and migration model for 10-30% startup speedup.
  • Primitive collections. Map List<string> to native JSON columns on SQL Server and Postgres.
  • ExecuteUpdate / ExecuteDelete. Set-based operations without loading entities. Your UPDATE ... WHERE queries finally map cleanly.
  • Improved JSON column support. Query inside JSON with LINQ; nested document patterns work.
  • Cosmos DB provider improvements. Partition-key aware queries, change feed integration, hierarchical partition keys.

One anti-pattern to stop: treating EF as a rapid prototyping tool and never optimising. Profile your hot queries with AsNoTracking(), split queries for complex includes, and use compiled models in production. These three toggles alone often cut a service's DB load by 40%.

Deployment patterns that still win

  • Container Apps + azd up, the 2026 default. dotnet new webapi --aotazd up → deployed in 90 seconds.
  • Azure Functions (isolated worker), Flex Consumption plan eliminates the old cold-start tax; AOT makes it even better.
  • App Service Premium V3, for teams that want one PaaS that just works.
  • Static Web Apps, Blazor WASM + Azure Functions APIs + global CDN, $0-9/month.
  • AKS, when you need Dapr, custom scheduling, or GPU.

Whatever you pick, use GitHub Actions with OIDC federated credentials (no service principal secrets in repos). The Azure login flow takes 3 lines; don't skip it.

Five performance loops that move p99 latency

  1. Pool, don't allocate. ArrayPool<T>, MemoryPool<T>, and reusable buffers shave 20–40% off GC pressure on request-hot paths.
  2. Profile before you optimize. BenchmarkDotNet for micro, dotnet-trace + PerfView for macro. Most "slow" endpoints are actually slow DB calls disguised.
  3. Channel-based pipelines. Replace Task.WhenAll(list) with a bounded Channel<T> + worker count = CPU cores. Backpressure is free.
  4. Cache at the edge. Output Cache + Azure Front Door reduces app CPU by 70% for read-heavy workloads.
  5. HTTP/3 + QUIC + connection reuse. Kestrel defaults aren't always the best on Linux, tune MaxConcurrentStreams and KeepAlivePingDelay.

Aim for these numbers on a 2-vCPU Container Apps replica: 2,000 RPS sustained on a minimal API returning JSON, p99 < 30 ms. Anything below that and you have an architecture problem dressed up as a language problem.

Testing strategy that survives a growing team

LayerToolCoversFrequency
UnitxUnit + FluentAssertionsPure logic, validationEvery commit
IntegrationWebApplicationFactory + TestcontainersRoutes + DB + real dependenciesEvery commit
ContractVerify + OpenAPI diffBreaking API changesPR gate
Loadk6 / NBomberp95/p99 regressionsNightly
ChaosAzure Chaos StudioFailure modesWeekly in staging
End-to-endPlaywrightCritical user journeysPer release
SecurityGitHub Advanced Security + Defender for Cloud DevOpsSecrets, SAST, depsPR gate

Key unlock: use .NET Aspire for local orchestration so every dev runs the same topology as CI. "Works on my machine" dies the day everyone shares one manifest.

Production patterns that the docs don't shout about

  • Background services as separate processes. Don't run your outbox poller in the same container as your HTTP API. Separate processes, separate SLOs, independent scaling.
  • Graceful shutdown with IHostApplicationLifetime. Wire ApplicationStopping to flush queues and drain requests; SIGTERM → SIGKILL races cost you money.
  • Problem Details everywhere. RFC 9457 ProblemDetails responses turn support tickets from "it's broken" into "it returned 404 with code.RESOURCE_GONE".
  • Typed HttpClient + Polly for every external call. Retry + circuit breaker + timeout, always.
  • Feature flags via Azure App Configuration. Release > deploy. Roll to 1% → 10% → 100% without redeploy.
  • Health checks that actually check. AddHealthChecks() → DB, cache, queue. Readiness gates on these; liveness only on process liveness.
  • Structured logging with scopes. using (logger.BeginScope(new { RequestId, TenantId })) → every log line carries context. Log Analytics queries become trivial.

Migrating from .NET Framework 4.8 - the pragmatic path

Most 2026 migrations still target the long-tail of .NET Framework 4.x workloads. The winning approach: carve-out, not big-bang.

  1. Inventory. try-convert + upgrade-assistant produce a realistic gap list in hours, not weeks.
  2. Isolate external surface. Put a YARP reverse proxy in front. Individual routes flip to the new backend as they ship.
  3. Route by risk. Ship the dullest, lowest-traffic endpoints first. Build confidence on low stakes.
  4. WCF replaced by gRPC or CoreWCF. CoreWCF is first-class in 2026 and handles wsHttpBinding for legacy clients.
  5. EF6 to EF Core. Biggest trap is LINQ translations - audit with the built-in query logger before prod.
  6. Web Forms. There is no direct port. Migrate to Blazor or Razor Pages in vertical slices.
  7. Test surface. Snapshot golden responses before the cutover; assert byte-for-byte after.

Average enterprise timeline: 6-12 months for a 500K-LOC legacy system. If a vendor promises less, read the fine print.

Ten production bugs I keep debugging for other teams

  1. Async void - silently swallowed exceptions. Use async Task everywhere outside event handlers.
  2. Blocking on async via .Result or .Wait() - deadlock in ASP.NET Core is rare but possible in specific synchronisation contexts.
  3. Disposing HttpClient per call - socket exhaustion. Use IHttpClientFactory.
  4. EF Core ToList() before filtering - loads the whole table. Always push predicates into SQL.
  5. DI lifetime mismatch - singleton holding a scoped service. Validate with the built-in scope validation in Dev.
  6. Memory leaks from long-lived event handlers in singletons. Use weak references or unsubscribe.
  7. Large JSON payloads with Newtonsoft.Json. Switch to System.Text.Json source generators for 2-5x throughput.
  8. Unbounded retry storms when the backend is down. Polly circuit breaker, always.
  9. Missing CancellationToken plumbing - requests consume resources after the client disconnects.
  10. Parallel.ForEach on async work - use Parallel.ForEachAsync in .NET 6+.

Any code review that catches two of these before prod has paid for itself.

Native AOT, minimal footprint, and the edge in 2026

.NET 10 is the first release where Native AOT is genuinely production-ready for most web APIs. The practical numbers matter.

What you actually get

A typical Minimal API compiled with AOT produces a self-contained binary of 10-25 MB, starts in under 30 milliseconds, and allocates 40-60% less memory than the JIT build of the same app. On a 512 MB container, this means you fit more replicas per node and cold-start latency drops into human-imperceptible territory.

Where AOT hurts

Dynamic code generation is gone - so is anything that relies on it: most reflection-heavy ORMs in their old patterns, runtime LINQ to SQL compilation, and any library that builds IL at runtime. EF Core 10 has AOT support but with caveats around some query shapes. Newtonsoft.Json is unsupported in AOT; System.Text.Json with source generators replaces it cleanly.

The migration pattern that works

Start with the newest, smallest service in your portfolio. Switch it to AOT. Measure cold start, memory, cost per million requests. Publish the numbers internally. Convert one service per sprint until the blast radius of AOT incompatibility is well understood. Do not start with the oldest 300K-line WebForms refugee - it will fail for 40 unrelated reasons and give AOT a bad name.

Container Apps + AOT is the sweet spot

Container Apps charges by request duration and memory. AOT cuts both. A production workload running at 100 million requests a month typically saves 30-45% on Container Apps spend after switching. That pays for the migration effort within the first quarter.

Alternative - Native AOT is not the only path

Ready-to-Run compilation is a lighter-weight option that gives you 40-60% of the startup improvement without the restrictions. If your app has hard AOT blockers, ship R2R instead. Don't let perfect be the enemy of good.

Team templates that compound productivity

Teams that ship fastest aren't faster typists - they ship faster because the starting point is already 60% done. Build these templates once, reuse forever.

  • Minimal API template with Serilog, Polly, HealthChecks, OpenTelemetry, Problem Details, and a baseline Dockerfile already wired.
  • Blazor United template with auth, pre-rendering, interactive server + WASM islands, and a design system import.
  • Worker service template with Channel-based pipelines, graceful shutdown, and queue-trigger adapters for Service Bus and Storage Queues.
  • gRPC service template with health checks, interceptors for auth and logging, and a reference contract.
  • Test project template with Testcontainers, WebApplicationFactory helpers, and a golden-response snapshot harness.
  • .NET Aspire AppHost template that wires the above five together locally and in CI.

Keep them in an internal NuGet feed or dotnet new template pack. One afternoon of investment; years of compound returns.

Five habits that separate senior .NET engineers from the rest

Tools and frameworks change. The habits that make .NET engineers valuable compound across every release.

  1. Read the runtime issues on GitHub. Thirty minutes a week on dotnet/runtime and aspnetcore issues teaches you what the product team actually cares about and what is coming six months out.
  2. Benchmark before you claim. Assertions like "async is slower" or "AOT is always faster" sound confident and are usually wrong. BenchmarkDotNet settles arguments in 15 minutes.
  3. Read PRs on your dependencies. Subscribe to the release notes of EF Core, YARP, Serilog, Polly. A five-minute skim on release day saves hours of head-scratching later.
  4. Teach one thing per month. Internal tech talk, blog post, PR review comment with an explanation. Teaching forces you to understand, and compounds your authority over years.
  5. Own a production service end-to-end. Not just the code - the deployment, the alerting, the on-call. You learn more from a single on-call shift than a week of courses.

Do these for two years and you are the person the team asks first. Do them for five and recruiters find you faster than you can update your LinkedIn.

Tools and sources I rely on

  • Andrew Lock's .NET blog, deepest coverage of runtime internals.
  • David Fowler's GitHub gists, minimal reproductions of patterns.
  • Microsoft Learn .NET paths, still the canonical source.
  • .NET Conf talks (November each year).
  • JetBrains Rider or VS 2026, pick one, master it.
  • BenchmarkDotNet, never ship "faster" without numbers.
  • k6 or NBomber, load testing; NBomber is .NET-native and under-rated.
  • Open-source templates, CleanArchitecture by Jason Taylor, eShop reference app from dotnet/eShop.

Frequently Asked Questions

Should I start a new project in Minimal APIs or Controllers?

Minimal APIs for 95% of projects. They're the fastest path to production, support AOT, and the ergonomics are better for small-to-medium APIs. Use Controllers when you need deep MVC features (model binding inheritance, complex filters, action-result inheritance) or a very large API surface where grouping helps organisation.

Is Blazor production-ready for customer-facing apps?

Yes. Blazor Server is proven in high-traffic intranet apps. Blazor WebAssembly is excellent for offline-capable apps. Blazor United (the 2024-26 evolution) solves the rendering mode tax and is now a legitimate contender against React/Next.js for .NET-shop customer-facing apps. The win: shared C# code between server and client.

When should I use Native AOT?

For serverless (Functions, Container Apps scale-to-zero), high-density microservices, and ML inference sidecars. Not for large monoliths with heavy reflection dependencies. Try PublishAot=true on a leaf service first; if it builds and passes tests, you've qualified the dependency tree.

Is YARP a replacement for nginx?

In .NET-centric stacks, yes, YARP outperforms nginx for many workloads and lets you write proxy logic in C#. If you're a polyglot shop or need features like Lua scripting, keep nginx. For API gateway needs, use Azure API Management or Envoy in front of YARP.

How do I choose between .NET Aspire and raw docker-compose?

Aspire if your app is .NET-heavy. It gives you service discovery, telemetry, and a dashboard for free. Raw docker-compose if you have lots of non-.NET services and need precise container control. Nothing stops you from mixing, Aspire can manage containers alongside projects.

Will .NET 10 receive long-term support?

Yes, .NET 10 is an LTS release (released November 2025) with 3 years of support. Most enterprises should stay on LTS versions; .NET 11 will be STS and fine for modernisation projects that don't need the longer support window.

#ASP.NET Core#ASP.NET Core 10#.NET 10#Blazor#Minimal API#Native AOT#C# 14#web development

Join the HowToFixMe

One email every Sunday. Microsoft, Azure, AI, and the automations that actually save you hours.