SiamCafe · Blog
TensorFlow Serving GreenOps Sustainability — ML
บทความ

TensorFlow Serving GreenOps Sustainability — ML

เผยแพร่ 28 พฤษภาคม 2569
C# Blazor กับ Low Code No Code — วิธีใช้ Blazor สร้าง Web App และ Low Code Platform | SiamCafe Blog เรียนรู้การใช้ C# Blazor สร้าง Web Application ร่วมกับแนวคิด Low Code/No Code ตั้งแต่ Component Design, Data Binding, Authentication ไปจนถึง Drag-and-Drop UI Builder FAQ_Q:Blazor คืออะไร FAQ_A:Blazor เป็น Web Framework จาก Microsoft ที่ให้เขียน Web App ด้วย C# แทน JavaScript มี 2 แบบ Blazor Server ประมวลผลบน Server ส่ง UI Updates ผ่าน SignalR และ Blazor WebAssembly (WASM) รันบน Browser โดยตรง ใช้ .NET Runtime ใน Browser ไม่ต้องเขียน JavaScript FAQ_Q:Low Code/No Code คืออะไร FAQ_A:Low Code คือ Platform ที่ให้สร้าง Application ด้วยการลาก Drop Components และเขียน Code น้อยที่สุด No Code ไม่ต้องเขียน Code เลย ใช้ Visual Builder ทั้งหมด เหมาะกับ Business Users ที่ไม่มีพื้นฐาน Programming ตัวอย่าง Power Apps, OutSystems, Mendix, Bubble FAQ_Q:Blazor Server กับ Blazor WASM ต่างกันอย่างไร FAQ_A:Blazor Server ประมวลผลบน Server ส่ง DOM Updates ผ่าน SignalR WebSocket โหลดเร็ว ใช้ Resources น้อยบน Client แต่ต้องมี Connection ตลอด Blazor WASM รันบน Browser ด้วย WebAssembly ทำงาน Offline ได้ แต่ Initial Load ช้ากว่า ไม่ต้องพึ่ง Server Connection FAQ_Q:Blazor เหมาะกับงานแบบไหน FAQ_A: เหมาะกับทีมที่ถนัด C#/.NET ไม่ต้องเรียน JavaScript Framework ใหม่ Enterprise Apps ที่ต้องการ Type Safety, Internal Tools, Dashboard, Admin Panel, Forms-heavy Applications ใช้ Component Library เช่น MudBlazor, Radzen, Syncfusion ได้ BODY_START

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

อ่านเพิ่ม: Stable Diffusion ComfyUI API Gateway Pattern — คู่มือฉบับสมบ · อ่านเพิ่ม: Vercel Edge Functions Post-mortem Analysis — คู่มือฉบับสมบูร · อ่านเพิ่ม: Signals คืออะไร? Reactivity Pattern ใหม่ที่เปลี่ยนการเขียน F

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&lt;string&gt; 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&lt;DynamicField&gt; Fields { get; set; } = new();

public string SubmitUrl { get; set; } = "";

public string SubmitMethod { get; set; } = "POST";

}

FormRenderer.razor — Render Dynamic Form

@using MyBlazorApp.LowCode

&lt;EditForm Model="_formData" OnValidSubmit="OnSubmit"&gt;

&lt;h4&gt;@Definition.Title&lt;/h4&gt;

&lt;p&gt;@Definition.Description&lt;/p&gt;

@foreach (var field in Definition.Fields)

{

&lt;div class="mb-3"&gt;

&lt;label class="form-label"&gt;@field.Label&lt;/label&gt;

@switch (field.Type)

{

case "text":

case "email":

case "number":

&lt;input type="@field.Type"

class="form-control"

placeholder="@field.Placeholder"

@bind="_formData[field.Name]"

required="@field.Required" /&gt;

break;

case "textarea":

&lt;textarea class="form-control"

rows="4"

@bind="_formData[field.Name]"&gt;&lt;/textarea&gt;

break;

case "select":

&lt;select class="form-select" @bind="_formData[field.Name]"&gt;

&lt;option value=""&gt;-- Select --&lt;/option&gt;

@foreach (var opt in field.Options)

{

&lt;option value="@opt"&gt;@opt&lt;/option&gt;

}

&lt;/select&gt;

break;

case "checkbox":

&lt;input type="checkbox" class="form-check-input"

@bind="_formData[field.Name]" /&gt;

break;

}

&lt;/div&gt;

}

&lt;button type="submit" class="btn btn-primary"&gt;Submit&lt;/button&gt;

&lt;/EditForm&gt;

ตัวอย่าง Form Definition (JSON)

var contactForm = new FormDefinition

{

Title = "Contact Form",

Description = "กรอกข้อมูลติดต่อ",

SubmitUrl = "/api/contacts",

Fields = new List&lt;DynamicField&gt;

{

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&lt;T&gt; — Generic Data Table Component

@typeparam T

&lt;div class="table-responsive"&gt;

@if (ShowSearch)

{

&lt;input type="text" class="form-control mb-3"

placeholder="Search..."

@bind-value="SearchText"

@bind-value:event="oninput" /&gt;

}

&lt;table class="table table-hover"&gt;

&lt;thead&gt;

&lt;tr&gt;

@foreach (var col in Columns)

{

&lt;th style="cursor:pointer"

@onclick="() =&gt; Sort(col.Field)"&gt;

@col.Title

@if (_sortField == col.Field)

{

&lt;span&gt;@(_sortAsc ? "▲" : "▼")&lt;/span&gt;

}

&lt;/th&gt;

}

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

@foreach (var item in PagedItems)

{

&lt;tr @onclick="() =&gt; OnRowClick.InvokeAsync(item)"

style="cursor:pointer"&gt;

@RowTemplate(item)

&lt;/tr&gt;

}

&lt;/tbody&gt;

&lt;/table&gt;

&lt;nav&gt;

&lt;ul class="pagination"&gt;

@for (var i = 1; i &lt;= TotalPages; i++)

{

var page = i;

&lt;li class="page-item @(page == CurrentPage ? "active" : "")"&gt;

&lt;a class="page-link" @onclick="() =&gt; GoToPage(page)"&gt;@page&lt;/a&gt;

&lt;/li&gt;

}

&lt;/ul&gt;

&lt;/nav&gt;

&lt;/div&gt;

@code {

[Parameter] public List&lt;T&gt; Items { get; set; } = new();

[Parameter] public List&lt;ColumnDef&gt; Columns { get; set; } = new();

[Parameter] public RenderFragment&lt;T&gt; RowTemplate { get; set; }

[Parameter] public EventCallback&lt;T&gt; 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 =&gt; (int)Math.Ceiling(Items.Count / (double)PageSize);

private List&lt;T&gt; PagedItems =&gt; Items

.Skip((CurrentPage - 1) * PageSize)

.Take(PageSize).ToList();

private void GoToPage(int page) =&gt; 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;

}

ใช้งาน

&lt;DataTable Items="users" Columns="columns" PageSize="20"&gt;

&lt;RowTemplate Context="user"&gt;

&lt;td&gt;@user.Name&lt;/td&gt;

&lt;td&gt;@user.Email&lt;/td&gt;

&lt;td&gt;@user.Role&lt;/td&gt;

&lt;/RowTemplate&gt;

&lt;/DataTable&gt;

Console.WriteLine("Blazor Component Library:");

Console.WriteLine(" DataTable&lt;T&gt; — 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