TL;DR
Blazor Server + Semantic Kernel + PostgreSQL to stack, który pozwala budować pełnoprawne AI-powered aplikacje bez przełączania się między językami. Jeden język, jeden debugger, jeden ekosystem. A gdy potrzebujesz JS - masz IJSRuntime i interop działa bezproblemowo.
Problem: Context switching zabija produktywność
Klasyczny stack webowy wymaga żonglowania:
- Frontend: TypeScript/JavaScript, React/Vue/Angular
- Backend: C#/.NET lub Node.js
- AI: Python (LangChain, OpenAI SDK)
- Baza: SQL + ORM
Każdy przeskok między językami to:
- Zmiana mental model
- Inne konwencje nazewnictwa
- Inne narzędzia do debugowania
- Inne ekosystemy pakietów
Rozwiązanie: C# everywhere
┌─────────────────────────────────────────┐
│ Blazor Server │
│ ┌─────────────────────────────────┐ │
│ │ UI Components (C# + Razor) │ │
│ └──────────────┬──────────────────┘ │
│ │ SignalR │
│ ┌──────────────▼──────────────────┐ │
│ │ Services (C#) │ │
│ │ ├── BusinessLogic │ │
│ │ ├── SemanticKernel (AI) │ │
│ │ └── EF Core (Data) │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
Jeden język od UI do AI
// Komponent Blazor
@code {
[Inject] private IChatService ChatService { get; set; }
private async Task SendMessage(string message)
{
// Ten sam język co w serwisie AI
await foreach (var chunk in ChatService.StreamResponseAsync(message))
{
response += chunk;
StateHasChanged(); // Real-time UI update
}
}
}
// Serwis AI - ten sam projekt, te same typy
public class ChatService : IChatService
{
private readonly Kernel _kernel;
public async IAsyncEnumerable<string> StreamResponseAsync(string message)
{
var chat = _kernel.GetRequiredService<IChatCompletionService>();
await foreach (var chunk in chat.GetStreamingChatMessageContentsAsync(history))
{
yield return chunk.Content ?? "";
}
}
}
Zero serializacji między warstwami - te same modele C# od Razora po bazę danych.
Semantic Kernel vs LangChain
| Aspekt | Semantic Kernel (.NET) | LangChain (Python) |
|---|---|---|
| Typowanie | Silne, compile-time | Dynamiczne, runtime |
| Integracja z .NET | Natywna | Wymaga API bridge |
| Performance | Excellent | Good |
| Debugging | VS/Rider z full support | Print statements |
| Async | IAsyncEnumerable natywnie |
Asyncio complexity |
Przykład: AI Moderation w jednej klasie
public class CommentModerationService
{
private readonly Kernel _kernel;
public async Task<ModerationResult> ModerateAsync(string content)
{
var chat = _kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory();
history.AddSystemMessage(
"Classify as OK, SPAM, or TOXIC. Respond with one word only.");
history.AddUserMessage(content);
var response = await chat.GetChatMessageContentAsync(history);
return response.Content?.Trim().ToUpper() switch
{
"OK" => ModerationResult.Approved,
"SPAM" => ModerationResult.Spam,
"TOXIC" => ModerationResult.Toxic,
_ => ModerationResult.NeedsReview
};
}
}
Cały flow AI - od promptu po decyzję - w typowanym C#.
SignalR: Real-time bez WebSocket boilerplate
Blazor Server używa SignalR pod spodem. Streaming AI responses? Automatyczny:
// Serwer streamuje
await foreach (var token in aiService.GenerateAsync(prompt))
{
responseText += token;
StateHasChanged(); // UI się aktualizuje w real-time
}
Żadnego:
- Manualnego WebSocket managementu
- Custom event systemu
- Reconnection logic
SignalR ogarnia wszystko.
JavaScript Interop: Gdy naprawdę potrzebujesz JS
Blazor nie eliminuje JS - daje kontrolę nad tym, kiedy go używasz:
// C# komponent
@inject IJSRuntime JS
private async Task InitCropper(ElementReference imageElement)
{
// Wywołanie JS library (Cropper.js)
await JS.InvokeVoidAsync("initCropper", imageElement, new
{
aspectRatio = 16.0 / 9.0,
viewMode = 1
});
}
private async Task<CroppedImageResult> GetCroppedImage()
{
// JS zwraca dane do C#
return await JS.InvokeAsync<CroppedImageResult>("getCroppedImageData");
}
// wwwroot/js/imagecrop.js
window.initCropper = (element, options) => {
window.cropper = new Cropper(element, options);
};
window.getCroppedImageData = () => {
const canvas = window.cropper.getCroppedCanvas();
return {
dataUrl: canvas.toDataURL('image/webp', 0.9),
width: canvas.width,
height: canvas.height
};
};
Best of both worlds: C# dla logiki, JS dla DOM manipulation gdzie potrzebne.
DbContextFactory: Blazor Server done right
Blazor Server ma długotrwałe połączenia. Standardowy DbContext per-request nie działa:
// ❌ Źle - DbContext żyje za długo
public class MyService
{
private readonly AppDbContext _db; // Problemy z concurrent access
}
// ✅ Dobrze - krótkotrwałe konteksty
public class MyService
{
private readonly IDbContextFactory<AppDbContext> _dbFactory;
public async Task<List<Item>> GetItemsAsync()
{
await using var db = await _dbFactory.CreateDbContextAsync();
return await db.Items.ToListAsync();
}
}
Rejestracja:
builder.Services.AddPooledDbContextFactory<AppDbContext>(
options => options.UseNpgsql(connectionString));
Deployment: Jedna aplikacja, zero orchestration
Azure App Service
└── Blazor Server App
├── UI (Razor Components)
├── API endpoints (minimal API)
├── AI Services (Semantic Kernel)
├── Background jobs (IHostedService)
└── Static files (wwwroot)
Nie potrzebujesz:
- Osobnego frontend servera
- API Gateway
- Python microservice dla AI
- Message queue dla async tasks
Jedna aplikacja .NET robi wszystko.
Kiedy NIE używać Blazor Server?
- Offline-first apps - wymaga stałego połączenia
- Massive scale (miliony użytkowników) - każdy user = SignalR connection
- SEO-critical public sites - prerendering działa, ale dodaje complexity
- Team bez doświadczenia C# - krzywa uczenia jest realna
Podsumowanie
| Korzyść | Impact |
|---|---|
| Jeden język | -50% context switching |
| Silne typowanie | Błędy w compile-time, nie runtime |
| Semantic Kernel | AI bez Python dependency |
| SignalR built-in | Real-time bez boilerplate |
| JS Interop | Escape hatch gdy potrzebny |
| Jeden deployment | Prostsze DevOps |
Stack Blazor Server + Semantic Kernel + PostgreSQL to nie silver bullet. Ale dla AI-powered aplikacji webowych, gdzie liczy się szybkość developmentu i maintainability - to obecnie najefektywniejsza kombinacja w ekosystemie .NET.
Cały kod z tego artykułu pochodzi z produkcyjnej aplikacji. Jeśli chcesz zobaczyć pełną implementację - sprawdź case study DevFolio.
Potrzebujesz pomocy z AI?
Porozmawiajmy o tym, jak mogę pomóc Twojej firmie wdrożyć AI.
Skontaktuj się
💬 Komentarze (0)
Zostaw komentarz
Bądź pierwszy, który skomentuje!