AutoApi

Write a service interface once, call it from any render mode

Purpose of Component

AutoApi removes the hand written controller and the hand written API client. You declare a service interface once, in a shared library, and a source generator writes the rest:

  • On the server it creates minimal API endpoints that call your real implementation. It can also wrap that implementation so authorization is enforced in Interactive Server mode too.
  • In WebAssembly it creates an implementation of the same interface that calls those endpoints with HttpClient.

Components inject the interface and do not need to know which render mode they run in. The same calls, the same exceptions and the same authorization rules apply everywhere.

When to Use

In a Blazor Web App that uses Interactive Auto, or both Interactive Server and Interactive WebAssembly, when a component needs data from the server and you do not want to write the endpoint, the client and the error mapping twice.

Features

  • One interface for every render mode: the server uses the real implementation, WebAssembly uses a generated client.
  • The same RfException family in every mode, sent over the wire as ProblemDetails.
  • [RfAuthorize] enforced on the endpoint and by a generated server-side decorator, so Interactive Server calls are protected as well.
  • Fast and trim safe in WebAssembly when you provide a JsonSerializerContext.
  • The shared library stays free of ASP.NET: attributes and exceptions ship in a dependency free package.

Basic Usage

Inject the interface in any component. It works in every render mode.

@inject IOrderService Orders

@code {
    OrderDto? order;
    protected override async Task OnInitializedAsync() => order = await Orders.GetOrderAsync(42);
}

See it working in the Todos example, and learn each part step by step in the tutorials.

Attributes

All attributes live in the RForge.Blazor.AutoApi namespace.

Attribute Goes on Purpose
[RForgeAutoApi] Interface Opts in every method of the interface. HttpClientName and RouteSegment are optional.
[RForgeAutoApiMethod] Method Overrides the defaults for one method: HttpMethod (Get, Post, Put, Patch, Delete), a static Route and HttpClientName.
[RfAuthorize] and [RfAllowAnonymous] Interface or method Mirror ASP.NET's AuthorizeAttribute (Policy, Roles, AuthenticationSchemes) and AllowAnonymousAttribute, without an ASP.NET reference.
[RForgeAutoApiHost] Server marker class Generates MapRForgeAutoApi() and AddRForgeAutoApiServer().
[RForgeAutoApiClient] Client marker class Generates a client for each interface and AddRForgeAutoApi().
[assembly: RForgeAutoApiDefaults] Shared library Settings both sides must agree on, read by both generators: RoutePrefix (default /api/auto), DefaultHttpMethod (default Post), HttpClientName, JsonContext and ExcludedAttributes.

Routes and Binding

  • The default route is {RoutePrefix}/{RouteSegment}/{MethodName}, for example /api/auto/IOrderService/GetOrderAsync. Custom routes are static: placeholders such as {id} are not supported (RFAA007).
  • Post (the default), Put and Patch send a JSON object keyed by parameter name, even for a single parameter, so adding a parameter with a default value is not a breaking change.
  • Get and Delete send a query string and accept simple types only (primitives, string, decimal, Guid, the date and time types, enums and their nullable versions). RFAA006 reports anything else.
  • CancellationToken parameters are never sent. On the client the token goes to HttpClient; on the server it is RequestAborted.
  • Task and ValueTask return 204; Task<T> and ValueTask<T> return 200 with a JSON body.

Error Handling

The same catch block works in Interactive Server and in WebAssembly.

Type Meaning
RfException(message, statusCode = 400, code = null) An expected error whose message is safe to show to users.
RfUnauthorizedException (401) Not authenticated.
RfForbiddenException (403) Authenticated but not authorized.
RfNotFoundException (404) The requested item does not exist.
RfApiException An unexpected server failure. Thrown in WebAssembly only; in Interactive Server the original exception propagates.
try { order = await Orders.GetOrderAsync(id); }
catch (RfException ex) { message = ex.Message; }          // expected, safe to display (both modes)
catch (Exception ex)   { logger.LogError(ex, "..."); }    // unexpected

Diagnostics

