Modul Pembelajaran Lengkap

Bangun Aplikasi MangsIpul

Panduan komprehensif pengembangan aplikasi Desktop Windows Forms dengan arsitektur REST API. Materi mencakup dari persiapan lingkungan, desain UI/UX, hingga implementasi CRUD dan deployment.

.NET 6+
WinForms
REST API
Newtonsoft.JSON
2026
Enterprise Edition
v3.0
01

Persiapan & Instalasi Tools

Membangun fondasi lingkungan pengembangan yang solid

🎯 Mengapa Visual Studio 2022 Community?

Visual Studio 2022 Community adalah IDE gratis dan full-featured yang menjadi standar industri untuk pengembangan .NET. Berikut keunggulannya:

  • Debugger Profesional - Fitur breakpoint, watch, dan immediate window untuk melacak bug dengan presisi tinggi
  • WinForms Designer - Editor visual drag-and-drop untuk mempercepat desain UI tanpa perlu coding manual posisi kontrol
  • IntelliCode - AI-assisted coding yang memprediksi kode berdasarkan konteks, meningkatkan produktivitas hingga 30%
  • NuGet Package Manager - Manajemen library eksternal terintegrasi, memudahkan instalasi Newtonsoft.Json, RestSharp, dll
  • Live Share - Kolaborasi real-time dengan tim untuk pair programming

📥 Prosedur Download & Instalasi Lengkap

1

Kunjungi situs resmi Microsoft

Buka visualstudio.microsoft.com/downloads menggunakan browser (Chrome/Edge/Firefox).

2

Pilih edisi Community

Scroll ke bawah, temukan bagian "Visual Studio Community 2022", lalu klik tombol Free Download. Edisi Community gratis untuk individu, open source, dan tim kecil (maks 5 developer).

3

Jalankan Installer

File vs_community.exe akan terdownload. Klik kanan → Run as Administrator (penting untuk instalasi komponen sistem).

4

Pilih Workload yang Tepat

Centang ".NET desktop development". Workload ini akan menginstal:

  • .NET Framework 4.8 SDK & .NET 6/7/8 SDK
  • Windows Forms Designer
  • MSBuild compiler
  • Debugging tools
5

Klik Install & Tunggu Proses

Proses instalasi memakan waktu 10-30 menit tergantung kecepatan internet. Pastikan koneksi stabil karena total download sekitar 5-8 GB.

Verifikasi Instalasi

✔ Visual Studio 2022 muncul di Start Menu

✔ Bisa membuat project baru dengan template "Windows Forms App"

✔ NuGet Package Manager bisa diakses via Tools → NuGet Package Manager

02

Arsitektur Proyek

Membangun struktur kode yang scalable dan maintainable

Arsitektur aplikasi desktop modern memisahkan UI (Presentation Layer), Logika Bisnis (Business Logic), dan Akses Data (Data Access). Berikut struktur folder yang direkomendasikan untuk proyek MangsIpul:

MangsIpulApp/
├── 📁 Forms/                      # Semua Form UI
│   ├── LoginForm.cs
│   ├── MainDashboard.cs
│   ├── TransaksiForm.cs
│   └── ProdukForm.cs
│
├── 📁 Models/                     # Class untuk merepresentasikan data
│   ├── User.cs
│   ├── Barang.cs
│   ├── Transaksi.cs
│   └── ApiResponse.cs
│
├── 📁 Services/                   # Service layer (API, Session, dll)
│   ├── ApiService.cs              # HttpClient terpusat
│   ├── SessionManager.cs          # Manajemen token & user session
│   └── AuthService.cs             # Login/logout logic
│
├── 📁 Helpers/                    # Utility classes
│   ├── FormHelper.cs              # Navigasi form
│   └── ValidationHelper.cs        # Validasi input
│
├── 📁 Resources/                  # Assets (gambar, icon)
│   └── logo.png
│
├── app.config                     # Konfigurasi aplikasi (base URL, dll)
└── Program.cs                     # Entry point aplikasi
                        
📁 Forms Layer

Berisi semua file .cs untuk UI. Setiap form hanya bertanggung jawab untuk menampilkan data dan mengirim input user. Tidak boleh ada logika bisnis atau akses database langsung di sini.

