.NET MAUI Blazor platform-specific code
| Product family | ASP.NET Core |
|---|---|
| Document source | Aspnet Core Aspnetcore 10.0 |
| Guide type | Reference Guide |
| Skill level | Intermediate to advanced |
| Time | 15 - 60 minutes depending on environment |
This page documents .NET MAUI Blazor platform-specific code for engineers working with ASP.NET Core. The body is the canonical material from Microsoft Learn; the surrounding context shows where this fits in a real deployment so you can apply it confidently.
Hands-on walkthrough
Blazor sits in a weird place — it's been around since 2018, it's still evolving, and every major release shifts the recommended pattern. .NET MAUI Blazor platform-specific code is one of those mid-level topics that's easy to get 80% right and then bleed an hour on the last 20%. Here's how I work through it on real projects.
I've rebuilt this flow three times across three different employers. Same trap each time, same 20-minute fix once you know where to look.
Where this fits in a Blazor app
Modern Blazor apps in .NET 10 ship with multiple render modes: Static SSR, Interactive Server, Interactive WebAssembly, and Auto. The topic at hand affects how you reason about lifecycle, state, and what code runs where. I'll be explicit about which mode each detail applies to, because mixing them up is the most common Blazor mistake.
Setup I assume you have
- Visual Studio 2026 (or VS Code 1.96+ with the C# Dev Kit, MIT-licensed, free).
- .NET 10 SDK installed:
winget install Microsoft.DotNet.SDK.10or download the installer. About 220 MB. - A Blazor Web App project created with
dotnet new blazor -n MyBlazorApp --interactivity Auto --use-program-main. - 10-15 minutes of uninterrupted time. Blazor's interactive feedback loop is fast, but the first build can take 60-90 seconds on a cold cache.
Walkthrough
- Open the project in your editor of choice. I prefer VS Code for this because the hot reload is more predictable than VS 2026's first release.
- Identify which render mode your component runs in. Add
@attribute [RenderModeInteractiveServer]or check the parent's@rendermode. This decides whether yourOnInitializedAsyncruns once on the server or twice (once SSR, once interactive). - Add the relevant code. If the topic is about a service, register it in
Program.cswith the right lifetime —builder.Services.AddScoped<IMyService, MyService>()for per-circuit,AddSingletonfor global state. - Inject and use. Use
[Inject]property or@injectdirective at the top of the Razor file. - Test in dev mode with
dotnet watch. The hot reload usually picks up component changes in 1-2 seconds. - Profile what runs where. Open browser dev tools, network tab, and watch what comes from the server vs the WASM bundle. Misclassified calls are the silent killer.
State management notes
State in Blazor is opinionated by render mode. I keep a small mental table for this:
- Static SSR: no client state. Each request is fresh. Don't try to store anything in-memory between requests at the component level.
- Interactive Server: state lives in a SignalR circuit, in server memory, per user session. If the connection drops, the circuit is recovered for 3 minutes, then released. Plan for that.
- Interactive WebAssembly: state lives in the browser. Memory is yours; persistence isn't unless you use
ProtectedLocalStorageor similar.
How I verify a Blazor change worked
I never trust the first interactive render. There's almost always a server-side pre-render that runs different code paths.
- Disable JS in the browser briefly to see the SSR output alone. If it crashes there, you have a server-side bug masked by the interactive layer.
- Open browser dev tools, Application tab, and watch SignalR traffic on the WS connection. Each
StateHasChangedroundtrip shows up as a binary frame. - Run a 60-second user flow with the browser DevTools network throttled to "Fast 3G", you'll catch races that don't show on local LAN.
- Use
dotnet-trace collect --providers Microsoft-AspNetCore-Server-Kestrelfor low-level circuit diagnostics on a misbehaving session.
Where this falls over
- Calling JS interop during SSR. Wrap any
IJSRuntimecall inOnAfterRenderAsync(firstRender: true). SSR has no JS runtime; you'll get a clear exception, but only the first time anyone hits the page in production. - Using Scoped services for shared state. Each user gets their own scope. If you're seeing data leak across users, you've accidentally used a Singleton or static field.
- Forgetting that pre-rendering runs twice. Your
OnInitializedAsyncruns once for SSR and again for interactive. Idempotent fetches only.
If you need to back out
Blazor changes are usually easy to revert because they live in one component file. Git revert the commit, restart the app, done. If you've changed Program.cs service registrations, double-check that no other component depends on the lifetime you removed.
If the fix doesn't stick after a deploy, check whether your build cache is serving stale artifacts. I've been bitten by this on Azure DevOps Pipelines more times than I'd like to admit.
Operational notes
The next section is what I wish someone had handed me three years ago.
Team and runbook. On a team of 4-8 engineers, I've found a 30-minute walkthrough of this topic during onboarding pays off within the first sprint. New folks stop asking the same three questions in standups.
Cost watch. If your usage is spiky, consider Container Apps or Functions instead of always-on hosting. Scale-to-zero is real money saved.
Security pass. If you have a security team, loop them in before production. Even a one-paragraph summary in Slack catches issues earlier than a formal review.
Observability. Set an SLO. 99.5% of requests under 300 ms. 99.9% under 1 second. Pick numbers, write them down, monitor them. Without an SLO, 'fast enough' is whatever feels right to whoever complained last.
Patterns I keep coming back to
Across many projects in ASP.NET Core, a few patterns repeat that are worth naming explicitly.
- The "smoke test on every deploy" pattern. A 10-line script that hits 5 endpoints, checks response code and latency, exits non-zero on failure. Run it as the last step of every CD pipeline. Catches 70% of post-deploy regressions before they reach a user.
- The "feature flag everything risky" pattern. Microsoft.FeatureManagement.AspNetCore (free, ships with the framework). Wrap any change that could blow up behind a flag, default off in production, flip the flag after the change is verified.
- The "twin environment" pattern. A staging environment that is byte-for-byte identical to production in configuration, with a fraction of the data. Costs maybe 20% extra on Azure if you size sensibly. Catches roughly 50% of "works locally, fails in prod" bugs.
- The "single source of secrets" pattern. Azure Key Vault or HashiCorp Vault. Not a .env file. Not appsettings.Production.json with values committed. Real secret storage with rotation policies.
Decisions worth revisiting periodically
Some choices made early in a project age fast. I keep a list of "look at this every six months" items per project. ASP.NET Core-flavored versions look like this:
- SDK and framework version. Are you on the current LTS? Microsoft's support window is fixed; falling behind two majors means you're paying interest in security patches.
- Dependency surface. Run
dotnet list package --outdatedquarterly. Each outdated package is a small risk; cumulatively, large risk. - Build time. If your CI builds creep past 8-10 minutes, the developer feedback loop slows enough to hurt productivity. Profile and trim.
- Test coverage and execution time. Coverage that climbs slowly is fine. Test suites that climb slowly toward 30 minutes is a warning sign: refactor or parallelize.
- Production error budget. If you're hitting your error budget consistently, slow feature work and pay down operational debt. If you're under-using your error budget, you're shipping too slowly.
Why I do it this way
Years ago I treated each topic in isolation. I'd read the docs, implement the feature, ship it, move on. The result was a lot of features that worked individually and didn't compose well. Today I default to: read the docs, sketch a diagram on paper or a digital whiteboard, identify the trust boundary, identify the failure mode, then implement. Ten extra minutes up front, hours saved down the road.
The other shift was treating tests as the design tool, not the verification tool. Writing the test first forces you to think about the API surface from the caller's perspective. By the time the test compiles, the design has been pressure-tested by your own most demanding consumer: a test that has to drive the feature without knowing the implementation.
Practical example: last month I rewrote a small reporting endpoint. The first draft was a single 60-line action method that pulled data, transformed it, and returned JSON. Tests forced me to split it into an IReportRepository, an IReportFormatter, and a thin controller. Same functionality, but now I can swap the formatter for HTML output by writing one new class and zero changes to the controller. That's the power of writing the test first.
References worth bookmarking
- Microsoft Learn, the canonical source. Whatever version of this page exists at learn.microsoft.com beats my summary.
- The ASP.NET Core GitHub repo's issues and discussions. Real bugs and real solutions, sometimes faster than the docs catch up.
- The .NET API browser at learn.microsoft.com/en-us/dotnet/api. perfect for "what does this method actually return when X."
- The official .NET blog at devblogs.microsoft.com/dotnet, explains the "why" behind changes that the API docs leave implicit.
How to apply this in practice
- Treat this as a starting point. Your tenant, SKU, region, and licence level all change the surface area in small but real ways.
- Run the procedure in a non-production environment first. A staging slot, a dev tenant, a sandboxed subscription: pick one and use it.
- Pin the version you implement against. When you commit to a design choice based on this page, write the date and the exact ASP.NET Core version into your ADR (architecture decision record).
- Cross-check the current Microsoft Learn page before rolling production. The product team updates docs often; what's true today may shift in two months.
Caveats and what to double-check
- ASP.NET Core terminology drifts. The same concept can have two or three names across docs cohorts written in different years.
- Some features in this area may still be in preview, confirm GA status before relying on it for production SLAs.
- Regional availability varies. A feature documented as "global" may actually roll out region by region across months.
- Pricing for ASP.NET Core-related services changes regularly. This page does not track pricing. Use the official Microsoft pricing calculator for the latest numbers.
Related work in your environment
- Document this reference in your team wiki along with which workloads currently depend on it.
- Set up a Microsoft Learn RSS feed or doc-change alert for the source page so your team is notified when Microsoft updates the canonical version.
- Add a periodic review to your governance cadence. Quarterly is a sensible default for ASP.NET Core.
FAQ
References
- Microsoft Learn - official documentation for ASP.NET Core
- Microsoft tech community forums and Q&A
- Azure / Microsoft 365 service health dashboards
Related fixes
Related guides worth a look while you sort this one out:
- ASP.NET Core Blazor Hybrid security considerations
- ASP.NET Core Blazor synchronization context
- Pass root component parameters in ASP.NET Core Blazor Hybrid
- Reuse Razor components in ASP.NET Core Blazor Hybrid
- ASP.NET Core performance and API improvements
- Asynchronous EF methods in ASP.NET Core web apps