Technology

C# Blazor Best Practices ที่ต้องรู้

c blazor best practices ทตองร
C# Blazor Best Practices ที่ต้องรู้ | SiamCafe Blog
2026-01-29· อ. บอม — SiamCafe.net· 10,272 คำ

Blazor Best Practices

C# Blazor Best Practices Component Design State Management Performance Authentication Testing Blazor Server WebAssembly Hybrid .NET 8 Production

ModeExecutionLatencyOfflineSEOเหมาะกับ
Blazor ServerServer (SignalR)ต่ำไม่ได้ดี (SSR)Internal apps, real-time
Blazor WASMBrowser (WebAssembly)สูง (initial)ได้ (PWA)ต้อง Pre-renderSPA, offline apps
Blazor AutoServer → WASMต่ำ → ต่ำได้ (หลัง switch)ดี.NET 8 recommended
Blazor HybridNative (MAUI)ต่ำมากได้N/ADesktop/Mobile app

Component Patterns

# === Blazor Component Patterns ===

# // ProductCard.razor — Single Responsibility Component
# @code {
#     [Parameter] public ProductDto Product { get; set; } = null!;
#     [Parameter] public EventCallback<int> OnAddToCart { get; set; }
#     [Parameter] public RenderFragment? ChildContent { get; set; }
#
#     private async Task HandleAddToCart()
#     {
#         await OnAddToCart.InvokeAsync(Product.Id);
#     }
# }
#
# // GenericList.razor — Template Component
# @typeparam TItem
# @code {
#     [Parameter] public IEnumerable<TItem> Items { get; set; } = [];
#     [Parameter] public RenderFragment<TItem> ItemTemplate { get; set; } = null!;
#     [Parameter] public RenderFragment? EmptyTemplate { get; set; }
# }
# <div>
#     @if (Items.Any())
#     {
#         @foreach (var item in Items)
#         {
#             @ItemTemplate(item)
#         }
#     }
#     else
#     {
#         @EmptyTemplate
#     }
# </div>
#
# // Usage:
# <GenericList Items="products" Context="product">
#     <ItemTemplate>
#         <ProductCard Product="product" OnAddToCart="AddToCart" />
#     </ItemTemplate>
#     <EmptyTemplate>
#         <p>No products found</p>
#     </EmptyTemplate>
# </GenericList>

from dataclasses import dataclass

@dataclass
class ComponentPattern:
    pattern: str
    use_case: str
    example: str
    benefit: str

patterns = [
    ComponentPattern("Parameter + EventCallback", "Parent-child communication",
        "[Parameter] public string Title { get; set; }", "Clear data flow"),
    ComponentPattern("CascadingValue", "Deep component tree data",
        "", "Avoid prop drilling"),
    ComponentPattern("RenderFragment", "Template/slot components",
        "[Parameter] public RenderFragment ChildContent", "Flexible composition"),
    ComponentPattern("@typeparam", "Generic reusable components",
        "@typeparam TItem on GenericList", "Type-safe reusable lists"),
    ComponentPattern("@key", "Efficient list rendering",
        "@foreach with @key='item.Id'", "Prevent full list re-render"),
    ComponentPattern("Base Component", "Shared logic inheritance",
        "ComponentBase with common methods", "DRY principle"),
]

print("=== Component Patterns ===")
for p in patterns:
    print(f"  [{p.pattern}] Use: {p.use_case}")
    print(f"    Example: {p.example}")
    print(f"    Benefit: {p.benefit}")

State and Performance

# === State Management and Performance ===

# // State Container Pattern
# public class CartState
# {
#     public List<CartItem> Items { get; private set; } = [];
#     public decimal Total => Items.Sum(i => i.Price * i.Quantity);
#     public event Action? OnChange;
#
#     public void AddItem(CartItem item)
#     {
#         var existing = Items.FirstOrDefault(i => i.ProductId == item.ProductId);
#         if (existing != null)
#             existing.Quantity += item.Quantity;
#         else
#             Items.Add(item);
#         NotifyStateChanged();
#     }
#
#     private void NotifyStateChanged() => OnChange?.Invoke();
# }
#
# // Register in DI
# builder.Services.AddScoped<CartState>();
#
# // Use in Component
# @inject CartState Cart
# @implements IDisposable
#
# protected override void OnInitialized()
# {
#     Cart.OnChange += StateHasChanged;
# }
#
# public void Dispose()
# {
#     Cart.OnChange -= StateHasChanged;
# }

# // Performance: Virtualization
# <Virtualize Items="products" Context="product">
#     <ProductCard Product="product" />
# </Virtualize>
#
# // Performance: ShouldRender override
# protected override bool ShouldRender()
# {
#     return _hasChanged;
# }

@dataclass
class PerfTip:
    tip: str
    problem: str
    solution: str
    impact: str