📁 Models Layer

Plain C# class yang merepresentasikan struktur data (mirip dengan JSON dari API). Contoh: User punya property Id, Username, Email.

📁 Services Layer

Tempat semua logika komunikasi dengan API, manajemen session, dan business rules. Form hanya memanggil method dari service ini.

📁 Helpers Layer

Function-function utility yang reusable, seperti format tanggal, validasi email, atau navigasi antar form.

💡 Prinsip Penting: Pisahkan UI dari logika! Jika nanti ingin migrasi dari WinForms ke WPF atau MAUI, Anda hanya perlu mengganti folder Forms, sementara Models dan Services bisa digunakan ulang 100%.

03

Inisialisasi Proyek

Langkah membuat proyek baru di Visual Studio

Langkah-langkah Membuat Proyek Baru:

  1. Buka Visual Studio 2022 → Klik "Create a new project"
  2. Pilih template "Windows Forms App (.NET Framework)" atau "Windows Forms App" (.NET 6/7/8)
  3. Isi:
    • Project name: MangsIpulApp
    • Location: Pilih folder (misal: D:\Projects\)
    • Framework: .NET 6.0 (LTS) atau .NET 8.0
  4. Klik "Create"

Install NuGet Packages yang Diperlukan:

# Buka Tools → NuGet Package Manager → Package Manager Console
# Kemudian jalankan perintah berikut:

Install-Package Newtonsoft.Json -Version 13.0.3
Install-Package RestSharp -Version 110.2.0
Install-Package Microsoft.Extensions.Configuration.Json -Version 7.0.0
                                

Newtonsoft.Json digunakan untuk serialize/deserialize JSON dari API.
RestSharp alternatif HttpClient yang lebih sederhana.
Microsoft.Extensions.Configuration untuk membaca file konfigurasi seperti appsettings.json.

Kode Program.cs (Entry Point):

using System;
using System.Windows.Forms;

namespace MangsIpulApp
{
    internal static class Program
    {
        /// <summary>
        /// Entry point utama aplikasi.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // Mengaktifkan visual styles untuk tampilan modern
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            
            // Menampilkan form login terlebih dahulu
            // LoginForm akan mengecek apakah user sudah login sebelumnya
            Application.Run(new LoginForm());
        }
    }
}
                                

[STAThread] adalah attribute yang wajib ada untuk aplikasi Windows Forms. STA = Single-Threaded Apartment, diperlukan untuk komponen COM seperti Clipboard dan Drag-Drop.

04

Desain Form Login (Step-by-Step)

Membuat antarmuka login yang profesional dan user-friendly

TAHAP 1

Konfigurasi Jendela Form

Buka file LoginForm.cs di designer, lalu set properti berikut di Properties Window:

  • Text "MangsIpul Login System"
  • Size 450, 600
  • StartPosition CenterScreen
  • FormBorderStyle FixedDialog
  • MaximizeBox False
  • BackColor White atau #F3F4F6
Preview
[Form akan muncul di tengah layar]
TAHAP 2

Menambahkan Komponen Input

1

Label "Username" - Drag dari Toolbox → Label, set Text = "Email / Username"

2

TextBox Username - Drag TextBox, set (Name) = txtUsername, set placeholder menggunakan event Enter/Leave

3

Label "Password" - Sama seperti username

4

TextBox Password - Drag TextBox, set:

  • (Name) = txtPassword
  • UseSystemPasswordChar = TrueWAJIB! (menampilkan bullet ●●● bukan teks asli)
5

CheckBox "Show Password" - Untuk toggle visibility password

6

Button Login - Drag Button, set properti:

  • (Name) = btnLogin
  • Text = "MASUK" atau "LOGIN"
  • BackColor = #6d28d9 (Purple)
  • ForeColor = White
  • FlatStyle = Flat, lalu set FlatAppearance.BorderSize = 0
  • Cursor = Hand (ubah pointer saat hover)
TAHAP 3 (OPTIONAL)

Menggunakan TableLayoutPanel untuk Responsif