ID Severity Meaning
RFAA001 Error A [RForgeAutoApiHost] or [RForgeAutoApiClient] class must be static partial.
RFAA002 Warning The assembly of the given type contains no [RForgeAutoApi] interfaces.
RFAA003 Error Only Task, Task<T>, ValueTask and ValueTask<T> return types are supported.
RFAA004 Error Generic interfaces and generic methods are not supported.
RFAA005 Error ref, out and in parameters are not supported.
RFAA006 Error A GET or DELETE method has a parameter that is not a simple type.
RFAA007 Error A custom route contains a placeholder. Routes must be static.
RFAA008 Error A route is used by more than one method.
RFAA009 Warning A parameter or return type is not registered in the JSON context.
RFAA010 Warning An attribute could not be reconstructed and was not copied to the endpoint.
RFAA011 Error A method has more than one CancellationToken parameter.
RFAA012 Info No JsonContext configured. Reflection based serialization is used.
RFAA013 Error [RForgeAutoApi] interfaces may only contain instance methods.
RFAA014 Error Only one host class and one client class are allowed per project.

The specification covers authorization in Interactive Server mode, copied endpoint attributes, HttpClient setup and serialization in detail.

How to Setup

  1. Install the packages
  2. Declare the interface in a shared library
  3. Mark the server and the client
  4. Register the services

Install the Packages

Package Install in Contents
RForge.Blazor.AutoApi.Abstractions The shared library Attributes, RfHttpMethod and the exception types. No ASP.NET dependency.
RForge.Blazor.AutoApi.Generator The server project and the client project The source generator and its diagnostics. It adds no runtime assembly.
Command Line
dotnet add MyApp.Shared package RForge.Blazor.AutoApi.Abstractions
dotnet add MyApp package RForge.Blazor.AutoApi.Generator
dotnet add MyApp.Client package RForge.Blazor.AutoApi.Generator

The RForge.Blazor package installs the whole family instead, including both AutoApi packages. Install it in each project that needs a generator, because NuGet does not pass analyzers through project references.

MyApp.Shared   -> Abstractions          (interfaces, DTOs, JsonSerializerContext)
MyApp          -> Generator + Shared    (server host, real implementations)
MyApp.Client   -> Generator + Shared    (WebAssembly project)

Declare the Interface

Put the interface, its data types and a JsonSerializerContext in the shared library. The context has to live in a referenced assembly, because the generator reads it from there. A context in the same project as the marker class looks empty to the generator and reports RFAA009.

[assembly: RForgeAutoApiDefaults(JsonContext = typeof(AppJsonContext))]

[RForgeAutoApi]
public interface IOrderService
{
    Task<OrderDto?> GetOrderAsync(int id, CancellationToken cancellationToken = default);
}

[JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
[JsonSerializable(typeof(OrderDto))]
[JsonSerializable(typeof(int))]
public partial class AppJsonContext : JsonSerializerContext { }

Mark the Server and the Client

// Server project
[RForgeAutoApiHost(typeof(IOrderService))]
public static partial class AutoApiEndpoints { }

// Client project
[RForgeAutoApiClient(typeof(IOrderService))]
public static partial class AutoApiClients { }

Use one host class and one client class per project (RFAA014).

Register the Services

// Program.cs (server)
builder.Services.AddScoped<IOrderService, OrderService>();  // your real implementation
builder.Services.AddRForgeAutoApiServer();                  // call AFTER your registrations
// ...
app.MapRForgeAutoApi();

// Program.cs (WebAssembly client)
builder.Services.AddRForgeAutoApi();

Requirements

Requirement Details
.NET .NET 8, 9 or 10. The generator runs on the .NET 8 SDK and newer.
Services with authorization Must be Scoped or Transient. A Singleton cannot see the current user, so registering one throws at startup.
Deployment Reordering overloads changes their generated routes, so deploy the server and the client together.

Which Package Do I Install?

You want Install
The UI components only RForge.Blazor.Components
Typed events and state only RForge.Blazor.Events
An API generated from a service interface RForge.Blazor.AutoApi.Abstractions and RForge.Blazor.AutoApi.Generator
Everything RForge.Blazor
An unhandled error has occurred. Reload 🗙