Blazor ????????? 12-Factor App ?????????????????????
Blazor ???????????? web framework ????????? Microsoft ????????????????????????????????? interactive web UI ???????????? C# ????????? JavaScript ?????????????????????????????? Blazor Server (render ???????????? server ???????????? SignalR) ????????? Blazor WebAssembly (render ???????????? client ?????? browser) ???????????????????????????????????????????????? ASP.NET Core ecosystem
12-Factor App ???????????? methodology ????????????????????????????????? software-as-a-service applications ??????????????? ??????????????????????????? Heroku ?????? 12 ????????????????????? Codebase (one codebase tracked in revision control), Dependencies (explicitly declare and isolate), Config (store config in environment), Backing Services (treat as attached resources), Build Release Run (strictly separate stages), Processes (execute as stateless processes), Port Binding (export services via port binding), Concurrency (scale out via process model), Disposability (maximize robustness with fast startup and graceful shutdown), Dev/Prod Parity (keep environments similar), Logs (treat logs as event streams), Admin Processes (run admin tasks as one-off processes)
??????????????? 12-Factor ???????????????????????? Blazor ??????????????? application scalable, maintainable, portable deploy ?????????????????? cloud provider (Azure, AWS, GCP) ?????????????????? container ???????????????
????????????????????? Blazor Project ????????? 12-Factor
??????????????? Blazor project structure
# === Blazor 12-Factor Project Setup ===
# 1. Create Blazor Server App
dotnet new blazorserver -o MyApp.Web --framework net8.0
cd MyApp.Web
# 2. Project Structure (12-Factor compliant)
# MyApp/
# MyApp.Web/ ??? Blazor Server app
# MyApp.Core/ ??? Business logic (shared)
# MyApp.Infrastructure/ ??? Data access, external services
# MyApp.Tests/ ??? Unit & integration tests
# docker-compose.yml
# Dockerfile
# 3. Create Solution
dotnet new sln -n MyApp
dotnet new classlib -o MyApp.Core --framework net8.0
dotnet new classlib -o MyApp.Infrastructure --framework net8.0
dotnet new xunit -o MyApp.Tests --framework net8.0
dotnet sln add MyApp.Web/MyApp.Web.csproj
dotnet sln add MyApp.Core/MyApp.Core.csproj
dotnet sln add MyApp.Infrastructure/MyApp.Infrastructure.csproj
dotnet sln add MyApp.Tests/MyApp.Tests.csproj
# 4. Add References
dotnet add MyApp.Web reference MyApp.Core MyApp.Infrastructure
dotnet add MyApp.Infrastructure reference MyApp.Core
dotnet add MyApp.Tests reference MyApp.Core MyApp.Infrastructure
# 5. Dockerfile (Factor V: Build, Release, Run)
cat > Dockerfile << 'EOF'
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.sln .
COPY MyApp.Web/*.csproj MyApp.Web/
COPY MyApp.Core/*.csproj MyApp.Core/
COPY MyApp.Infrastructure/*.csproj MyApp.Infrastructure/
RUN dotnet restore
COPY . .
RUN dotnet publish MyApp.Web -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "MyApp.Web.dll"]
EOF
# 6. docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
web:
build: .
ports:
- "8080:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__Default=Host=db;Database=myapp;Username=app;Password=secret
- Redis__ConnectionString=redis:6379
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:
EOF
echo "Blazor 12-Factor project created"
Configuration ????????? Environment Management
Factor III: Config ??? ?????????????????? configuration ???????????? environment
// === Factor III: Configuration Management ===
// Program.cs ??? Blazor Server Configuration
// File: Program.cs
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Factor III: Config from environment variables
builder.Configuration
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables() // Override with env vars
.AddCommandLine(args);
// Strongly-typed configuration (Factor III)
builder.Services.Configure<DatabaseConfig>(
builder.Configuration.GetSection("Database"));
builder.Services.Configure<CacheConfig>(
builder.Configuration.GetSection("Cache"));
builder.Services.Configure<AuthConfig>(
builder.Configuration.GetSection("Auth"));
// Factor IV: Backing Services as attached resources
var dbConnection = builder.Configuration.GetConnectionString("Default");
var redisConnection = builder.Configuration["Redis:ConnectionString"];
// Register services (Factor VI: Stateless processes)
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
// Factor XI: Logs as event streams
builder.Logging.ClearProviders();
builder.Logging.AddConsole(); // stdout
builder.Logging.AddJsonConsole(options =>
{
options.IncludeScopes = true;
options.TimestampFormat = "yyyy-MM-ddTHH:mm:ssZ";
});
var app = builder.Build();
// Factor VII: Port Binding
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
// Configuration classes
public class DatabaseConfig
{
public string ConnectionString { get; set; } = "";
public int MaxPoolSize { get; set; } = 20;
public int CommandTimeout { get; set; } = 30;
}
public class CacheConfig
{
public string ConnectionString { get; set; } = "";
public int DefaultTtlMinutes { get; set; } = 60;
}
public class AuthConfig
{
public string JwtSecret { get; set; } = "";
public int TokenExpiryMinutes { get; set; } = 60;
public string Issuer { get; set; } = "";
}
Dependency Injection ????????? Backing Services
Factor II ????????? IV: Dependencies ????????? Backing Services
// === Factor II: Dependencies & Factor IV: Backing Services ===
// File: MyApp.Core/Interfaces/IProductRepository.cs
namespace MyApp.Core.Interfaces;
public interface IProductRepository
{
Task<IEnumerable<Product>> GetAllAsync();
Task<Product?> GetByIdAsync(int id);
Task<Product> CreateAsync(Product product);
Task UpdateAsync(Product product);
Task DeleteAsync(int id);
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
public string Category { get; set; } = "";
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
// File: MyApp.Infrastructure/Repositories/ProductRepository.cs
namespace MyApp.Infrastructure.Repositories;
using Microsoft.Extensions.Logging;
using MyApp.Core.Interfaces;
using System.Collections.Concurrent;
public class ProductRepository : IProductRepository
{
private readonly ILogger<ProductRepository> _logger;
private static readonly ConcurrentDictionary<int, Product> _store = new();
private static int _nextId = 1;
public ProductRepository(ILogger<ProductRepository> logger)
{
_logger = logger;
}
public Task<IEnumerable<Product>> GetAllAsync()
{
_logger.LogInformation("Getting all products, count: {Count}", _store.Count);
return Task.FromResult<IEnumerable<Product>>(_store.Values.ToList());
}
public Task<Product?> GetByIdAsync(int id)
{
_store.TryGetValue(id, out var product);
return Task.FromResult(product);
}
public Task<Product> CreateAsync(Product product)
{
product.Id = Interlocked.Increment(ref _nextId);
product.CreatedAt = DateTime.UtcNow;
product.UpdatedAt = DateTime.UtcNow;
_store[product.Id] = product;
_logger.LogInformation("Created product {Id}: {Name}", product.Id, product.Name);
return Task.FromResult(product);
}
public Task UpdateAsync(Product product)
{
product.UpdatedAt = DateTime.UtcNow;
_store[product.Id] = product;
return Task.CompletedTask;
}
public Task DeleteAsync(int id)
{
_store.TryRemove(id, out _);
return Task.CompletedTask;
}
}
// File: MyApp.Infrastructure/DependencyInjection.cs
// Register all infrastructure services
namespace MyApp.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using MyApp.Core.Interfaces;
using MyApp.Infrastructure.Repositories;
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(
this IServiceCollection services)
{
// Factor IV: Backing services as attached resources
services.AddScoped<IProductRepository, ProductRepository>();
return services;
}
}
Build, Release, Run ????????? CI/CD
Factor V: Build Release Run ??? CI/CD pipeline
# === Factor V: Build, Release, Run ===
# GitHub Actions CI/CD Pipeline
cat > .github/workflows/ci-cd.yml << 'EOF'
name: Blazor CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
DOTNET_VERSION: '8.0.x'
REGISTRY: ghcr.io
IMAGE_NAME: }
jobs:
# BUILD stage
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: }
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore -c Release
- name: Test
run: dotnet test --no-build -c Release --verbosity normal
- name: Publish
run: dotnet publish MyApp.Web -c Release -o publish
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: blazor-app
path: publish/
# RELEASE stage (Docker)
release:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: }
username: }
password: }
- name: Build & Push Docker
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
}/}:}
}/}:latest
# RUN stage (Deploy)
deploy:
needs: release
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/blazor-app \
web=}/}:}
kubectl rollout status deployment/blazor-app
EOF
echo "CI/CD pipeline configured"
Logging, Monitoring ????????? Disposability
Factor IX ????????? XI: Disposability ????????? Logs
// === Factor IX: Disposability & Factor XI: Logs ===
// File: MyApp.Web/HealthChecks/AppHealthCheck.cs
namespace MyApp.Web.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
// Factor IX: Fast startup, graceful shutdown
public class AppHealthCheck : IHealthCheck
{
private readonly ILogger<AppHealthCheck> _logger;
public AppHealthCheck(ILogger<AppHealthCheck> logger)
{
_logger = logger;
}
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
// Check database connectivity
var isHealthy = true; // Replace with actual check
if (isHealthy)
{
_logger.LogDebug("Health check passed");
return Task.FromResult(HealthCheckResult.Healthy("OK"));
}
_logger.LogWarning("Health check failed");
return Task.FromResult(HealthCheckResult.Unhealthy("Database unavailable"));
}
}
// Factor XI: Structured Logging
// File: MyApp.Core/Services/OrderService.cs
namespace MyApp.Core.Services;
using Microsoft.Extensions.Logging;
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public async Task<OrderResult> ProcessOrder(int orderId)
{
using var scope = _logger.BeginScope(
new Dictionary<string, object>
{
["OrderId"] = orderId,
["CorrelationId"] = Guid.NewGuid().ToString()
});
_logger.LogInformation("Processing order {OrderId}", orderId);
try
{
// Process order
await Task.Delay(100); // Simulate work
_logger.LogInformation(
"Order {OrderId} processed successfully, Amount: {Amount}",
orderId, 1500.00m);
return new OrderResult { Success = true, OrderId = orderId };
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to process order {OrderId}: {Error}",
orderId, ex.Message);
return new OrderResult { Success = false, Error = ex.Message };
}
}
}
public class OrderResult
{
public bool Success { get; set; }
public int OrderId { get; set; }
public string? Error { get; set; }
}
// Factor IX: Graceful Shutdown in Program.cs
// app.Lifetime.ApplicationStopping.Register(() =>
// {
// logger.LogInformation("Application shutting down gracefully");
// // Drain connections, finish in-flight requests
// });
FAQ ??????????????????????????????????????????
Q: Blazor Server ????????? Blazor WebAssembly ??????????????????????????????????
A: Blazor Server render ????????? server ????????? UI updates ???????????? SignalR (WebSocket) ??????????????? initial load ????????????, ????????????????????? server resources ??????????????????, ?????????????????? thin clients ????????? ????????????????????? ???????????? connection ????????????, latency ????????????????????? network, server load ???????????????????????? users ???????????? Blazor WebAssembly (WASM) ??????????????? browser ??????????????? offline ?????????, ????????????????????? server connection, CDN deploy ????????? ????????????????????? initial download ???????????? (2-5MB), ?????????????????????????????? server resources ????????? Blazor United (.NET 8) ?????????????????????????????? ??????????????? render mode ????????? component ??????????????????????????????????????????????????? project ????????????
Q: 12-Factor ?????????????????????????????????????
A: ?????????????????????????????????????????? modern cloud applications 12-Factor ??????????????? Portable deploy ?????????????????? cloud (Azure, AWS, GCP, on-premise), Scalable scale horizontally ?????????????????????, Maintainable ????????? concerns ??????????????????, CI/CD friendly build, release, run ??????????????????, Container ready ????????????????????? Docker/Kubernetes ??????????????? ?????????????????????????????????????????????????????? 12 ???????????????????????? ???????????????????????? Config (Factor III), Dependencies (Factor II), Logs (Factor XI) ???????????? ????????????????????????????????????????????????????????????
Q: Blazor ?????? state management ????????????????????? ?????????????????? Factor VI (Stateless)?
A: Factor VI ???????????????????????? processes ???????????? stateless ????????????????????? state ?????? memory ?????????????????? Blazor Server ????????? Scoped services ?????????????????? per-circuit state (ok ?????????????????? session), Redis/Database ?????????????????? shared state (?????????????????????????????? 12-Factor), Cascading Parameters ?????????????????? component tree, Fluxor (Flux/Redux pattern) ?????????????????? complex state ?????????????????? Blazor WASM ????????? browser localStorage/sessionStorage, State containers (in-memory ok ?????????????????????????????? browser), API calls ?????????????????? persistent state ???????????? state ????????????????????? persist ?????????????????????????????? backing service (database, Redis) ?????????????????? process memory
Q: Deploy Blazor ?????? Kubernetes ????????????????????????????
A: ??????????????? Docker image ????????? Dockerfile ?????????????????????????????????????????? (multi-stage build), ??????????????? Kubernetes manifests Deployment, Service, Ingress, ConfigMap, Secret ????????????????????? environment variables ???????????? ConfigMap/Secret (Factor III), ????????? Health checks (liveness + readiness) ?????????????????? Factor IX, ???????????? HPA (Horizontal Pod Autoscaler) ?????????????????? Factor VIII (Concurrency), ????????? Helm chart ?????????????????? deployment ???????????? environments ?????????????????? Blazor Server ???????????????????????? sticky sessions (SignalR ???????????? connect ???????????? server ????????????) ????????? Kubernetes Ingress annotation nginx.ingress.kubernetes.io/affinity: cookie