Agar form terlihat rapi dan responsif saat di-resize (walau FixedDialog, tetap bagus untuk maintain layout), gunakan TableLayoutPanel:

  1. Drag TableLayoutPanel ke form, set Dock = Fill
  2. Atur jumlah baris dan kolom (misal 5 baris, 2 kolom)
  3. Masukkan Label dan TextBox ke dalam sel yang sesuai
  4. Atur properti ColumnStyles dan RowStyles untuk persentase lebar

💡 Fitur Tambahan: Toggle Show Password

private void chkShowPassword_CheckedChanged(object sender, EventArgs e)
{
    // Jika checkbox dicentang, tampilkan password asli
    // Jika tidak, tampilkan bullet
    txtPassword.UseSystemPasswordChar = !chkShowPassword.Checked;
}
                                
05

Implementasi API Login + Session Manager

Menghubungkan form login dengan backend REST API

1. Membuat SessionManager (Service)

Buat file baru Services/SessionManager.cs:

using System;

namespace MangsIpulApp.Services
{
    public static class SessionManager
    {
        // Menyimpan token JWT yang didapat dari API
        public static string BearerToken { get; set; }
        
        // Informasi user yang sedang login
        public static string CurrentUserId { get; set; }
        public static string CurrentUsername { get; set; }
        public static string CurrentUserEmail { get; set; }
        public static string CurrentUserRole { get; set; }
        
        // Cek apakah user sudah login
        public static bool IsLoggedIn => !string.IsNullOrEmpty(BearerToken);
        
        // Logout: hapus semua session
        public static void Logout()
        {
            BearerToken = null;
            CurrentUserId = null;
            CurrentUsername = null;
            CurrentUserEmail = null;
            CurrentUserRole = null;
        }
    }
}
                                

2. Membuat ApiService (HttpClient Handler)

Best Practice: Gunakan satu instance HttpClient untuk seluruh aplikasi untuk menghindari socket exhaustion.

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace MangsIpulApp.Services
{
    public class ApiService
    {
        private static HttpClient _client;
        private static readonly object _lock = new object();
        
        // Singleton pattern untuk HttpClient
        public static HttpClient Client
        {
            get
            {
                lock (_lock)
                {
                    if (_client == null)
                    {
                        _client = new HttpClient();
                        _client.BaseAddress = new Uri("http://localhost:8000/api/");
                        _client.DefaultRequestHeaders.Accept.Clear();
                        _client.DefaultRequestHeaders.Accept.Add(
                            new MediaTypeWithQualityHeaderValue("application/json"));
                        _client.Timeout = TimeSpan.FromSeconds(30);
                    }
                    return _client;
                }
            }
        }
        
        // Method untuk menambahkan token Authorization ke setiap request
        public static void SetAuthorizationHeader()
        {
            if (SessionManager.IsLoggedIn)
            {
                Client.DefaultRequestHeaders.Authorization = 
                    new AuthenticationHeaderValue("Bearer", SessionManager.BearerToken);
            }
        }
        
        // Hapus authorization header saat logout
        public static void RemoveAuthorizationHeader()
        {
            Client.DefaultRequestHeaders.Authorization = null;
        }
    }
}
                                

3. Model untuk Response Login

Buat file Models/LoginResponse.cs:

using Newtonsoft.Json;

namespace MangsIpulApp.Models
{
    public class LoginRequest
    {
        public string email { get; set; }
        public string password { get; set; }
    }
    
    public class LoginResponse
    {
        [JsonProperty("token")]
        public string Token { get; set; }
        
        [JsonProperty("user")]
        public User User { get; set; }
        
        [JsonProperty("message")]
        public string Message { get; set; }
    }
    
    public class User
    {
        [JsonProperty("id")]
        public int Id { get; set; }
        
        [JsonProperty("username")]
        public string Username { get; set; }
        
        [JsonProperty("email")]
        public string Email { get; set; }
        
        [JsonProperty("role")]
        public string Role { get; set; }
    }
    
    public class ErrorResponse
    {
        [JsonProperty("message")]
        public string Message { get; set; }
        
        [JsonProperty("errors")]
        public object Errors { get; set; }
    }
}
                                

