Wszystkie artykuły

Blazor Server + Semantic Kernel: Jeden stack do AI-powered aplikacji

Dlaczego C# na frontendzie i backendzie z integracją AI to game-changer dla produktywności i jakości kodu.

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.

DT

Damian Tarnowski

AI & .NET Architect

💬 Komentarze (0)

?

Zostaw komentarz

💭

Bądź pierwszy, który skomentuje!

Potrzebujesz pomocy z AI?

Porozmawiajmy o tym, jak mogę pomóc Twojej firmie wdrożyć AI.

Skontaktuj się

Asystent AI Damiana

Online • Odpowiadam natychmiast

Cześć! 👋

Jestem asystentem AI Damiana. Zapytaj mnie o technologie, projekty lub jak mogę Ci pomóc!

Powered by GPT-5.6 • Odpowiedzi mogą zawierać błędy

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please reload the page.