MangsIpul
Modul Pembelajaran Lengkap

Bangun Aplikasi MangsIpul

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

.NET 6+ WinForms REST API Newtonsoft.JSON
2026 Enterprise 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. 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
  • IntelliCode โ€” AI-assisted coding yang memprediksi kode, meningkatkan produktivitas ~30%
  • NuGet Package Manager โ€” Manajemen library terintegrasi (Newtonsoft.Json, RestSharp, dll)
  • Live Share โ€” Kolaborasi real-time untuk pair programming

๐Ÿ“ฅ Prosedur Download & Instalasi

1
Kunjungi situs resmi Microsoft
Buka visualstudio.microsoft.com/downloads menggunakan browser.
2
Pilih edisi Community
Temukan "Visual Studio Community 2022", klik Free Download. Gratis untuk individu & open source.
3
Jalankan Installer
Klik kanan file vs_community.exe โ†’ Run as Administrator.
4
Pilih Workload
Centang ".NET desktop development". Akan menginstal .NET SDK, WinForms Designer, MSBuild, dan Debugging tools.
5
Klik Install & Tunggu
Proses 10โ€“30 menit, total download ~5โ€“8 GB. Pastikan koneksi stabil.
โœ…
Verifikasi Instalasi:
VS 2022 muncul di Start Menu ยท Bisa membuat project "Windows Forms App" ยท NuGet tersedia via Tools menu
02
Arsitektur Proyek
Struktur kode yang scalable dan maintainable
Arsitektur modern memisahkan UI (Presentation Layer), Logika Bisnis, dan Akses Data. Berikut struktur folder yang direkomendasikan:
MangsIpulApp/ ├── 📁 Forms/ ├── LoginForm.cs ├── MainDashboard.cs ├── TransaksiForm.cs └── ProdukForm.cs ├── 📁 Models/ ├── User.cs ├── Barang.cs ├── Transaksi.cs └── ApiResponse.cs ├── 📁 Services/ ├── ApiService.cs ├── SessionManager.cs └── AuthService.cs ├── 📁 Helpers/ ├── FormHelper.cs └── ValidationHelper.cs ├── 📁 Resources/ └── logo.png ├── app.config └── Program.cs
๐Ÿ“ Forms Layer

Hanya menampilkan data dan menerima input user. Tidak boleh ada logika bisnis atau akses database langsung.

๐Ÿ“ Models Layer

Plain C# class yang merepresentasikan struktur data. Contoh: User punya property Id, Username, Email.

๐Ÿ“ Services Layer

Semua logika komunikasi dengan API, manajemen session, dan business rules. Form hanya memanggil method dari sini.

๐Ÿ“ Helpers Layer

Function utility yang reusable: format tanggal, validasi email, navigasi antar form.

๐Ÿ’ก
Prinsip Penting: Pisahkan UI dari logika! Jika nanti migrasi ke WPF atau MAUI, hanya folder Forms yang diganti โ€” Models dan Services bisa digunakan ulang 100%.
03
Inisialisasi Proyek
Membuat proyek baru di Visual Studio

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 = D:\Projects\ ยท Framework = .NET 6.0 (LTS)
4
Klik "Create"

Install NuGet Packages

Package Manager Console
# Buka Tools โ†’ NuGet Package Manager โ†’ Package Manager Console

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

Program.cs (Entry Point)

Program.cs
using System;
using System.Windows.Forms;

namespace MangsIpulApp
{
    internal static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            
            // Form login tampil pertama
            Application.Run(new LoginForm());
        }
    }
}
[STAThread] wajib ada untuk aplikasi WinForms. STA = Single-Threaded Apartment, diperlukan untuk komponen COM seperti Clipboard dan Drag-Drop.
04
Desain Form Login
Antarmuka login yang profesional & user-friendly
๐Ÿท๏ธ
TAHAP 1: Konfigurasi Jendela Form

Properti Form di Properties Window

