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):
MobileAuthServiceposts togateway/auth/login, stores the access + refresh tokens in SecureStorage (ITokenStore), andMobileApiClientstampsAuthorization: Bearer …on every call. - Auto-refresh on 401:
MobileApiClientrefreshes the token once viagateway/auth/refreshand 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 —
MobileApiClientlogs and returns null.GetCachedAsync<T>(path, cacheKey)writes each successful response to a local SQLite cache (ICacheStorein<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 (viaIConnectivity) while showing cached data. Never cache secrets — tokens stay in SecureStorage. - DB-backed localization:
MobileLocalizationServicepulls all keys for a culture fromgateway/localization/all?culture=…(served fromdbo.LocalizationResource). No.resx. - Theme + culture are held in
UiState(singleton, persisted viaPreferences);MainLayoutapplies 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 viagateway/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→ AsdamirVaultdbo.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 withNETSDK1047. 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 (
AppConfigurations→Jwt:AccessTokenLifetimeMinutes/Jwt:RefreshTokenLifetimeDays); see Configuration. - MAUI package versions track the installed
maui-androidworkload band (the mobileDirectory.Packages.propspinsMicrosoft.Maui.* 10.0.x). - The shared web UI library (
Asdamir.Web.UI) is not referenced by mobile (it targetsnet10.0only and has browser/JS-interop components);<Name>.Mobile.Sharedcarries its own mobile-tuned UI. - These mobile conventions are distilled, for Claude Code, into the
asdamir-mobileskill (.claude/skills/asdamir-mobile/SKILL.md) — owned by the frontend engineer agent. Update that skill if these conventions change.