Install
$ agentstack add skill-landim32-awesome-ai-skills-maui-architecture ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
.NET MAUI Presentation Layer — Extension of dotnet-architecture
You are an expert assistant that helps developers implement the MAUI presentation layer for entities whose backend layers (DTO, Domain, Infra.Interfaces, Infra, Application) follow the Clean Architecture defined in the dotnet-architecture skill.
This skill is an EXTENSION — it covers ONLY MAUI-specific concerns. For backend layers, invoke or follow dotnet-architecture.
Input
The user will describe the entity to create or modify: $ARGUMENTS
Before You Start
- Run
dotnet-architecturefirst (or confirm backend layers already exist) — this skill assumes DTO, Domain Model, Repository Interface, Domain Service, Infra Repository, and Application DI are already in place. - Run
dotnet sln listandlsto discover project names and folder layout. - The placeholder
{App}stands for the actual root namespace. Replace it everywhere. - Read at least one existing entity end-to-end (Model → Mapper → ViewModel → Page) to match style.
- Check existing UI strings to detect the app language and match it.
What This Skill Covers
| Layer | Responsibility | |-------|---------------| | Infra — AutoMapper (Mappings) | Profile mapping Domain Model ↔ DTO in Infra/Mappings/ | | Infra — AppDatabase | SQLite table registration | | MAUI — ViewModels | MVVM with CommunityToolkit.Mvvm, binds to DTOs | | MAUI — Pages | XAML + code-behind with DI | | MAUI — Shell | Navigation registration | | MAUI — MauiProgram.cs | DI for Repos, AppServices, ViewModels, Pages |
What This Skill Does NOT Cover (handled by dotnet-architecture)
- DTO creation (
{Entity}Info) - Domain Model (rich entity with Validate/Update/Create)
- Infra.Interfaces (Repository/AppService interfaces)
- Domain Services
- Infra Repository implementation
- Application DI (
DependencyInjection.cs)
Architecture Context
Mapping Flow
DB (SQLite) → Repository → Model (Domain) → AutoMapper → {Entity}Info (DTO) → ViewModel → Page
Page → ViewModel → {Entity}Info (DTO) → AutoMapper → Model (Domain) → Repository → DB
MAUI-Specific Conventions
- ViewModels bind to DTOs (
{Entity}Info), never to Domain Models directly - Singleton for repos/services/appservices — Transient for ViewModels/Pages
- AutoMapper registered once, scans Infra assembly for all Profiles
- Domain Models use SQLite attributes (
[PrimaryKey],[AutoIncrement],[Table],[Indexed]) — this is the only infrastructure concern allowed on Domain Models in MAUI apps CommunityToolkit.Mvvmsource generators:[ObservableObject],[ObservableProperty],[RelayCommand]
Packages: sqlite-net-pcl + SQLitePCLRaw.bundle_green, CommunityToolkit.Mvvm, AutoMapper.Extensions.Microsoft.DependencyInjection
Step-by-Step Implementation (MAUI Layers Only)
Step 1: AutoMapper Profile — {App}.Infra/Mappings/{Entity}Profile.cs
> Recommended: Use AutoMapper (AutoMapper.Extensions.Microsoft.DependencyInjection) to map between Domain Models and DTOs. It eliminates repetitive manual mapping code, reduces bugs from forgotten properties, and keeps the mapping logic centralized in Profile classes. Register once with AddAutoMapper(assembly) and all Profiles are auto-discovered.
using AutoMapper;
using {App}.DTOs;
using {App}.Models;
namespace {App}.Mappings;
public class {Entity}Profile : Profile
{
public {Entity}Profile()
{
CreateMap(); // Model → DTO (read)
CreateMap() // DTO → Model (write)
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedAt, opt => opt.Ignore());
}
}
Conventions:
- One Profile per entity in
{App}.Infra/Mappings/ - Model → Info: direct map (all readable props)
- Info → Model: ignore managed fields (
CreatedAt,UpdatedAt) — the entity'sUpdate()/Create()methods control those - AutoMapper auto-discovered via
AddAutoMapper(assembly)in DI
Step 2: Register Table — {App}.Infra/Context/AppDatabase.cs
Add to InitializeAsync(): await _database.CreateTableAsync();
Step 3: List ViewModel — {App}/ViewModels/{Entity}ListViewModel.cs
public partial class {Entity}ListViewModel : ObservableObject
{
private readonly I{Entity}Repository _{entity}Repository;
private readonly IMapper _mapper;
public {Entity}ListViewModel(I{Entity}Repository repo, IMapper mapper)
{ _{entity}Repository = repo; _mapper = mapper; }
[ObservableProperty] private ObservableCollection _items = [];
[ObservableProperty] private bool _isLoading;
[ObservableProperty] private bool _isEmpty;
[RelayCommand]
private async Task LoadItemsAsync()
{
IsLoading = true;
try
{
var models = await _{entity}Repository.GetAllAsync();
Items = new(_mapper.Map>(models));
IsEmpty = Items.Count == 0;
}
finally { IsLoading = false; }
}
[RelayCommand]
private async Task DeleteAsync({Entity}Info item)
{
if (!await Shell.Current.DisplayAlert("Delete", $"Delete \"{item.Name}\"?", "Yes", "No")) return;
await _{entity}Repository.DeleteAsync(item.Id); Items.Remove(item); IsEmpty = Items.Count == 0;
}
[RelayCommand]
private async Task GoToDetailAsync({Entity}Info item) =>
await Shell.Current.GoToAsync("{Entity}DetailPage", new Dictionary { { "{Entity}Info", item } });
}
Step 4: Detail ViewModel — {App}/ViewModels/{Entity}DetailViewModel.cs
public partial class {Entity}DetailViewModel : ObservableObject, IQueryAttributable
{
private readonly I{Entity}Repository _{entity}Repository;
private readonly IMapper _mapper;
public {Entity}DetailViewModel(I{Entity}Repository repo, IMapper mapper)
{ _{entity}Repository = repo; _mapper = mapper; }
[ObservableProperty] private int _{entity}Id;
[ObservableProperty] private string _name = string.Empty;
[ObservableProperty] private bool _isSaving;
[ObservableProperty] private bool _isNewItem = true;
[ObservableProperty] private string _pageTitle = "New {Entity}";
public void ApplyQueryAttributes(IDictionary query)
{
if (query.TryGetValue("{Entity}Info", out var obj) && obj is {Entity}Info info)
{ {Entity}Id = info.Id; Name = info.Name; IsNewItem = false; PageTitle = "Edit {Entity}"; }
}
[RelayCommand]
private async Task SaveAsync()
{
IsSaving = true;
try
{
var entity = IsNewItem ? {Entity}.Create(Name) : (await _{entity}Repository.GetByIdAsync({Entity}Id))!;
if (!IsNewItem) entity.Update(Name);
var error = entity.Validate();
if (error != null) { await Shell.Current.DisplayAlert("Error", error, "OK"); return; }
await _{entity}Repository.SaveAsync(entity);
await Shell.Current.GoToAsync("..");
}
catch (InvalidOperationException ex) { await Shell.Current.DisplayAlert("Error", ex.Message, "OK"); }
finally { IsSaving = false; }
}
[RelayCommand] private async Task GoBackAsync() => await Shell.Current.GoToAsync("..");
}
ViewModel conventions:
- ViewModels bind to
{Entity}Info(DTO), not Models IMapperinjected for conversions- Navigation passes DTOs
- Save/Update goes through domain entity methods (Create/Update/Validate) for business rules
[ObservableProperty]on_camelCasefields[RelayCommand]on{Method}Asyncmethods
Step 5: List Page XAML — {App}/Pages/{Entity}ListPage.xaml
XAML conventions:
- DTOs need
assembly={App}.DTOin xmlns - ViewModels in MAUI do NOT need assembly qualifier
- Use
x:DataTypefor compiled bindings
Step 6: Page Code-Behind — {App}/Pages/{Entity}ListPage.xaml.cs
public partial class {Entity}ListPage : ContentPage
{
private readonly {Entity}ListViewModel _viewModel;
public {Entity}ListPage({Entity}ListViewModel viewModel)
{ InitializeComponent(); BindingContext = _viewModel = viewModel; }
protected override async void OnAppearing()
{ base.OnAppearing(); await _viewModel.LoadItemsCommand.ExecuteAsync(null); }
}
Step 7: DI Registration — {App}/MauiProgram.cs
// AutoMapper — scans Infra assembly for all Profiles (register ONCE)
builder.Services.AddAutoMapper(typeof(AppDatabase).Assembly);
// Application Services (Domain Services — registered via Application project)
builder.Services.AddApplicationServices();
// Infrastructure
builder.Services.AddSingleton(); // AppService (if needed)
builder.Services.AddSingleton(); // Repository
// MAUI Presentation
builder.Services.AddTransient(); // ViewModels
builder.Services.AddTransient();
builder.Services.AddTransient(); // Pages
builder.Services.AddTransient();
Key points:
AddApplicationServices()comes from{App}.Applicationproject — registers all Domain Services- Domain Services are NOT registered individually in
MauiProgram.cs - Singleton for repos/services/appservices — Transient for ViewModels/Pages
- AutoMapper registered once, scans assembly for all Profiles
Step 8: Shell Navigation — AppShell
In .xaml: ``
In .xaml.cs: Routing.RegisterRoute("{Entity}DetailPage", typeof({Entity}DetailPage));
Step 9: Unit Tests — {App}.Tests/Services/{Entity}RepositoryTests.cs
public class {Entity}RepositoryTests : IAsyncLifetime
{
private AppDatabase _database = null!;
private {Entity}Repository _repository = null!;
private string _dbPath = null!;
public async Task InitializeAsync()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.db3");
_database = new AppDatabase(_dbPath); await _database.InitializeAsync();
_repository = new {Entity}Repository(_database);
}
public Task DisposeAsync() { if (File.Exists(_dbPath)) File.Delete(_dbPath); return Task.CompletedTask; }
[Fact] public async Task SaveAsync_Insert() { Assert.Equal(1, await _repository.SaveAsync({Entity}.Create("Test"))); }
[Fact] public async Task GetAllAsync() { /* save 2, assert count == 2 */ }
[Fact] public async Task SaveAsync_Update() { /* save, Update(), save again, assert new value */ }
[Fact] public async Task DeleteAsync() { /* save, delete, get returns null */ }
// Mapper tests
[Fact] public void MapModelToInfo()
{
var config = new MapperConfiguration(cfg => cfg.AddProfile());
var mapper = config.CreateMapper();
var model = {Entity}.Create("Test");
var info = mapper.Map(model);
Assert.Equal(model.Name, info.Name);
}
}
Checklist
| # | Layer | File | Notes | |---|-------|------|-------| | 0 | Backend | Run dotnet-architecture skill | DTO, Domain, Infra.Interfaces, Infra Repo, Application DI | | 1 | Infra | {App}.Infra/Mappings/{Entity}Profile.cs | AutoMapper Profile: Model ↔ Info | | 2 | Infra | Modify AppDatabase.cs → CreateTableAsync() | SQLite table | | 3-4 | MAUI | ViewModels/{Entity}ListViewModel.cs + {Entity}DetailViewModel.cs | MVVM | | 5-6 | MAUI | Pages/{Entity}ListPage.xaml(.cs) + {Entity}DetailPage.xaml(.cs) | UI | | 7 | MAUI | Modify MauiProgram.cs | DI: Repos, AppServices, VMs, Pages + AddApplicationServices() | | 8 | MAUI | Modify AppShell.xaml + .xaml.cs | Navigation | | 9 | Tests | {Entity}RepositoryTests.cs + mapper tests | Verification |
Response Guidelines
- Confirm backend exists — Check that DTO, Domain Model, Repository Interface/Implementation, and Application DI are in place. If not, run
dotnet-architecturefirst. - Discover first —
dotnet sln list, read existing entities to match patterns - Order — Mapper → AppDatabase → ViewModels → Pages → MauiProgram.cs → Shell → Tests
- Build after each layer to catch errors early
- Match the app language in UI strings
- ViewModels bind to DTOs — never to Domain Models directly
- Mapper — one Profile per entity in
Infra/Mappings/ - Singleton for repos/services/appservices — Transient for ViewModels/Pages
- Domain logic stays in Domain — no business rules in ViewModels or Pages
- AutoMapper registered once in
MauiProgram.cs, scans Infra assembly
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: landim32
- Source: landim32/awesome-ai-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.