AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Maui Architecture

skill-landim32-awesome-ai-skills-maui-architecture · by landim32

Extension of dotnet-architecture for .NET MAUI apps. Covers MAUI-specific layers: SQLite model attributes, AutoMapper profiles, AppDatabase registration, ViewModels (CommunityToolkit.Mvvm), XAML Pages, Shell navigation, and MauiProgram.cs DI. Use AFTER or TOGETHER WITH dotnet-architecture for the backend layers (DTO, Domain, Infra.Interfaces, Infra, Application).

No reviews yet
0 installs
38 views
0.0% view→install

Install

$ agentstack add skill-landim32-awesome-ai-skills-maui-architecture

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-landim32-awesome-ai-skills-maui-architecture)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Maui Architecture? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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

  1. Run dotnet-architecture first (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.
  2. Run dotnet sln list and ls to discover project names and folder layout.
  3. The placeholder {App} stands for the actual root namespace. Replace it everywhere.
  4. Read at least one existing entity end-to-end (Model → Mapper → ViewModel → Page) to match style.
  5. 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.Mvvm source 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's Update()/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
  • IMapper injected for conversions
  • Navigation passes DTOs
  • Save/Update goes through domain entity methods (Create/Update/Validate) for business rules
  • [ObservableProperty] on _camelCase fields
  • [RelayCommand] on {Method}Async methods

Step 5: List Page XAML — {App}/Pages/{Entity}ListPage.xaml


    
        
        
        
            
                
                    
                        
                            
                        
                        
                            
                                
                            
                            
                        
                    
                
            
        
        
    

XAML conventions:

  • DTOs need assembly={App}.DTO in xmlns
  • ViewModels in MAUI do NOT need assembly qualifier
  • Use x:DataType for 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}.Application project — 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.csCreateTableAsync() | 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

  1. Confirm backend exists — Check that DTO, Domain Model, Repository Interface/Implementation, and Application DI are in place. If not, run dotnet-architecture first.
  2. Discover firstdotnet sln list, read existing entities to match patterns
  3. Order — Mapper → AppDatabase → ViewModels → Pages → MauiProgram.cs → Shell → Tests
  4. Build after each layer to catch errors early
  5. Match the app language in UI strings
  6. ViewModels bind to DTOs — never to Domain Models directly
  7. Mapper — one Profile per entity in Infra/Mappings/
  8. Singleton for repos/services/appservices — Transient for ViewModels/Pages
  9. Domain logic stays in Domain — no business rules in ViewModels or Pages
  10. 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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.