Blazor is een van die frameworks waar twee werelden botsen. Je schrijft C#-logica en HTML-markup in hetzelfde bestand, met component parameters, event callbacks en een render lifecycle die zijn eigen ideeën heeft over wanneer dingen updaten.

De meeste AI-tools worstelen daarmee. Ze geven je React-achtig advies voor een Blazor-probleem, of genereren code die compileert maar niet rendert wanneer je het verwacht.

Claude Code is anders. Het leest je .razor-bestanden, begrijpt de component-hiërarchie en kent het verschil tussen StateHasChanged en InvokeAsync. Laat me je laten zien hoe dat er in de praktijk uitziet.


Een component vanaf nul bouwen

Stel, je hebt een notificatiebanner nodig. In plaats van zelf de structuur op te zetten, beschrijf je wat je wilt:

“Maak een Blazor component NotificationBanner die een bericht toont met een severity level (info, warning, error). Hij moet wegklikbaar zijn en na 5 seconden automatisch verdwijnen voor info-berichten.”

Claude Code maakt het .razor-bestand:

@if (IsVisible)
{
    <div class="notification-banner @SeverityCss" role="alert">
        <span class="notification-message">@Message</span>
        <button class="notification-close" @onclick="Dismiss">&times;</button>
    </div>
}

@code {
    [Parameter] public string Message { get; set; } = string.Empty;
    [Parameter] public Severity Level { get; set; } = Severity.Info;
    [Parameter] public EventCallback OnDismissed { get; set; }

    private bool IsVisible { get; set; } = true;
    private Timer? _autoHideTimer;

    private string SeverityCss => Level switch
    {
        Severity.Warning => "banner-warning",
        Severity.Error => "banner-error",
        _ => "banner-info"
    };

    protected override void OnParametersSet()
    {
        IsVisible = true;

        if (Level == Severity.Info)
        {
            _autoHideTimer?.Dispose();
            _autoHideTimer = new Timer(_ =>
                InvokeAsync(Dismiss), null, 5000, Timeout.Infinite);
        }
    }

    private async Task Dismiss()
    {
        IsVisible = false;
        _autoHideTimer?.Dispose();
        await OnDismissed.InvokeAsync();
    }

    public void Dispose() => _autoHideTimer?.Dispose();
}

Let op wat het goed doet: InvokeAsync voor de timer-callback (omdat die niet op de render-thread zit), OnParametersSet in plaats van OnInitialized (zodat het component reageert op nieuwe berichten), en een nette IDisposable-implementatie. Dit zijn de details waarvoor je normaal tien minuten documentatie zou doorspitten.


Component parameters en cascading values

Waar Claude Code echt uitblinkt is parent-child communicatie. Blazor’s model met [Parameter], [CascadingParameter] en EventCallback is krachtig maar verbose. Beschrijf de relatie en Claude Code regelt de bedrading.

“Ik heb een DataGrid component nodig met een ColumnDefinition child component. De parent stuurt de databron mee, en elke kolom bepaalt hoe een veld gerenderd wordt.”

Claude Code genereert de parent:

<CascadingValue Value="this">
    <table class="data-grid">
        <thead>
            <tr>@ChildContent</tr>
        </thead>
        <tbody>
            @foreach (var item in Items)
            {
                <tr>
                    @foreach (var column in _columns)
                    {
                        <td>@column.CellTemplate(item)</td>
                    }
                </tr>
            }
        </tbody>
    </table>
</CascadingValue>

@code {
    [Parameter] public IEnumerable<object> Items { get; set; } = [];
    [Parameter] public RenderFragment? ChildContent { get; set; }

    private readonly List<ColumnDefinition> _columns = [];

    internal void AddColumn(ColumnDefinition column)
    {
        _columns.Add(column);
        StateHasChanged();
    }
}

En het child component dat zichzelf registreert bij de parent:

@code {
    [CascadingParameter] public DataGrid? ParentGrid { get; set; }
    [Parameter] public string Title { get; set; } = string.Empty;
    [Parameter] public RenderFragment<object>? CellTemplate { get; set; }

    protected override void OnInitialized()
    {
        ParentGrid?.AddColumn(this);
    }
}

Het weet dat je CascadingValue gebruikt om de parent-referentie door te geven, en het plaatst de kolomregistratie in OnInitialized — niet in OnParametersSet, want dat zou dubbele registraties veroorzaken bij re-renders.


Event handling en state management

Blazor’s event-model is een van die gebieden waar Claude Code je beschermt tegen subtiele bugs. Vraag het om een zoekcomponent te bouwen met debounced input:

<input type="text" @oninput="OnSearchInput" placeholder="Zoeken..." />

@if (_isSearching)
{
    <span class="spinner">Bezig met zoeken...</span>
}

@code {
    [Parameter] public EventCallback<string> OnSearch { get; set; }

    private CancellationTokenSource? _debounceToken;
    private bool _isSearching;

    private async Task OnSearchInput(ChangeEventArgs e)
    {
        var query = e.Value?.ToString() ?? string.Empty;

        _debounceToken?.Cancel();
        _debounceToken = new CancellationTokenSource();

        try
        {
            await Task.Delay(300, _debounceToken.Token);
            _isSearching = true;
            StateHasChanged();
            await OnSearch.InvokeAsync(query);
        }
        catch (TaskCanceledException) { }
        finally
        {
            _isSearching = false;
        }
    }
}

Het gebruikt @oninput in plaats van @onchange (zodat de zoekactie afgaat terwijl je typt), voegt debouncing toe met cancellation, en roept StateHasChanged expliciet aan omdat de spinner-state midden in de methode verandert.


Formulieren en validatie