4. Implementasi Method Login di LoginForm.cs

using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json;
using MangsIpulApp.Models;
using MangsIpulApp.Services;

namespace MangsIpulApp.Forms
{
    public partial class LoginForm : Form
    {
        private readonly HttpClient _httpClient;
        
        public LoginForm()
        {
            InitializeComponent();
            _httpClient = ApiService.Client;
        }
        
        private async void btnLogin_Click(object sender, EventArgs e)
        {
            // Validasi input tidak boleh kosong
            if (string.IsNullOrWhiteSpace(txtUsername.Text))
            {
                MessageBox.Show("Username/Email tidak boleh kosong!", 
                    "Validasi", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                txtUsername.Focus();
                return;
            }
            
            if (string.IsNullOrWhiteSpace(txtPassword.Text))
            {
                MessageBox.Show("Password tidak boleh kosong!", 
                    "Validasi", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                txtPassword.Focus();
                return;
            }
            
            // Disable button sambil proses login
            btnLogin.Enabled = false;
            btnLogin.Text = "Memproses...";
            
            // Buat request object
            var loginRequest = new LoginRequest
            {
                email = txtUsername.Text.Trim(),
                password = txtPassword.Text
            };
            
            try
            {
                // Serialize ke JSON
                var json = JsonConvert.SerializeObject(loginRequest);
                var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
                
                // Kirim POST request ke endpoint login
                var response = await _httpClient.PostAsync("login", content);
                var responseBody = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    // Parse response JSON
                    var loginResult = JsonConvert.DeserializeObject<LoginResponse>(responseBody);
                    
                    // Simpan token dan data user ke SessionManager
                    SessionManager.BearerToken = loginResult.Token;
                    SessionManager.CurrentUserId = loginResult.User.Id.ToString();
                    SessionManager.CurrentUsername = loginResult.User.Username;
                    SessionManager.CurrentUserEmail = loginResult.User.Email;
                    SessionManager.CurrentUserRole = loginResult.User.Role;
                    
                    // Set header Authorization untuk request selanjutnya
                    ApiService.SetAuthorizationHeader();
                    
                    // Tampilkan pesan sukses
                    MessageBox.Show($"Selamat datang, {loginResult.User.Username}!", 
                        "Login Berhasil", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    
                    // Buka Main Dashboard
                    var dashboard = new MainDashboard();
                    dashboard.Show();
                    
                    // Sembunyikan form login, jangan di Close() karena akan mengakhiri aplikasi
                    this.Hide();
                }
                else
                {
                    // Handle error dari API (401, 404, 500, dll)
                    try
                    {
                        var errorResult = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);
                        MessageBox.Show($"Login gagal: {errorResult?.Message ?? "Username atau password salah"}", 
                            "Error Login", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    catch
                    {
                        MessageBox.Show($"Login gagal: Server mengembalikan error ({(int)response.StatusCode})", 
                            "Error Login", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            catch (HttpRequestException ex)
            {
                MessageBox.Show($"Tidak dapat terhubung ke server.\nPeriksa koneksi internet Anda.\nDetail: {ex.Message}", 
                    "Network Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (TaskCanceledException)
            {
                MessageBox.Show("Request timeout. Server mungkin sedang sibuk atau koneksi lambat.", 
                    "Timeout", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Terjadi kesalahan: {ex.Message}", 
                    "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                // Enable button kembali
                btnLogin.Enabled = true;
                btnLogin.Text = "LOGIN";
            }
        }
        
        // Event handler untuk tombol Close
        private void btnClose_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    }
}
                                

🔄 Alur Logika Login

1. User klik "Login"

↓ Validasi input (tidak boleh kosong)

2. Buat object LoginRequest {email, password}

↓ Serialize ke JSON

3. Kirim POST request ke /api/login

↓ Tunggu response dari server

4. Jika success (200):

→ Parse JSON response

→ Simpan token ke SessionManager

→ Set Authorization header untuk request selanjutnya

→ Buka MainDashboard

→ Sembunyikan LoginForm

5. Jika gagal:

→ Tampilkan pesan error dari server

06

Modul Transaksi - Desain & Data Binding

Menampilkan data transaksi dengan DataGridView dan mapping ke API

Struktur Data Transaksi dari API

{
    "id": 101,
    "kode_transaksi": "TRX-20260115-001",
    "tanggal": "2026-01-15T10:30:00",
    "jumlah": 2,
    "total_harga": 500000,
    "status": "completed",
    "user": {
        "id": 5,
        "username": "john_doe",
        "alamat": "Jl. Merdeka No. 10, Jakarta"
    },
    "barang": {
        "id": 12,
        "nama": "Laptop Gaming",
        "harga": 250000
    }
}
                                

Setting Kolom DataGridView (Manual Binding)

Karena struktur JSON memiliki nested object (user.username, barang.nama), kita perlu mapping manual.

Display Header DataPropertyName Tipe Data Penjelasan
Kode Transaksikode_transaksistringID unik transaksi
TanggaltanggalDateTimeWaktu transaksi
Pembeliuser.usernamestringNama pembeli (nested)
Alamatuser.alamatstringAlamat pengiriman (nested)
Nama Barangbarang.namastringNama produk (nested)
JumlahjumlahintQuantity barang
Total Hargatotal_hargadecimalFormat: Rp 0,000
Statusstatusstringpending, completed, cancelled

⚠️ Catatan: DataGridView tidak bisa langsung binding ke nested property (user.username). Kita harus menggunakan Custom Binding atau LINQ projection untuk flatten data.

Kode untuk Load Data Transaksi dengan Custom Binding

using Newtonsoft.Json;
using MangsIpulApp.Services;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MangsIpulApp.Forms
{
    public partial class TransaksiForm : Form
    {
        private readonly HttpClient _httpClient;
        
        public TransaksiForm()
        {
            InitializeComponent();
            _httpClient = ApiService.Client;
            Load += async (s, e) => await LoadTransaksiData();
        }
        
        private async Task LoadTransaksiData()
        {
            try
            {
                // Tampilkan loading indicator
                dgvTransaksi.DataSource = null;
                lblStatus.Text = "Loading data...";
                
                // Panggil API GET /transaksi
                var response = await _httpClient.GetAsync("transaksi");
                var jsonResponse = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    // Deserialize ke list of dynamic atau model
                    var transaksiList = JsonConvert.DeserializeObject<List<dynamic>>(jsonResponse);
                    
                    // Flatten data untuk ditampilkan di DataGridView
                    var flatList = new List<TransaksiViewModel>();
                    foreach (var item in transaksiList)
                    {
                        flatList.Add(new TransaksiViewModel
                        {
                            KodeTransaksi = item.kode_transaksi,
                            Tanggal = DateTime.Parse(item.tanggal.ToString()),
                            Pembeli = item.user.username,
                            Alamat = item.user.alamat,
                            NamaBarang = item.barang.nama,
                            Jumlah = item.jumlah,
                            TotalHarga = item.total_harga,
                            Status = item.status
                        });
                    }
                    
                    // Binding ke DataGridView
                    dgvTransaksi.DataSource = flatList;
                    
                    // Format kolom
                    FormatDataGridView();
                    
                    lblStatus.Text = $"Menampilkan {flatList.Count} transaksi";
                }
                else
                {
                    lblStatus.Text = $"Error: {response.StatusCode}";
                    MessageBox.Show($"Gagal load data: {jsonResponse}", "Error", 
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            catch (Exception ex)
            {
                lblStatus.Text = "Error loading data";
                MessageBox.Show($"Error: {ex.Message}", "Exception", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        private void FormatDataGridView()
        {
            // Set alignment untuk kolom angka
            if (dgvTransaksi.Columns["TotalHarga"] != null)
                dgvTransaksi.Columns["TotalHarga"].DefaultCellStyle.Format = "Rp #,0";
            
            if (dgvTransaksi.Columns["Tanggal"] != null)
                dgvTransaksi.Columns["Tanggal"].DefaultCellStyle.Format = "dd MMM yyyy HH:mm";
            
            // Warna background berdasarkan status
            dgvTransaksi.CellFormatting += (s, e) => {
                if (dgvTransaksi.Columns[e.ColumnIndex].Name == "Status" && e.Value != null)
                {
                    string status = e.Value.ToString().ToLower();
                    if (status == "completed")
                        e.CellStyle.BackColor = System.Drawing.Color.LightGreen;
                    else if (status == "pending")
                        e.CellStyle.BackColor = System.Drawing.Color.LightYellow;
                    else if (status == "cancelled")
                        e.CellStyle.BackColor = System.Drawing.Color.LightPink;
                }
            };
        }
    }
    
    // ViewModel untuk flatten data
    public class TransaksiViewModel
    {
        public string KodeTransaksi { get; set; }
        public DateTime Tanggal { get; set; }
        public string Pembeli { get; set; }
        public string Alamat { get; set; }
        public string NamaBarang { get; set; }
        public int Jumlah { get; set; }
        public decimal TotalHarga { get; set; }
        public string Status { get; set; }
    }
}
                                
07

CRUD Transaksi dengan API

Create, Read, Update, Delete data transaksi melalui REST API

CREATE (POST)
// Menambah transaksi baru
private async Task CreateTransaksi()
{
    var newTransaksi = new {
        barang_id = selectedBarangId,
        user_id = SessionManager.CurrentUserId,
        jumlah = nudJumlah.Value,
        alamat_pengiriman = txtAlamat.Text
    };
    
    var json = JsonConvert.SerializeObject(newTransaksi);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
    var response = await _httpClient.PostAsync("transaksi", content);
    if(response.IsSuccessStatusCode)
        await LoadTransaksiData();
}
                            
UPDATE (PUT)
// Update status transaksi
private async Task UpdateStatus(int id, string status)
{
    var updateData = new { status = status };
    var json = JsonConvert.SerializeObject(updateData);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
    var response = await _httpClient.PutAsync($"transaksi/{id}", content);
    if(response.IsSuccessStatusCode)
        await LoadTransaksiData();
}
                            
DELETE
// Hapus transaksi
private async Task DeleteTransaksi(int id)
{
    var confirm = MessageBox.Show("Yakin hapus transaksi ini?", 
        "Konfirmasi", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
    
    if(confirm == DialogResult.Yes)
    {
        var response = await _httpClient.DeleteAsync($"transaksi/{id}");
        if(response.IsSuccessStatusCode)
            await LoadTransaksiData();
    }
}
                            
READ (GET BY ID)
// Ambil detail transaksi
private async Task GetDetailTransaksi(int id)
{
    var response = await _httpClient.GetAsync($"transaksi/{id}");
    var json = await response.Content.ReadAsStringAsync();
    var transaksi = JsonConvert.DeserializeObject<dynamic>(json);
    
    txtKodeTransaksi.Text = transaksi.kode_transaksi;
    txtTotal.Text = transaksi.total_harga.ToString();
}
                            
08

Modul Produk - Manajemen Barang

CRUD data produk dengan validasi stok dan harga

Model Produk

namespace MangsIpulApp.Models
{
    public class Produk
    {
        public int id { get; set; }
        public string nama { get; set; }
        public string kode_produk { get; set; }
        public decimal harga { get; set; }
        public int stok { get; set; }
        public string kategori { get; set; }
        public string deskripsi { get; set; }
        public DateTime created_at { get; set; }
    }
}
                                

Form Produk - Load Data

public partial class ProdukForm : Form
{
    private async Task LoadProdukData()
    {
        try
        {
            var response = await _httpClient.GetAsync("produk");
            var json = await response.Content.ReadAsStringAsync();
            
            if (response.IsSuccessStatusCode)
            {
                var produkList = JsonConvert.DeserializeObject<List<Produk>>(json);
                dgvProduk.DataSource = produkList;
                
                // Format kolom harga
                dgvProduk.Columns["harga"].DefaultCellStyle.Format = "Rp #,0";
                
                // Color warning jika stok < 10
                dgvProduk.CellFormatting += (s, e) => {
                    if (dgvProduk.Columns[e.ColumnIndex].Name == "stok" && e.Value != null)
                    {
                        int stok = Convert.ToInt32(e.Value);
                        if (stok < 10)
                            e.CellStyle.ForeColor = System.Drawing.Color.Red;
                    }
                };
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show($"Error load produk: {ex.Message}");
        }
    }
}
                                

✅ Validasi yang perlu ditambahkan pada form produk:
- Nama produk tidak boleh kosong
- Harga harus > 0
- Stok minimal 0 (tidak negatif)
- Kode produk harus unik (cek ke API terlebih dahulu)
- Konfirmasi sebelum delete

09

Arsitektur Navigasi - MDI Parent & Child Form

Mengelola multiple form dalam satu window dengan proper navigation

Konsep MDI (Multiple Document Interface)

MDI Parent adalah form utama yang menjadi container untuk form-form child. Cocok untuk aplikasi desktop dengan banyak modul seperti dashboard, transaksi, laporan, dll.

Setting MainDashboard sebagai MDI Parent
// Di Properties Form MainDashboard
this.IsMdiContainer = true;
// atau di constructor
public MainDashboard()
{
    InitializeComponent();
    this.IsMdiContainer = true;
}
                                    
OpenChildForm Method (Best Practice)
private Form activeForm = null;
public void OpenChildForm(Form childForm)
{
    if (activeForm != null)
        activeForm.Close();
    
    activeForm = childForm;
    childForm.TopLevel = false;
    childForm.FormBorderStyle = FormBorderStyle.None;
    childForm.Dock = DockStyle.Fill;
    this.Controls.Add(childForm);
    this.Tag = childForm;
    childForm.BringToFront();
    childForm.Show();
}
                                    

Implementasi Navigasi dengan MenuStrip

💡 Tips Navigasi yang Baik:
  • Gunakan Panel sebagai container untuk child form, bukan langsung di MainDashboard
  • Simpan history navigasi untuk fitur back/forward
  • Tambahkan loading indicator saat membuka form yang berat
  • Gunakan BackgroundWorker atau async/await untuk operasi yang memakan waktu
  • Jangan lupa dispose form yang sudah ditutup untuk menghindari memory leak
10

Global Error Handling & Logging

Menangani exception secara profesional di level aplikasi

// Di Program.cs - Tambahkan global exception handlers
static class Program
{
    [STAThread]
    static void Main()
    {
        // Handle unhandled exceptions dari UI thread
        Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
        
        // Handle unhandled exceptions dari non-UI threads
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new LoginForm());
    }
    
    private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        LogException(e.Exception);
        MessageBox.Show($"Terjadi kesalahan: {e.Exception.Message}\n\nSilakan hubungi support.", 
            "Kesalahan Aplikasi", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    
    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        var ex = (Exception)e.ExceptionObject;
        LogException(ex);
        
        MessageBox.Show($"Kesalahan fatal: {ex.Message}\nAplikasi akan ditutup.", 
            "Kesalahan Kritis", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    
    private static void LogException(Exception ex)
    {
        string logPath = Path.Combine(Application.StartupPath, "logs", $"error_{DateTime.Now:yyyyMMdd}.log");
        Directory.CreateDirectory(Path.GetDirectoryName(logPath));
        
        string logContent = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] " +
            $"Message: {ex.Message}\n" +
            $"Stack Trace: {ex.StackTrace}\n" +
            $"Source: {ex.Source}\n" +
            new string('-', 50) + "\n";
        
        File.AppendAllText(logPath, logContent);
    }
}
                        
11

Deployment & Packaging

Mempublikasikan aplikasi ke installer dan distribusi

Cara Membuat Installer dengan ClickOnce

  1. Klik kanan project → Publish
  2. Pilih Folder → Browse lokasi publish
  3. Pilih Install from folder
  4. Set Installation Mode = "The application is available online only" atau "Offline"
  5. Klik FinishPublish
  6. Hasil installer akan berada di folder publish, file .exe dan .application

🎯 Checklist Sebelum Deployment:

  • ✓ Application manifest sudah di-sign
  • ✓ Base URL API diubah ke production
  • ✓ Connection string tidak hardcode
  • ✓ Build dalam mode Release
  • ✓ Semua dependency sudah terpackage
  • ✓ Testing di environment bersih