Blazor Web Framework
Blazor เป็น Web Framework จาก Microsoft ที่ให้เขียน Web App ด้วย C# แทน JavaScript มี Component-based Architecture เหมือน React/Vue แต่ใช้ C# และ Razor Syntax รองรับทั้ง Server-side และ Client-side (WebAssembly)
Low Code/No Code เป็นแนวคิดที่ให้สร้าง Application ด้วยการลาก Drop Components เขียน Code น้อยที่สุดหรือไม่ต้องเขียนเลย Blazor สามารถสร้าง Low Code Platform ได้ด้วย Dynamic Components และ Drag-and-Drop
Blazor Application Development
// === Blazor Web App ===
// dotnet new blazor -n MyBlazorApp --interactivity Auto
// cd MyBlazorApp
// === Components/UserList.razor ===
@page "/users"
@using MyBlazorApp.Models
@using MyBlazorApp.Services
@inject IUserService UserService
@inject NavigationManager Navigation
<PageTitle>Users</PageTitle>
<h3>User Management</h3>
@if (_loading)
{
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
}
else
{
<div class="mb-3">
<input type="text" class="form-control"
placeholder="Search users..."
@bind-value="_searchText"
@bind-value:event="oninput"
@onkeyup="OnSearch" />
</div>
<table class="table table-striped">
<thead>
<tr>
<th @onclick='() => SortBy("Name")'>Name</th>
<th @onclick='() => SortBy("Email")'>Email</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var user in _filteredUsers)
{
<tr>
<td>@user.Name</td>
<td>@user.Email</td>
<td><span class="badge bg-primary">@user.Role</span></td>
<td>
<button class="btn btn-sm btn-outline-primary"
@onclick="() => EditUser(user.Id)">Edit</button>
<button class="btn btn-sm btn-outline-danger"
@onclick="() => DeleteUser(user.Id)">Delete</button>
</td>
</tr>
}
</tbody>
</table>
<p>Total: @_filteredUsers.Count users</p>
}
@code {
private List<User> _users = new();
private List<User> _filteredUsers = new();
private string _searchText = "";
private bool _loading = true;
private string _sortField = "Name";
private bool _sortAsc = true;
protected override async Task OnInitializedAsync()
{
_users = await UserService.GetUsersAsync();
_filteredUsers = _users;
_loading = false;
}
private void OnSearch()
{
_filteredUsers = string.IsNullOrEmpty(_searchText)
? _users
: _users.Where(u =>
u.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase) ||
u.Email.Contains(_searchText, StringComparison.OrdinalIgnoreCase))
.ToList();
}
private void SortBy(string field)
{
if (_sortField == field) _sortAsc = !_sortAsc;
else { _sortField = field; _sortAsc = true; }
_filteredUsers = _sortField switch
{
"Name" => _sortAsc
? _filteredUsers.OrderBy(u => u.Name).ToList()
: _filteredUsers.OrderByDescending(u => u.Name).ToList(),
"Email" => _sortAsc
? _filteredUsers.OrderBy(u => u.Email).ToList()
: _filteredUsers.OrderByDescending(u => u.Email).ToList(),
_ => _filteredUsers,
};
}
private void EditUser(int id) => Navigation.NavigateTo($"/users/edit/{id}");
private async Task DeleteUser(int id)
{
await UserService.DeleteUserAsync(id);
_users = await UserService.GetUsersAsync();
OnSearch();
}
}
Low Code Component Builder
// === Low Code Dynamic Form Builder ===
// Components/FormBuilder.razor
// DynamicField.cs — Field Definition
namespace MyBlazorApp.LowCode;
public class DynamicField
{
public string Name { get; set; } = "";
public string Label { get; set; } = "";
public string Type { get; set; } = "text"; // text, number, email, select, checkbox, textarea
public bool Required { get; set; }
public string Placeholder { get; set; } = "";
public List<string> Options { get; set; } = new(); // สำหรับ Select
public string DefaultValue { get; set; } = "";
public int? MinLength { get; set; }
public int? MaxLength { get; set; }
public string ValidationMessage { get; set; } = "";
}
public class FormDefinition
{
public string Title { get; set; } = "";
public string Description { get; set; } = "";
public List<DynamicField> Fields { get; set; } = new();
public string SubmitUrl { get; set; } = "";
public string SubmitMethod { get; set; } = "POST";
}
// FormRenderer.razor — Render Dynamic Form
// @using MyBlazorApp.LowCode
//
// <EditForm Model="_formData" OnValidSubmit="OnSubmit">
// <h4>@Definition.Title</h4>
// <p>@Definition.Description</p>
//
// @foreach (var field in Definition.Fields)
// {
// <div class="mb-3">
// <label class="form-label">@field.Label</label>
//
// @switch (field.Type)
// {
// case "text":
// case "email":
// case "number":
// <input type="@field.Type"
// class="form-control"
// placeholder="@field.Placeholder"
// @bind="_formData[field.Name]"
// required="@field.Required" />
// break;
//
// case "textarea":
// <textarea class="form-control"
// rows="4"
// @bind="_formData[field.Name]"></textarea>
// break;
//
// case "select":
// <select class="form-select" @bind="_formData[field.Name]">
// <option value="">-- Select --</option>
// @foreach (var opt in field.Options)
// {
// <option value="@opt">@opt</option>
// }
// </select>
// break;
//
// case "checkbox":
// <input type="checkbox" class="form-check-input"
// @bind="_formData[field.Name]" />
// break;
// }
// </div>
// }
//
// <button type="submit" class="btn btn-primary">Submit</button>
// </EditForm>
// ตัวอย่าง Form Definition (JSON)
var contactForm = new FormDefinition
{
Title = "Contact Form",
Description = "กรอกข้อมูลติดต่อ",
SubmitUrl = "/api/contacts",
Fields = new List<DynamicField>
{
new() { Name = "name", Label = "ชื่อ", Type = "text", Required = true },
new() { Name = "email", Label = "Email", Type = "email", Required = true },
new() { Name = "phone", Label = "โทรศัพท์", Type = "text" },
new() { Name = "department", Label = "แผนก", Type = "select",
Options = new() { "Sales", "Support", "Engineering" } },
new() { Name = "message", Label = "ข้อความ", Type = "textarea", Required = true },
new() { Name = "subscribe", Label = "รับข่าวสาร", Type = "checkbox" },
},
};
Console.WriteLine($"Form: {contactForm.Title}");
Console.WriteLine($"Fields: {contactForm.Fields.Count}");
foreach (var f in contactForm.Fields)
Console.WriteLine($" {f.Name} ({f.Type}) {(f.Required ? "*" : "")}");
Blazor Component Library
// === Reusable Blazor Components ===
// Components/Shared/DataTable.razor
// DataTable<T> — Generic Data Table Component
// @typeparam T
//
// <div class="table-responsive">
// @if (ShowSearch)
// {
// <input type="text" class="form-control mb-3"
// placeholder="Search..."
// @bind-value="SearchText"
// @bind-value:event="oninput" />
// }
//
// <table class="table table-hover">
// <thead>
// <tr>
// @foreach (var col in Columns)
// {
// <th style="cursor:pointer"
// @onclick="() => Sort(col.Field)">
// @col.Title
// @if (_sortField == col.Field)
// {
// <span>@(_sortAsc ? "▲" : "▼")</span>
// }
// </th>
// }
// </tr>
// </thead>
// <tbody>
// @foreach (var item in PagedItems)
// {
// <tr @onclick="() => OnRowClick.InvokeAsync(item)"
// style="cursor:pointer">
// @RowTemplate(item)
// </tr>
// }
// </tbody>
// </table>
//
// <nav>
// <ul class="pagination">
// @for (var i = 1; i <= TotalPages; i++)
// {
// var page = i;
// <li class="page-item @(page == CurrentPage ? "active" : "")">
// <a class="page-link" @onclick="() => GoToPage(page)">@page</a>
// </li>
// }
// </ul>
// </nav>
// </div>
//
// @code {
// [Parameter] public List<T> Items { get; set; } = new();
// [Parameter] public List<ColumnDef> Columns { get; set; } = new();
// [Parameter] public RenderFragment<T> RowTemplate { get; set; }
// [Parameter] public EventCallback<T> OnRowClick { get; set; }
// [Parameter] public bool ShowSearch { get; set; } = true;
// [Parameter] public int PageSize { get; set; } = 10;
//
// private string SearchText = "";
// private int CurrentPage = 1;
// private string _sortField = "";
// private bool _sortAsc = true;
//
// private int TotalPages => (int)Math.Ceiling(Items.Count / (double)PageSize);
// private List<T> PagedItems => Items
// .Skip((CurrentPage - 1) * PageSize)
// .Take(PageSize).ToList();
//
// private void GoToPage(int page) => CurrentPage = page;
// private void Sort(string field) { /* sorting logic */ }
// }
// ColumnDef.cs
public class ColumnDef
{
public string Field { get; set; } = "";
public string Title { get; set; } = "";
public string Width { get; set; } = "";
public bool Sortable { get; set; } = true;
}
// ใช้งาน
// <DataTable Items="users" Columns="columns" PageSize="20">
// <RowTemplate Context="user">
// <td>@user.Name</td>
// <td>@user.Email</td>
// <td>@user.Role</td>
// </RowTemplate>
// </DataTable>
Console.WriteLine("Blazor Component Library:");
Console.WriteLine(" DataTable<T> — Generic sortable, searchable, paginated table");
Console.WriteLine(" FormBuilder — Dynamic form from JSON definition");
Console.WriteLine(" DragDrop — Drag and drop UI builder");
Console.WriteLine(" ChartWidget — Chart.js wrapper for Blazor");
Best Practices
- Component Reusability: สร้าง Generic Components ที่ใช้ซ้ำได้ เช่น DataTable<T>, FormField
- State Management: ใช้ Cascading Parameters หรือ Fluxor สำหรับ Global State
- Lazy Loading: ใช้ Lazy Assembly Loading สำหรับ Blazor WASM ลด Initial Load
- Virtualization: ใช้ Virtualize Component สำหรับ List ที่มีข้อมูลมาก
- Error Boundaries: ใช้ ErrorBoundary Component จับ Errors ไม่ให้ App Crash
- Pre-rendering: เปิด Pre-rendering สำหรับ SEO และ Initial Load เร็วขึ้น
Blazor คืออะไร
Web Framework จาก Microsoft เขียน Web App ด้วย C# แทน JavaScript มี Blazor Server ประมวลผลบน Server SignalR และ Blazor WASM รันบน Browser WebAssembly ไม่ต้องเขียน JavaScript
Low Code/No Code คืออะไร
Platform สร้าง Application ลาก Drop Components เขียน Code น้อย No Code ไม่ต้องเขียนเลย Visual Builder เหมาะ Business Users ตัวอย่าง Power Apps OutSystems Mendix Bubble
Blazor Server กับ Blazor WASM ต่างกันอย่างไร
Server ประมวลผลบน Server DOM Updates SignalR WebSocket โหลดเร็ว ต้อง Connection WASM รันบน Browser WebAssembly Offline ได้ Initial Load ช้ากว่า ไม่ต้องพึ่ง Server
Blazor เหมาะกับงานแบบไหน
ทีมถนัด C#/.NET ไม่ต้องเรียน JavaScript Enterprise Apps Type Safety Internal Tools Dashboard Admin Panel Forms-heavy ใช้ MudBlazor Radzen Syncfusion
สรุป
Blazor ให้เขียน Web App ด้วย C# ใช้ Component-based Architecture เหมือน React/Vue เมื่อรวมกับ Low Code สร้าง Dynamic Forms จาก JSON Definition Drag-and-Drop UI Builder Generic Components ที่ใช้ซ้ำได้ เหมาะกับ Enterprise Apps ที่ทีมถนัด .NET