Text"MangsIpul Login System"
Size450, 600
StartPositionCenterScreen
FormBorderStyleFixedDialog
MaximizeBoxFalse
BackColorWhite / #F3F4F6
๐Ÿท๏ธ
TAHAP 2: Menambahkan Komponen Input
1
Label Username โ€” Text = "Email / Username"
2
TextBox Username โ€” (Name) = txtUsername
3
Label Password โ€” sama seperti di atas
4
TextBox Password โ€” (Name) = txtPassword, UseSystemPasswordChar = True โ† WAJIB!
5
CheckBox โ€” "Show Password" untuk toggle visibility
6
Button Login โ€” (Name) = btnLogin, BackColor = #6d28d9, ForeColor = White, FlatStyle = Flat, Cursor = Hand

๐Ÿ’ก Toggle Show Password

LoginForm.cs
private void chkShowPassword_CheckedChanged(object sender, EventArgs e)
{
    // Centang = tampilkan teks; kosong = tampilkan bullet
    txtPassword.UseSystemPasswordChar = !chkShowPassword.Checked;
}
05
API Login + Session Manager
Menghubungkan form login dengan backend REST API

1. SessionManager.cs

Services/SessionManager.cs
using System;

namespace MangsIpulApp.Services
{
    public static class SessionManager
    {
        public static string BearerToken { get; set; }
        public static string CurrentUserId { get; set; }
        public static string CurrentUsername { get; set; }
        public static string CurrentUserEmail { get; set; }
        public static string CurrentUserRole { get; set; }
        
        public static bool IsLoggedIn => !string.IsNullOrEmpty(BearerToken);
        
        public static void Logout()
        {
            BearerToken = null;
            CurrentUserId = null;
            CurrentUsername = null;
            CurrentUserEmail = null;
            CurrentUserRole = null;
        }
    }
}

2. ApiService.cs (Singleton HttpClient)

Services/ApiService.cs
using System;
using System.Net.Http;
using System.Net.Http.Headers;

namespace MangsIpulApp.Services
{
    public class ApiService
    {
        private static HttpClient _client;
        private static readonly object _lock = new object();
        
        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;
                }
            }
        }
        
        public static void SetAuthorizationHeader()
        {
            if (SessionManager.IsLoggedIn)
                Client.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue("Bearer", SessionManager.BearerToken);
        }
        
        public static void RemoveAuthorizationHeader()
        {
            Client.DefaultRequestHeaders.Authorization = null;
        }
    }
}

3. Models/LoginResponse.cs

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; }
    }
}

4. Implementasi Login di LoginForm.cs

