Skip to content

Mobile App (.NET MAUI Blazor Hybrid)

Generated by: asdamir new mobile <Name> · Templates: src/Asdamir.Tools/Templates/Mobile*.sbn

Every app built on the framework can ship a mobile client. The CLI scaffolds a MAUI Blazor Hybrid app that renders Razor UI in a native WebView and talks only to the app's Gateway over HTTP — the same central, layered model as the web app (no direct DB access; identity, menus, localization and permissions all come from AppManagement through the Gateway).

What gets generated

asdamir new mobile <Name> writes four projects:

Project Target Role
<Name>.Mobile net10.0-android MAUI host — native shell + BlazorWebView, DI, appsettings.json
<Name>.Mobile.Shared net10.0 Razor class library — pages, layout, services (the UI)
<Name>.Mobile.Data net10.0 SQLite offline cache
<Name>.Mobile.Shared.Tests net10.0 xUnit tests (token store contract, …)

Android is the default target; iOS/Windows are present but commented in <Name>.Mobile.csproj.

asdamir new mobile GeneratedMobile \
  --gateway-url https://192.168.1.137:7001/ \
  --output /path/to/parent --local-feed /path/to/feed

--gateway-url is burned into appsettings.json (Gateway:BaseUrl). It must point at the host running the app's Gateway, reachable from the device — not localhost. For an Android emulator use https://10.0.2.2:7001; for a physical device use the host's LAN IP and bind the Gateway to it (--urls https://0.0.0.0:7001).

Architecture

  • Auth is token-based (not the web's HttpOnly cookie): MobileAuthService posts to gateway/auth/login, stores the access + refresh tokens in SecureStorage (ITokenStore), and MobileApiClient stamps Authorization: Bearer … on every call.
  • Auto-refresh on 401: MobileApiClient refreshes the token once via gateway/auth/refresh and retries; if that fails it clears the tokens and routes to /login. See Authentication.
  • Offline-resilient: a transport failure (no connectivity / timeout / Gateway down) never throws to the UI — MobileApiClient logs and returns null. GetCachedAsync<T>(path, cacheKey) writes each successful response to a local SQLite cache (ICacheStore in <Name>.Mobile.Data) and serves the last-known copy when the call fails, so screens render instead of going blank. The Dashboard uses it and shows an offline banner (via IConnectivity) while showing cached data. Never cache secrets — tokens stay in SecureStorage.
  • DB-backed localization: MobileLocalizationService pulls all keys for a culture from gateway/localization/all?culture=… (served from dbo.LocalizationResource). No .resx.
  • Theme + culture are held in UiState (singleton, persisted via Preferences); MainLayout applies the theme class. INSPINIA --asd-* tokens (light + .theme-dark).

Screens

  • Login (/login) — INSPINIA-styled card with a language selector (TR/EN/RU), theme toggle (light/dark), a password show/hide eye, and a Forgot password link. Posts to the Gateway.
  • Forgot password (/forgot-password) — emails a reset link via gateway/auth/forgot-password.
  • App shell (authed) — a top app bar (hamburger + brand) and a left navigation drawer that reads the menu items from the DB (gateway/menu → AsdamirVault dbo.Menus, AppId-scoped, filtered by the user's permissions), with the signed-in user and a logout.
  • Dashboard (/) — mirrors the web app's dashboard: a welcome card + stat cards, every label from DB localization and the DemoItems count from real business data (gateway/demo-items).

Build & run

The MAUI Android workload and an Android SDK platform are required:

# 1. MAUI workload
dotnet workload install maui-android

# 2. Android SDK platform + build-tools (+ accept licenses) — installs into ~/Library/Android/sdk
dotnet build src/<Name>.Mobile/<Name>.Mobile.csproj -t:InstallAndroidDependencies \
  -f net10.0-android -p:AcceptAndroidSDKLicenses=true

# 3. Build / deploy / run — build ONE RID (a plain multi-RID build fails with NETSDK1047)
dotnet build src/<Name>.Mobile/<Name>.Mobile.csproj -f net10.0-android -r android-arm64           # produces the APK
dotnet build src/<Name>.Mobile/<Name>.Mobile.csproj -t:Run -f net10.0-android -r android-arm64     # deploy + launch on the connected device

Why a single -r: the .NET 10 Android workload resolves the RID for a build automatically; an explicit <RuntimeIdentifiers> list (a .NET 9-era workaround) makes a plain build evaluate every RID and fail with NETSDK1047. The template omits it — build/deploy a single target with -r.

Dev gateway over HTTPS

The dev Gateway uses a self-signed ASP.NET HTTPS certificate the device won't trust. In Debug, the MAUI app's gateway HttpClient accepts any server certificate (guarded by #if DEBUG, never in Release) so the device can reach the dev Gateway over HTTPS. For a physical device also open the host firewall to the Gateway port.

Conventions

  • Token lifetimes are DB-driven per app (AppConfigurationsJwt:AccessTokenLifetimeMinutes / Jwt:RefreshTokenLifetimeDays); see Configuration.
  • MAUI package versions track the installed maui-android workload band (the mobile Directory.Packages.props pins Microsoft.Maui.* 10.0.x).
  • The shared web UI library (Asdamir.Web.UI) is not referenced by mobile (it targets net10.0 only and has browser/JS-interop components); <Name>.Mobile.Shared carries its own mobile-tuned UI.
  • These mobile conventions are distilled, for Claude Code, into the asdamir-mobile skill (.claude/skills/asdamir-mobile/SKILL.md) — owned by the frontend engineer agent. Update that skill if these conventions change.