NE.Standard

Getting Started

NE.Standard is a server-driven UI framework: you write a view (a component tree) and a controller (observable state and commands), the framework compiles and renders them, and keeps the rendered page in sync in both directions. This walks through wiring that up in a new ASP.NET Core project.

Install

The framework is a pre-release, so every package needs --prerelease until the first stable release, and every package must be installed at the same version:

dotnet add package NE.Standard.UI.Web --prerelease
dotnet add package NE.Standard.UI.Web.Renderers --prerelease

NE.Standard.UI.Web is the ASP.NET Core host (shell, SignalR channel, file transfer, the embedded TypeScript client); NE.Standard.UI.Web.Renderers supplies the HTML renderers for the built-in components. Everything else — the compiler, the binding model, the built-in components — arrives transitively.

Two startup classes

Configuration is split in two: one class for services and one for the application (routes, persistence, localization). Both are empty shells you fill in.

using Microsoft.Extensions.DependencyInjection;
using NE.Standard.UI.Web.Renderers.DI;
using NE.Standard.UI.Web.Startup;

internal sealed class AppWebStartup : WebStartupBase<AppStartup>
{
    protected override void ConfigureServices(IServiceCollection services)
        => services.AddStandardRenderers();
}
using NE.Standard.UI.Application;
using NE.Standard.UI.Startup;

public sealed class AppStartup : UIStartupBase
{
    protected override void ConfigureApplication(UIApplicationBuilder application)
        => application.Route<CounterView, CounterController>("/");
}

AddStandardRenderers() registers the renderers for the built-in components — call it once from ConfigureServices. application.Route<TView, TController>(route) registers a page with a controller; application.Route<TView>(route) registers one with no controller, for a page that has nothing to bind.

Program.cs

using Microsoft.AspNetCore.Builder;
using NE.Standard.UI.Web.Hosting;
using NE.Standard.UI.Web.Startup;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

WebStartupBuilder.Configure<AppWebStartup, AppStartup>(builder.Services);

WebApplication app = builder.Build();

// The framework maps a catch-all GET route, which matches every path — static files must be served
// (and routing added) before that map, or every static file comes back as a rendered page.
app.UseStaticFiles();
app.UseRouting();

await app.MapStandardUIWebAsync();

await app.RunAsync();

WebStartupBuilder.Configure<TWebStartup, TStartup>(services) runs both startup classes. app.MapStandardUIWebAsync() maps the asset endpoints, the file endpoints, the SignalR hub, and the catch-all page route — in that order, which is why UseStaticFiles/UseRouting must run first and by hand rather than through MapStaticAssets() (its precompressed variants collide with the framework's own response compression).

A first view and controller

A view declares the component tree; a controller holds the state a component binds to.

using NE.Standard.UI.Abstractions.Styling;
using NE.Standard.UI.Authoring.Components;
using NE.Standard.UI.Components.BuiltIns.Actions;
using NE.Standard.UI.Components.BuiltIns.Contents;
using NE.Standard.UI.Components.BuiltIns.Layouts;
using NE.Standard.UI.Authoring.Views;

internal sealed class CounterView : UIViewBase, IUIViewDefinition
{
    public static string ViewKey => "counter";

    protected override IVisualComponent CreateContent()
        => new ContainerComponent()
            .SetPadding(new UIThickness(16))
            .AddChild(new TextComponent()
                .BindTitle(nameof(CounterController.Count))
                .SetPlacement(1, 1, 24, 1)
            )
            .AddChild(new ButtonComponent()
                .OnClick(nameof(CounterController.Increment))
                .ConfigureDefaultContent(c => c.SetTitle("Add one"))
                .SetPlacement(1, 2, 24, 1)
            );
}
using NE.Standard.UI.Controllers;
using NE.Standard.UI.Primitives.Annotations;

internal sealed partial class CounterController : UIControllerBase
{
    [RecursiveMember]
    public partial int Count { get; set; }

    [UICommand]
    public void Increment() => Count++;
}

BindTitle binds TextComponent.Title to the controller's Count path; OnClick wires the button to the Increment command by name. [RecursiveMember] generates the notifying setter for Count and the path segment a binding resolves against; [UICommand] makes Increment callable from the client. Register the route in AppStartup.ConfigureApplication as shown above.

Running it

dotnet run

Requires the .NET 10 SDK, and Node for the TypeScript client — the client builds as part of dotnet build, so a client type error fails the build too (dotnet build -p:SkipWebClientBuild=true skips that step when Node is unavailable).

Icons

Icon sets are separate packages, one per set, each with a matching NE.Standard.UI.Web.Icons.<Set> package that plugs into the web host. Register the glyphs an application actually uses in ConfigureServices:

using NE.Standard.UI.Icons.Material;
using NE.Standard.UI.Web.Icons.Material;

protected override void ConfigureServices(IServiceCollection services)
{
    services.AddStandardRenderers();
    services.AddMaterialWebIcons(MaterialIconStyle.Fill | MaterialIconStyle.Outlined, MaterialIcons.Settings);
}

Only the glyphs named this way are registered — a whole style of the Material set costs megabytes, so the call takes the names it should serve. MaterialIconStyle is a flags enum (Fill, Outlined); registering both drawings lets a component choose per instance.

Where to go next

The counter is the whole model: a view, a controller, a route. A first real screen is three more pages away — a list bound to a collection (Items), a form with validation and a submit (Binding, Validation), and a sign-in page with a route behind it (Security, Signing in); examples/TeamRoom is all three in one small application.

  • Views — the component tree, placement grid, and the fluent authoring style.
  • Controllers — observable state, commands, and the request lifecycle.
  • Binding — paths, scopes, modes, and validation.
  • Interactions and effects — client-side reactions and what a command can return.
  • Routing — registering more than one route, route parameters, and filters.
  • Web hosting — the ASP.NET Core host in depth, and how to extend it.
  • Components — the built-in component catalogue.