Forms/LoginForm.cs
private async void btnLogin_Click(object sender, EventArgs e)
{
    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;
    }
    
    btnLogin.Enabled = false;
    btnLogin.Text = "Memproses...";
    
    var loginRequest = new LoginRequest
    {
        email = txtUsername.Text.Trim(),
        password = txtPassword.Text
    };
    
    try
    {
        var json = JsonConvert.SerializeObject(loginRequest);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        var response = await _httpClient.PostAsync("login", content);
        var responseBody = await response.Content.ReadAsStringAsync();
        
        if (response.IsSuccessStatusCode)
        {
            var result = JsonConvert.DeserializeObject<LoginResponse>(responseBody);
            
            SessionManager.BearerToken = result.Token;
            SessionManager.CurrentUserId = result.User.Id.ToString();
            SessionManager.CurrentUsername = result.User.Username;
            SessionManager.CurrentUserEmail = result.User.Email;
            SessionManager.CurrentUserRole = result.User.Role;
            ApiService.SetAuthorizationHeader();
            
            MessageBox.Show($"Selamat datang, {result.User.Username}!",
                "Login Berhasil", MessageBoxButtons.OK, MessageBoxIcon.Information);
            
            new MainDashboard().Show();
            this.Hide();
        }
        else
        {
            var err = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);
            MessageBox.Show($"Login gagal: {err?.Message ?? "Username atau password salah"}",
                "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
    catch (HttpRequestException ex)
    {
        MessageBox.Show($"Tidak dapat terhubung ke server.\n{ex.Message}",
            "Network Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    catch (TaskCanceledException)
    {
        MessageBox.Show("Request timeout. Coba lagi.", "Timeout",
            MessageBoxButtons.OK, MessageBoxIcon.Warning);
    }
    finally
    {
        btnLogin.Enabled = true;
        btnLogin.Text = "LOGIN";
    }
}

๐Ÿ”„ Alur Logika Login

1. User klik "Login"
โ†“ Validasi input (tidak boleh kosong)
2. Buat object LoginRequest {email, password}
โ†“ Serialize ke JSON โ†’ POST /api/login
3. Tunggu response dari server
โ†“ Jika 200 OK:
โ†’ Simpan token ke SessionManager
โ†’ Set Authorization header
โ†’ Buka MainDashboard, sembunyikan LoginForm
โ†“ Jika gagal:
โ†’ Tampilkan pesan error dari server
06
Desain & Data Binding Transaksi
DataGridView + mapping nested JSON dari API

Contoh Struktur JSON dari API

Response JSON
{
  "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"
  },
  "barang": {
    "id": 12,
    "nama": "Laptop Gaming",
    "harga": 250000
  }
}

Mapping Kolom DataGridView

Header DataPropertyName Tipe
Kode Transaksikode_transaksistring
TanggaltanggalDateTime
Pembeliuser.usernamestring
Alamatuser.alamatstring
Nama Barangbarang.namastring
Jumlahjumlahint
Total Hargatotal_hargadecimal
Statusstatusstring
โš ๏ธ
DataGridView tidak bisa binding ke nested property (user.username). Gunakan LINQ projection atau ViewModel untuk flatten data.

Load Data dengan Custom Binding (ViewModel)

Forms/TransaksiForm.cs
private async Task LoadTransaksiData()
{
    try
    {
        dgvTransaksi.DataSource = null;
        lblStatus.Text = "Loading data...";
        
        var response = await _httpClient.GetAsync("transaksi");
        var jsonResponse = await response.Content.ReadAsStringAsync();
        
        if (response.IsSuccessStatusCode)
        {
            var list = JsonConvert.DeserializeObject<List<dynamic>>(jsonResponse);
            
            var flatList = new List<TransaksiViewModel>();
            foreach (var item in list)
            {
                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
                });
            }
            
            dgvTransaksi.DataSource = flatList;
            
            // Format kolom
            dgvTransaksi.Columns["TotalHarga"].DefaultCellStyle.Format = "Rp #,0";
            dgvTransaksi.Columns["Tanggal"].DefaultCellStyle.Format = "dd MMM yyyy HH:mm";
            
            // Warna status
            dgvTransaksi.CellFormatting += (s, e) => {
                if (dgvTransaksi.Columns[e.ColumnIndex].Name == "Status" && e.Value != null)
                {
                    string st = e.Value.ToString().ToLower();
                    if (st == "completed")
                        e.CellStyle.BackColor = Color.LightGreen;
                    else if (st == "pending")
                        e.CellStyle.BackColor = Color.LightYellow;
                    else if (st == "cancelled")
                        e.CellStyle.BackColor = Color.LightPink;
                }
            };
            
            lblStatus.Text = $"Menampilkan {flatList.Count} transaksi";
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Error: {ex.Message}");
    }
}

// ViewModel untuk flatten data nested
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 via REST API
POST โ€” CREATE
private async Task CreateTransaksi()
{
    var data = new {
        barang_id = selectedBarangId,
        user_id = SessionManager.CurrentUserId,
        jumlah = nudJumlah.Value,
        alamat_pengiriman = txtAlamat.Text
    };
    var json = JsonConvert.SerializeObject(data);
    var content = new StringContent(
        json, Encoding.UTF8, "application/json");
    
    var res = await _httpClient.PostAsync("transaksi", content);
    if (res.IsSuccessStatusCode)
        await LoadTransaksiData();
}
PUT โ€” UPDATE
private async Task UpdateStatus(int id, string status)
{
    var data = new { status = status };
    var json = JsonConvert.SerializeObject(data);
    var content = new StringContent(
        json, Encoding.UTF8, "application/json");
    
    var res = await _httpClient.PutAsync(
        $"transaksi/{id}", content);
    if (res.IsSuccessStatusCode)
        await LoadTransaksiData();
}
DELETE
private async Task DeleteTransaksi(int id)
{
    var confirm = MessageBox.Show(
        "Yakin hapus transaksi ini?",
        "Konfirmasi",
        MessageBoxButtons.YesNo,
        MessageBoxIcon.Question);
    
    if (confirm == DialogResult.Yes)
    {
        var res = await _httpClient.DeleteAsync(
            $"transaksi/{id}");
        if (res.IsSuccessStatusCode)
            await LoadTransaksiData();
    }
}
GET โ€” DETAIL
private async Task GetDetailTransaksi(int id)
{
    var res = await _httpClient.GetAsync(
        $"transaksi/{id}");
    var json = await res.Content.ReadAsStringAsync();
    var data = JsonConvert.DeserializeObject<dynamic>(json);
    
    txtKodeTransaksi.Text = data.kode_transaksi;
    txtTotal.Text = data.total_harga.ToString();
}
08
Manajemen Produk
CRUD data produk dengan validasi stok dan harga

Model Produk

Models/Produk.cs
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

Forms/ProdukForm.cs
private async Task LoadProdukData()
{
    try
    {
        var response = await _httpClient.GetAsync("produk");
        var json = await response.Content.ReadAsStringAsync();
        
        if (response.IsSuccessStatusCode)
        {
            var list = JsonConvert.DeserializeObject<List<Produk>>(json);
            dgvProduk.DataSource = list;
            
            dgvProduk.Columns["harga"].DefaultCellStyle.Format = "Rp #,0";
            
            // Warning stok rendah
            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 = Color.Red;
                }
            };
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Error load produk: {ex.Message}");
    }
}
โœ…
Validasi yang diperlukan:
Nama produk tidak boleh kosong ยท Harga harus > 0 ยท Stok minimal 0 ยท Kode produk harus unik ยท Konfirmasi sebelum delete
09
MDI Parent & Child Form
Multiple form dalam satu window dengan proper navigation
MDI Parent adalah form utama yang menjadi container untuk form-form child. Cocok untuk aplikasi dengan banyak modul seperti dashboard, transaksi, laporan, dll.