tips = [
    PerfTip("Use @key", "List re-renders all items on change",
        "@key='item.Id' on each item", "50-90% fewer DOM updates"),
    PerfTip("Virtualize", "Rendering 1000+ items causes lag",
        " renders only visible", "Instant scroll for large lists"),
    PerfTip("ShouldRender", "Component re-renders unnecessarily",
        "Override ShouldRender() return false when no change", "Eliminate wasted renders"),
    PerfTip("Dispose handlers", "Event handlers cause memory leaks",
        "Implement IDisposable, unsubscribe in Dispose()", "Prevent memory growth"),
    PerfTip("Lazy Assembly", "WASM downloads all DLLs upfront",
        "LazyAssemblyLoader for rarely-used pages", "Reduce initial download 30-60%"),
    PerfTip("Pre-render SSR", "WASM shows blank before JS loads",
        "Use InteractiveAuto for SSR + WASM", "Instant first paint"),
]

print("\n=== Performance Tips ===")
for t in tips:
    print(f"  [{t.tip}] Problem: {t.problem}")
    print(f"    Fix: {t.solution}")
    print(f"    Impact: {t.impact}")

Auth and Testing

# === Authentication and Testing ===

# // Authentication Setup (.NET 8)
# builder.Services.AddAuthentication()
#     .AddCookie()
#     .AddJwtBearer(options => {
#         options.TokenValidationParameters = new() {
#             ValidateIssuer = true,
#             ValidIssuer = "https://myapp.com",
#             ValidateAudience = true,
#             ValidAudience = "myapp-api",
#             IssuerSigningKey = new SymmetricSecurityKey(key),
#         };
#     });
#
# // AuthorizeView in Razor
# <AuthorizeView Roles="Admin">
#     <Authorized>
#         <AdminPanel />
#     </Authorized>
#     <NotAuthorized>
#         <p>Access Denied</p>
#     </NotAuthorized>
# </AuthorizeView>
#
# // [Authorize] on page
# @page "/admin"
# @attribute [Authorize(Roles = "Admin")]

# // bUnit Testing
# [Fact]
# public void ProductCard_ShowsName()
# {
#     using var ctx = new TestContext();
#     var product = new ProductDto { Id = 1, Name = "Test", Price = 99 };
#     var cut = ctx.RenderComponent<ProductCard>(
#         parameters => parameters.Add(p => p.Product, product));
#     cut.Find("h3").TextContent.ShouldBe("Test");
# }
#
# [Fact]
# public void AddToCart_FiresCallback()
# {
#     using var ctx = new TestContext();
#     var clicked = false;
#     var cut = ctx.RenderComponent<ProductCard>(parameters => parameters
#         .Add(p => p.Product, product)
#         .Add(p => p.OnAddToCart, EventCallback.Factory.Create<int>(this, id => clicked = true)));
#     cut.Find("button").Click();
#     clicked.ShouldBeTrue();
# }

@dataclass
class BestPractice:
    category: str
    practice: str
    why: str

practices = [
    BestPractice("Component", "แยก Component เล็ก ทำหน้าที่เดียว", "Reusable, testable, maintainable"),
    BestPractice("State", "ใช้ State Container Pattern + DI", "Centralized state, predictable updates"),
    BestPractice("Performance", "ใช้ @key Virtualize ShouldRender", "Smooth UI, less memory"),
    BestPractice("Auth", "Server-side validation เสมอ ไม่พึ่ง UI", "Security cannot be client-side only"),
    BestPractice("Testing", "ใช้ bUnit test ทุก Component", "Catch bugs early, safe refactor"),
    BestPractice("Error", "ใช้ ErrorBoundary wrap components", "Graceful error handling"),
    BestPractice("DI", "Register services ด้วย Scoped lifetime", "Correct lifecycle for Blazor"),
    BestPractice("Dispose", "Implement IDisposable ทุก Component ที่ subscribe", "Prevent memory leaks"),
]

print("Best Practices:")
for b in practices:
    print(f"  [{b.category}] {b.practice}")
    print(f"    Why: {b.why}")

เคล็ดลับ

Blazor คืออะไร

Web Framework Microsoft C# แทน JavaScript Interactive Web App Server SignalR WebAssembly Browser Hybrid MAUI Razor Component .NET 8 Auto Render

Component Design ควรทำอย่างไร

เล็ก Single Responsibility Parameter EventCallback CascadingValue RenderFragment Template @key List Base Component Layout

จัดการ State อย่างไร

Field Property CascadingValue State Container DI ProtectedLocalStorage Fluxor Redux StateHasChanged เฉพาะจำเป็น Scoped Lifetime

ปรับ Performance อย่างไร

@key List Virtualize ShouldRender Lazy Assembly LazyAssemblyLoader Pre-render SSR IDisposable Memory Leak StateHasChanged DOM Update

สรุป

C# Blazor Best Practices Component Design State Management Performance @key Virtualize Authentication bUnit Testing .NET 8 Auto Mode Production Deployment

📖 บทความที่เกี่ยวข้อง

gRPC Protobuf Best Practices ที่ต้องรู้อ่านบทความ → Rust Actix Web Best Practices ที่ต้องรู้อ่านบทความ → Weights Biases Best Practices ที่ต้องรู้อ่านบทความ → Python FastAPI Best Practices ที่ต้องรู้อ่านบทความ → LlamaIndex RAG Best Practices ที่ต้องรู้อ่านบทความ →

📚 ดูบทความทั้งหมด →