Getting Started

BlazorCodeFirst lets you write Blazor UI as plain C#.

Installation#

Add the runtime and the source generator to your project, then derive your components from BodyComponentBase.

dotnet add package BlazorCodeFirst --prerelease

The published version carries a prerelease suffix. Without --prerelease the command looks for the latest stable version, of which there is none, and resolves nothing.

A first component#

A component is a partial class with one overridden property. The class must be partial, because the generator writes the rendering into it, and top-level, because reopening a nested class from the generated file would mean re-declaring every enclosing type, type parameters included.

using Microsoft.AspNetCore.Components;
using BlazorCodeFirst;
using static BlazorCodeFirst.Html;

[Route("/")]
public partial class Home : BodyComponentBase
{
    protected override View Body =>
        Div[
            H1["Hello"],
            Span["Welcome to BlazorCodeFirst."]];
}

That expression names the HTML it produces. Attributes chain onto the element, children go in brackets, and a bare string is a text node.

protected override View Body =>
    Div[
        H1["Hello"],
        Span["Welcome to BlazorCodeFirst."]];
<div>
    <h1>Hello</h1>
    <span>Welcome to BlazorCodeFirst.</span>
</div>

The getter reaches one expression#

Three spellings satisfy that, and all three translate identically:

protected override View Body => Div[H1["Hello"]];                    // fine
protected override View Body { get => Div[H1["Hello"]]; }            // fine
protected override View Body { get { return Div[H1["Hello"]]; } }    // fine

Locals and expression statements may precede that return. They are copied into the generated RenderView ahead of the calls that emit render-tree frames, which is where a ForEach content block's statements already go:

protected override View Body
{
    get
    {
        var greeting = $"Hello, {_name}";
        return Div[H1[greeting]];
    }
}

A second return and native control flow each need a sequence space of their own, so neither is accepted (BCF1004). If the body genuinely cannot be written in this shape, override RenderView by hand: the design-time expression is then unused, and nothing is reported.

Where the surface is read#

Html.Div, .Class(...), .OnClick(...) and every other factory and decoration are inert. View is an empty struct, an element helper returns nothing, and a decoration returns its receiver unchanged. The generator reads the syntax you wrote, never the value, and it reads it in three places: a component's Body, a layout's Chrome, and the body of a [ViewPart] method.

The same API is callable from anywhere, and nothing reads it outside those three places. Written in an event handler, a service, or a helper method it still compiles, but it emits no render-tree frames, so nothing is rendered and no event handler is registered. That is BCF3029, and BCF3030 is the same mistake seen from a call site.

Why the build stops#

The compiler reports any expression it cannot translate into RenderTreeBuilder calls, rather than emitting something that renders differently from what you wrote. Every diagnostic has an entry in the reference, and these five are the ones reported most often:

BCF1001 the class is not partial
BCF1005 the class is nested
BCF1004 the getter does not reach one returned expression
BCF1002 the expression names a local the generated file cannot see
BCF1003 the expression uses a construct the generator does not read

These five reject a shape the generator cannot read at all. The same rule also catches shapes that read fine but would render two different DOMs: BCF3016 rejects children on a void element, because prerendering closes the tag and the HTML parser pushes them out to siblings, while interactive rendering never opens that path.

A class can carry two faults at once, a missing partial and an untranslatable getter, and only one is reported at a time. The partial check runs first, so BCF1001 is reported alone; adding the modifier is what surfaces BCF1004.

Next steps#

An unhandled error has occurred. Reload 🗙