Formulieren bouwen in Blazor betekent EditForm, DataAnnotationsValidator en een serie componenten die samenwerken. Claude Code handelt de hele keten af.

“Maak een contactformulier met naam, e-mail en bericht. E-mail moet gevalideerd worden. Toon validatiemeldingen inline.”

<EditForm Model="@_model" OnValidSubmit="HandleSubmit">
    <DataAnnotationsValidator />

    <div class="form-group">
        <label for="name">Naam</label>
        <InputText id="name" @bind-Value="_model.Name" class="form-control" />
        <ValidationMessage For="@(() => _model.Name)" />
    </div>

    <div class="form-group">
        <label for="email">E-mail</label>
        <InputText id="email" @bind-Value="_model.Email" class="form-control" />
        <ValidationMessage For="@(() => _model.Email)" />
    </div>

    <div class="form-group">
        <label for="message">Bericht</label>
        <InputTextArea id="message" @bind-Value="_model.Message" class="form-control" />
        <ValidationMessage For="@(() => _model.Message)" />
    </div>

    <button type="submit" disabled="@_submitting">
        @(_submitting ? "Verzenden..." : "Verstuur")
    </button>
</EditForm>

@code {
    private ContactFormModel _model = new();
    private bool _submitting;

    private async Task HandleSubmit()
    {
        _submitting = true;
        await ContactService.SubmitAsync(_model);
        _model = new ContactFormModel();
        _submitting = false;
    }
}

Het weet dat je OnValidSubmit moet gebruiken (niet OnSubmit), combineert DataAnnotationsValidator met ValidationMessage, en reset het formulier door een nieuw model-object te maken — de Blazor-manier, niet de “alle velden handmatig leegmaken”-manier.


Herbruikbare componenten extraheren

Een van mijn favoriete toepassingen: vraag Claude Code om bestaande pagina’s te refactoren naar herbruikbare componenten. Het leest je pagina, herkent herhaalde patronen en extraheert ze.

“Bekijk de admin-pagina’s in /Pages/Admin/. Haal gemeenschappelijke patronen eruit en maak er gedeelde componenten van.”

Claude Code leest alle admin-pagina’s, ziet dat elke pagina een header heeft met een titel, een breadcrumb en een actieknop. Het maakt een PageHeader.razor component en vervangt de gedupliceerde markup in alle pagina’s — inclusief het bijwerken van de parameter-bindings.

Dat soort cross-file refactoring is waar volledige codebase-toegang het verschil maakt. Een chat-gebaseerde tool kan dit niet, omdat die niet alle bestanden tegelijk ziet.


Waar Claude Code moeite mee heeft bij Blazor

Ik wil eerlijk zijn over de beperkingen.

Render lifecycle edge cases. Claude Code plaatst soms logica in OnInitialized terwijl het in OnAfterRender hoort, vooral bij alles wat de DOM raakt of vereist dat het component volledig gerenderd is. Als je JS interop doet of elementgroottes meet, check dan welke lifecycle-methode het gekozen heeft.

JavaScript interop. Claude Code kan IJSRuntime-calls schrijven, maar de JavaScript-kant klopt niet altijd. Het genereert soms een .js-bestand dat niet past bij hoe jij je JS-modules hebt opgezet, of vergeet dat IJSObjectReference disposed moet worden.

Streaming rendering en enhanced navigation. De nieuwere .NET 8 render-modes — @rendermode InteractiveServer, StreamRendering, enhanced nav — zijn nog lastig. Claude Code mixt soms patronen van verschillende render-modes of genereert code die volledige interactiviteit veronderstelt terwijl je in statische SSR zit.

CSS isolation-eigenaardigheden. Het kent .razor.css scoped styles, maar genereert af en toe selectors die niet werken met Blazor’s scoping-mechanisme (zoals het direct targeten van child components).

Niets hiervan is een dealbreaker. Maar je moet weten waar je twee keer moet kijken.


Tips voor het prompten van Claude Code voor Blazor

Na maanden Blazor componenten bouwen met Claude Code, dit is wat werkt:

  1. Geef de render-mode aan. Begin met “Ik gebruik Interactive Server rendering” of “Dit is een statische SSR-pagina.” Het verandert de gegenereerde code aanzienlijk.

  2. Noem je .NET-versie. Blazor in .NET 6 en .NET 8 zijn behoorlijk verschillend. Zeg “.NET 8” van tevoren.

  3. Beschrijf de component tree. “Dit component is een child van MainLayout en ontvangt de huidige gebruiker via CascadingParameter” geeft Claude Code de context die het nodig heeft.

  4. Vraag ook om de modelklasse. Bij formulieren: zeg “Genereer ook het model met DataAnnotations.” Anders krijg je een component dat verwijst naar een klasse die nog niet bestaat.

  5. Gebruik je CLAUDE.md. Documenteer je component-conventies, je CSS-framework (Bootstrap? Tailwind?) en je projectstructuur. Claude Code gebruikt dit iedere keer.


Probeer het zelf

De volgende keer dat je een Blazor component nodig hebt, probeer dit:

claude

Beschrijf het component — wat het toont, welke parameters het heeft, hoe het samenwerkt met zijn parent. Wees specifiek over de render-mode en .NET-versie. Laat Claude Code de Razor-syntax, de lifecycle-methodes en de parameter-bedrading afhandelen.

Je zult merken dat het meest vervelende deel van Blazor-development — onthouden welke lifecycle-methode je moet gebruiken, de event callback-signatures goed krijgen, cascading parameters aansluiten — precies het deel is waar Claude Code het beste in is.


Bouw je Blazor componenten met Claude Code? Ik hoor graag welke patronen voor jou werken. Stuur me een bericht via de contactpagina.