Set MDI Container

MainDashboard.cs
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);
    childForm.BringToFront();
    childForm.Show();
}

Implementasi Navigasi MenuStrip

Forms/MainDashboard.cs
private void transaksiToolStripMenuItem_Click(object sender, EventArgs e)
{
    OpenChildForm(new TransaksiForm());
    lblModuleTitle.Text = "Modul Transaksi";
}

private void produkToolStripMenuItem_Click(object sender, EventArgs e)
{
    OpenChildForm(new ProdukForm());
    lblModuleTitle.Text = "Modul Manajemen Produk";
}

private void logoutToolStripMenuItem_Click(object sender, EventArgs e)
{
    var confirm = MessageBox.Show("Yakin ingin logout?", "Konfirmasi",
        MessageBoxButtons.YesNo, MessageBoxIcon.Question);
    
    if (confirm == DialogResult.Yes)
    {
        SessionManager.Logout();
        ApiService.RemoveAuthorizationHeader();
        
        new LoginForm().Show();
        this.Close();
    }
}
๐Ÿ’ก
Tips Navigasi:
Gunakan Panel sebagai container child form ยท Tambahkan loading indicator saat buka form berat ยท Gunakan async/await untuk operasi berat ยท Dispose form yang sudah ditutup untuk mencegah memory leak
10
Global Error Handling
Menangani exception secara profesional di level aplikasi
Program.cs โ€” Global Exception Handler
static class Program
{
    [STAThread]
    static void Main()
    {
        Application.ThreadException +=
            new ThreadExceptionEventHandler(Application_ThreadException);
        
        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 log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] " +
            $"Message: {ex.Message}\n" +
            $"Stack: {ex.StackTrace}\n" +
            $"Source: {ex.Source}\n" +
            new string('-', 50) + "\n";
        
        File.AppendAllText(logPath, log);
    }
}
11
Deployment & Packaging
Mempublikasikan aplikasi ke installer

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 = "Offline" atau "Online only"
5
Klik Finish โ†’ Publish. Output berupa file .exe dan .application

๐ŸŽฏ Checklist Sebelum Deployment

Application manifest di-sign
Base URL API ke production
Connection string tidak hardcode
Build mode Release
Semua dependency ter-package
Testing di environment bersih