Blazor Best Practices
C# Blazor Best Practices Component Design State Management Performance Authentication Testing Blazor Server WebAssembly Hybrid .NET 8 Production
| Mode | Execution | Latency | Offline | SEO | เหมาะกับ |
|---|---|---|---|---|---|
| Blazor Server | Server (SignalR) | ต่ำ | ไม่ได้ | ดี (SSR) | Internal apps, real-time |
| Blazor WASM | Browser (WebAssembly) | สูง (initial) | ได้ (PWA) | ต้อง Pre-render | SPA, offline apps |
| Blazor Auto | Server → WASM | ต่ำ → ต่ำ | ได้ (หลัง switch) | ดี | .NET 8 recommended |
| Blazor Hybrid | Native (MAUI) | ต่ำมาก | ได้ | N/A | Desktop/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}")
เคล็ดลับ
- @key: ใช้ @key ทุก List Rendering ป้องกัน Re-render ทั้งหมด
- Dispose: Implement IDisposable ทุก Component ที่ Subscribe Event
- Auto Mode: ใช้ InteractiveAuto ใน .NET 8 ได้ SSR + WASM อัตโนมัติ
- bUnit: เขียน bUnit Test ทุก Component ตรวจ Render + Events
- ErrorBoundary: Wrap Component ด้วย ErrorBoundary ป้องกัน Crash
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
