Menggabungkan .NET MAUI dengan Azure AI Foundry adalah cara yang menarik untuk menambahkan pengalaman AI ke aplikasi mobile dan desktop dari satu basis kode. Model dari AI Foundry dapat dihubungkan ke aplikasi .NET MAUI melalui Microsoft.Extensions.AI, sehingga integrasi AI menjadi lebih konsisten dan portabel.
Mengapa memilih AI Foundry dan Microsoft.Extensions.AI?
Salah satu alasan perpaduan teknologi ini adalah kita tidak mengikat aplikasi langsung ke satu implementasi SDK tertentu di lapisan UI. Microsoft.Extensions.AI menyediakan abstraksi seperti IChatClient, sehingga aplikasi kita bisa berbicara dengan model AI secara konsisten. Library ini memang dibuat untuk memudahkan integrasi dengan berbagai layanan AI dan menjaga interoperabilitas antar layanan.
Dengan kata lain, ViewModel pada MAUI tidak perlu tahu terlalu banyak detail tentang provider AI. Ia cukup menerima IChatClient, lalu mengirim prompt dan membaca respons. Pola ini selaras dengan arsitektur MAUI yang lazim menggunakan dependency injection dan MVVM. Microsoft juga menunjukkan pola serupa pada contoh MAUI mereka dengan mendaftarkan chat client ke container layanan aplikasi.
Studi Kasus: TravelMate AI
Kita ingin membangun aplikasi bernama TravelMate AI. Pengguna memasukkan beberapa parameter sederhana yaitu:
- kota tujuan,
- lama perjalanan,
- kisaran budget,
- preferensi wisata (kuliner, sejarah, fotografi, belanja, alam), dan
- gaya perjalanan (santai, padat, solo, keluarga, dan sebagainya).
Setelah itu, aplikasi akan mengirimkan permintaan ke model AI dan menghasilkan itinerary yang terstruktur, misalnya:
- ringkasan perjalanan.
- rencana per hari.
- alokasi anggaran secara estimatif.
- tips praktis untuk perjalanan.
Arsitektur aplikasi yang akan dibangun
Arsitektur aplikasi yang akan kita bangun cukup sederhana namun representatif. UI .NET MAUI menangkap input pengguna, ViewModel menyusun prompt berdasarkan input tersebut, lalu IChatClient mengirim request ke model yang dideploy di Azure AI Foundry. Setelah model mengembalikan respons, UI menampilkan hasilnya ke pengguna. Pola ini konsisten dengan pendekatan yang digunakan dalam contoh resmi .NET MAUI dan dokumentasi Microsoft.Extensions.AI.
Di sisi Azure, kita dapat memulai dari resource model yang akan dideploy, lalu menguji prompt lebih dulu di Azure AI Foundry portal melalui Chat playground. Playground sangat berguna untuk menguji perilaku model, menyesuaikan system message, dan bahkan melihat potongan kode yang setara dari sesi yang sedang diuji. Pendekatan ini sangat membantu sebelum logika prompt dipindahkan ke aplikasi MAUI.
Prasyarat
- .NET SDK yang kompatibel dengan .NET MAUI.
- Visual Studio atau editor lain yang telah dipasang workload .NET MAUI.
- Azure Subscription.
- Resource Azure AI Foundry Models dengan model seperti gpt-4o atau gpt-4o-mini yang sudah dideploy.
- Endpoint, API key, dan nama deployment model untuk development lokal, atau alternatifnya autentikasi berbasis Microsoft Entra ID.
Struktur Project pada .NET MAUI
Di bawah ini adalah struktur project yang saya sarankan untuk sample ini. Struktur ini dibuat sederhana agar mudah dipahami, tetapi tetap cukup rapi untuk dipakai sebagai dasar pengembangan lanjutan. Pola DI + ViewModel + IChatClient mengikuti pendekatan yang digunakan pada contoh resmi .NET MAUI dan dokumentasi Microsoft.Extensions.AI
TravelMateAI/├── App.xaml├── App.xaml.cs├── MauiProgram.cs├── MainPage.xaml├── MainPage.xaml.cs├── Models/│ └── TravelPlanInput.cs├── Services/│ └── TravelPromptBuilder.cs└── ViewModels/ └── TripPlannerViewModel.cs``
Instalasi NuGET Package
Tambahkan package berikut ke project.
dotnet add package Azure.AI.OpenAIdotnet add package Microsoft.Extensions.AIdotnet add package Microsoft.Extensions.AI.OpenAIdotnet add package CommunityToolkit.Mvvm
Selain itu, siapkan environment variable. Untuk sample ini, kita akan menggunakan:
AZURE_OPENAI_ENDPOINTAZURE_OPENAI_API_KEYAZURE_OPENAI_DEPLOYMENT
App.xaml
<?xml version="1.0" encoding="utf-8" ?><Application x:Class="TravelMateAI.App" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> <Application.Resources> <ResourceDictionary> <Color x:Key="PrimaryColor">#0F172A</Color> <Color x:Key="AccentColor">#2563EB</Color> <Color x:Key="MutedColor">#64748B</Color> <Color x:Key="BorderColor">#CBD5E1</Color> <Color x:Key="SurfaceColor">#F8FAFC</Color> <Style TargetType="Label"> <Setter Property="TextColor" Value="{StaticResource PrimaryColor}" /> <Setter Property="FontFamily" Value="OpenSansRegular" /> </Style> <Style TargetType="Entry"> <Setter Property="BackgroundColor" Value="White" /> <Setter Property="TextColor" Value="{StaticResource PrimaryColor}" /> <Setter Property="PlaceholderColor" Value="{StaticResource MutedColor}" /> <Setter Property="HeightRequest" Value="46" /> </Style> <Style TargetType="Editor"> <Setter Property="BackgroundColor" Value="White" /> <Setter Property="TextColor" Value="{StaticResource PrimaryColor}" /> <Setter Property="HeightRequest" Value="320" /> <Setter Property="AutoSize" Value="TextChanges" /> </Style> <Style TargetType="Button"> <Setter Property="BackgroundColor" Value="{StaticResource AccentColor}" /> <Setter Property="TextColor" Value="White" /> <Setter Property="CornerRadius" Value="10" /> <Setter Property="HeightRequest" Value="48" /> <Setter Property="FontAttributes" Value="Bold" /> </Style> </ResourceDictionary> </Application.Resources></Application>
App.xaml.cs
namespace TravelMateAI;public partial class App : Application{ public App(MainPage mainPage) { InitializeComponent(); MainPage = new NavigationPage(mainPage); }}
MauiProgram.cs
Kode berikut bertujuan membuat AzureOpenAIClient, mengambil chat client dari deployment, mengonversinya menjadi IChatClient, lalu mendaftarkannya ke dependency injection container.
using Azure.AI.OpenAI;using Microsoft.Extensions.AI;using Microsoft.Extensions.DependencyInjection;using Microsoft.Maui.Hosting;using System.ClientModel;using TravelMateAI.ViewModels;namespace TravelMateAI;public static class MauiProgram{ public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>(); var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini"; if (string.IsNullOrWhiteSpace(endpoint) || string.IsNullOrWhiteSpace(apiKey)) { throw new InvalidOperationException( "Environment variables AZURE_OPENAI_ENDPOINT dan AZURE_OPENAI_API_KEY harus diisi."); } var azureClient = new AzureOpenAIClient( new Uri(endpoint), new ApiKeyCredential(apiKey)); var chatClient = azureClient .GetChatClient(deployment) .AsIChatClient(); builder.Services.AddChatClient(chatClient); builder.Services.AddSingleton<MainPage>(); builder.Services.AddSingleton<TripPlannerViewModel>(); return builder.Build(); }}
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?><ContentPage x:Class="TravelMateAI.MainPage" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewModels="clr-namespace:TravelMateAI.ViewModels" x:DataType="viewModels:TripPlannerViewModel" Title="TravelMate AI" BackgroundColor="{StaticResource SurfaceColor}"> <ScrollView> <VerticalStackLayout Padding="20" Spacing="14"> <Label Text="TravelMate AI" FontSize="28" FontAttributes="Bold" /> <Label Text="Buat itinerary perjalanan dengan bantuan Azure AI Foundry dan .NET MAUI" FontSize="14" TextColor="{StaticResource MutedColor}" /> <Frame Padding="16" BorderColor="{StaticResource BorderColor}" CornerRadius="14" BackgroundColor="White"> <VerticalStackLayout Spacing="12"> <Label Text="Tujuan" FontAttributes="Bold" /> <Entry Placeholder="Contoh: Yogyakarta" Text="{Binding Destination}" /> <Label Text="Durasi (hari)" FontAttributes="Bold" /> <Entry Placeholder="Contoh: 3" Keyboard="Numeric" Text="{Binding DurationDays}" /> <Label Text="Budget" FontAttributes="Bold" /> <Entry Placeholder="Contoh: Rp2.500.000" Text="{Binding Budget}" /> <Label Text="Minat" FontAttributes="Bold" /> <Entry Placeholder="Contoh: kuliner, sejarah, fotografi" Text="{Binding Interests}" /> <Label Text="Gaya Perjalanan" FontAttributes="Bold" /> <Entry Placeholder="Contoh: santai" Text="{Binding TravelStyle}" /> <Label Text="Tipe Perjalanan" FontAttributes="Bold" /> <Entry Placeholder="Contoh: solo / keluarga / pasangan" Text="{Binding CompanionType}" /> <Grid ColumnDefinitions="*,*" ColumnSpacing="10"> <Button Grid.Column="0" Text="Generate Itinerary" Command="{Binding GenerateItineraryCommand}" /> <Button Grid.Column="1" Text="Reset" Command="{Binding ResetFormCommand}" BackgroundColor="#475569" /> </Grid> <ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" Color="{StaticResource AccentColor}" /> </VerticalStackLayout> </Frame> <Frame Padding="16" BorderColor="{StaticResource BorderColor}" CornerRadius="14" BackgroundColor="White"> <VerticalStackLayout Spacing="10"> <Label Text="Hasil Itinerary" FontSize="18" FontAttributes="Bold" /> <Editor Text="{Binding ItineraryResult}" IsReadOnly="True" /> </VerticalStackLayout> </Frame> </VerticalStackLayout> </ScrollView></ContentPage>
MainPage.xaml.cs
using TravelMateAI.ViewModels;namespace TravelMateAI;public partial class MainPage : ContentPage{ public MainPage(TripPlannerViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; }}
Models/TravelPlanInput.cs
namespace TravelMateAI.Models;public class TravelPlanInput{ public string Destination { get; set; } = string.Empty; public int DurationDays { get; set; } public string Budget { get; set; } = string.Empty; public string Interests { get; set; } = string.Empty; public string TravelStyle { get; set; } = string.Empty; public string CompanionType { get; set; } = string.Empty;}
Services/TravelPromptBuilder.cs
Prompt berikut memanfaatkan konsep system message dan user message. System message dipakai untuk mendikte perilaku model dan memformat hasil, sementara user message membawa konteks spesifik dari input pengguna.
using TravelMateAI.Models;namespace TravelMateAI.Services;public static class TravelPromptBuilder{ public static string BuildSystemPrompt() => """ Anda adalah AI travel planner profesional. Tugas Anda: 1. Menyusun itinerary perjalanan yang realistis, praktis, dan mudah dibaca. 2. Menjawab dalam bahasa Indonesia yang formal namun tetap ramah. 3. Menghindari klaim harga yang terlalu pasti jika tidak ada data valid; gunakan estimasi seperlunya. 4. Menyesuaikan saran dengan durasi, budget, minat, dan gaya perjalanan pengguna. Formatkan hasil dengan struktur berikut: - Ringkasan perjalanan - Itinerary per hari - Estimasi alokasi budget - Tips perjalanan Jika informasi pengguna tidak lengkap, tetap berikan itinerary terbaik berdasarkan asumsi yang wajar. """; public static string BuildUserPrompt(TravelPlanInput input) => $""" Buat itinerary perjalanan dengan detail berikut: Tujuan: {input.Destination} Durasi: {input.DurationDays} hari Budget: {input.Budget} Minat: {input.Interests} Gaya perjalanan: {input.TravelStyle} Tipe perjalanan: {input.CompanionType} Kebutuhan tambahan: - Hasil harus praktis untuk wisatawan Indonesia. - Bagi itinerary per hari. - Sertakan opsi kegiatan pagi, siang, dan malam jika memungkinkan. - Sertakan estimasi alokasi budget yang masuk akal. - Tambahkan tips perjalanan singkat di bagian akhir. """;}
ViewModels/TripPlannerViewModel.cs
Implementasi ini menggunakan CommunityToolkit.Mvvm agar ViewModel tetap ringkas dan idiomatis. Di sisi AI, kode mengirim daftar ChatMessage ke IChatClient.GetResponseAsync(...).
using CommunityToolkit.Mvvm.ComponentModel;using CommunityToolkit.Mvvm.Input;using Microsoft.Extensions.AI;using TravelMateAI.Models;using TravelMateAI.Services;namespace TravelMateAI.ViewModels;public partial class TripPlannerViewModel : ObservableObject{ private readonly IChatClient _chatClient; [ObservableProperty] private string destination = "Yogyakarta"; [ObservableProperty] private string durationDays = "3"; [ObservableProperty] private string budget = "Rp2.500.000"; [ObservableProperty] private string interests = "kuliner, sejarah, fotografi"; [ObservableProperty] private string travelStyle = "santai"; [ObservableProperty] private string companionType = "solo"; [ObservableProperty] private string itineraryResult = "Silakan isi data perjalanan, lalu klik 'Generate Itinerary'."; [ObservableProperty] private bool isBusy; public TripPlannerViewModel(IChatClient chatClient) { _chatClient = chatClient; } [RelayCommand] private async Task GenerateItineraryAsync() { if (IsBusy) return; if (string.IsNullOrWhiteSpace(Destination)) { ItineraryResult = "Tujuan perjalanan wajib diisi."; return; } if (!int.TryParse(DurationDays, out var duration) || duration <= 0) { ItineraryResult = "Durasi perjalanan harus berupa angka lebih dari 0."; return; } try { IsBusy = true; ItineraryResult = "Sedang menghasilkan itinerary..."; var input = new TravelPlanInput { Destination = Destination.Trim(), DurationDays = duration, Budget = Budget.Trim(), Interests = Interests.Trim(), TravelStyle = TravelStyle.Trim(), CompanionType = CompanionType.Trim() }; var systemPrompt = TravelPromptBuilder.BuildSystemPrompt(); var userPrompt = TravelPromptBuilder.BuildUserPrompt(input); var messages = new List<ChatMessage> { new(ChatRole.System, systemPrompt), new(ChatRole.User, userPrompt) }; var response = await _chatClient.GetResponseAsync(messages); ItineraryResult = response.Text; } catch (Exception ex) { ItineraryResult = $"Terjadi kesalahan saat memanggil layanan AI: {ex.Message}"; } finally { IsBusy = false; } } [RelayCommand] private void ResetForm() { Destination = "Yogyakarta"; DurationDays = "3"; Budget = "Rp2.500.000"; Interests = "kuliner, sejarah, fotografi"; TravelStyle = "santai"; CompanionType = "solo"; ItineraryResult = "Form direset. Silakan generate itinerary baru."; }}
Saat tombol “Generate Itinerary” ditekan, aplikasi membangun dua jenis pesan: system message dan user message. System message dipakai untuk mengatur perilaku model, termasuk gaya jawaban dan format keluaran, sedangkan user message membawa input spesifik dari pengguna. Setelah itu, daftar pesan dikirim ke model melalui IChatClient.GetResponseAsync(...), lalu respons model ditampilkan kembali ke UI. Semua konteks ini berada di sisi aplikasi, karena pada pola chat completions standar, riwayat percakapan perlu dikirim ulang agar model memahami konteks interaksi yang sedang berlangsung.
Mengintegrasikan Azure AI Foundry ke .NET MAUI bukan lagi sesuatu yang kompleks. Dengan bantuan Microsoft.Extensions.AI, developer dapat membangun aplikasi AI cross-platform menggunakan abstraksi yang konsisten dan pola .NET yang sudah familiar, seperti dependency injection dan MVVM. Contoh TravelMate AI dalam artikel ini menunjukkan bahwa sebuah aplikasi MAUI dapat memanfaatkan AI untuk menghasilkan nilai bisnis yang nyata, dalam hal ini, menyusun itinerary perjalanan yang relevan, terstruktur, dan mudah digunakan oleh end-user.
Referensi
- Blog resmi .NET: Using AI Foundry with .NET MAUI
- Microsoft Learn: Microsoft.Extensions.AI libraries
- Microsoft Learn: Use the IChatClient interface
- Microsoft Learn: Azure OpenAI chat completions quickstart
- Microsoft Learn: Microsoft Foundry quickstart
Leave a comment