11 · Best practices

One-page checklist: project layout, interfaces, errors, authorization, testing

A one-page checklist of the recommendations from the tutorials. Each item links to the tutorial that explains it.

This is the layout the tutorials use. It is a recommendation, not a requirement. The things that are required are called out in tutorial 01.

MyApp.Shared/                           references RForge.Blazor.AutoApi.Abstractions only, no ASP.NET
├── I<Feature>Service.cs                [RForgeAutoApi] interfaces
├── <Feature>Dto.cs                     the types the interfaces send and return
└── AppJsonContext.cs                   the JsonSerializerContext, plus [assembly: RForgeAutoApiDefaults(...)]
MyApp/                                  server project (references the Generator package)
├── Program.cs                          register services, AddRForgeAutoApiServer(), MapRForgeAutoApi()
├── AutoApiEndpoints.cs                 the [RForgeAutoApiHost] class
└── Services/<Feature>Service.cs        the real implementations
MyApp.Client/                           WebAssembly project (references the Generator package)
├── Program.cs                          AddRForgeAutoApi()
├── AutoApiClients.cs                   the [RForgeAutoApiClient] class
└── Features/…                          components that inject the interfaces
MyApp.Tests/                            round-trip, server-mode and component tests

Where each file goes

FilePut it inWhyTutorial
Interfaces, DTOs, JsonSerializerContextMyApp.SharedBoth the server and the client need them, and the generators read them from a referenced assembly01, 03
[assembly: RForgeAutoApiDefaults]MyApp.SharedBoth generators read it, so the server and client always agree on prefix, default method and JSON context01, 03
[RForgeAutoApiHost] classThe server projectIt generates ASP.NET code01
[RForgeAutoApiClient] classThe WebAssembly projectIt generates the HttpClient implementation01
Generator package referenceEvery project that has a marker classNuGet doesn't pass analyzers through project references01, 10
The real servicesThe server project, registered before AddRForgeAutoApiServer()Server mode and prerendering call them directly05, 06
Handlers and client namesMyApp.Client/Program.cs, on the builder AddRForgeAutoApi() returnsOne place to add tokens and extra clients07
Rate limiting and other endpoint conventionsProgram.cs, chained on MapRForgeAutoApi()They apply to every generated endpoint08
TestsMyApp.TestsOne fixture shares an in-memory server09

Naming

ThingConventionExample
InterfaceI<Feature>ServiceIBookService
MethodVerb, ends in AsyncGetBookAsync, AddBookAsync
Data type<Noun>DtoBookDto
Marker classesAutoApiEndpoints and AutoApiClients
Error codeLower snake case, one per reasontitle_required, book_not_found

Interfaces and methods

  • [ ] Every method returns Task, Task<T>, ValueTask or ValueTask<T>. No generics, no ref/out/in, no properties or events. (02, 10)
  • [ ] Remember that [RForgeAutoApi] opts in every method of the interface. Split an interface if some methods should not be callable from the browser. (02)
  • [ ] Keep the default Post unless a method is a safe read. Use Get and Delete only with simple parameters. (02, 03)
  • [ ] Take a CancellationToken (at most one) and pass it on to your async calls. (03)
  • [ ] Avoid overloads, or give each its own Route. Default routes for overloads are numbered in declaration order. (02)
  • [ ] Add new parameters with a default value. Adding one is not a breaking change. (03)

Serialization

  • [ ] Declare one JsonSerializerContext in the shared library and point RForgeAutoApiDefaults.JsonContext at it. (03)
  • [ ] List every parameter and return type in it, including List<T> and simple types such as int. (03)
  • [ ] Treat RFAA009 as an error in CI, so a missing type can't fall back to reflection unnoticed. (10)

Errors

  • [ ] Throw RfException (with a code) for expected failures, and RfNotFoundException for a missing item. (04)
  • [ ] Write RfException messages you are happy to show a user. They are sent to the browser. (04)
  • [ ] In components, catch (RfException) to show the message, and log everything else. The same catch works in every render mode. (04)
  • [ ] Don't catch Exception to hide failures. Unexpected errors arrive as RfApiException in WebAssembly, with the details left on the server. (04)

Authorization

  • [ ] Put [RfAuthorize] on the interface and [RfAllowAnonymous] on the few methods that are public. (05)
  • [ ] Register services that have authorization rules as Scoped or Transient. A Singleton throws at startup. (05, 10)
  • [ ] Call AddRForgeAutoApiServer() after all your service registrations. (05, 10)
  • [ ] Remember that Interactive Server mode makes no HTTP call. The decorator, not the endpoint, enforces the rules there. (05, 06)

Components and render modes

  • [ ] Inject the interface and nothing else. Don't check the render mode in the component. (06)
  • [ ] Don't assume a call is cheap or free of serialization. In WebAssembly it is an HTTP request. (06)

HttpClient and endpoints

  • [ ] Add handlers (for example a token handler) to the builder AddRForgeAutoApi() returns. (07)
  • [ ] Use HttpClientName only when an interface really talks to a different host. (07)
  • [ ] Chain rate limiting and other conventions onto MapRForgeAutoApi(). (08)

Deploying

  • [ ] Deploy the server and the WebAssembly client from the same build. The routes are generated from the same source, so mixed versions can disagree. (02, 10)

Testing

  • [ ] One round-trip fixture with TestServer and a test auth scheme. Cover each exception type and each authorization rule. (09)
  • [ ] Test Interactive Server mode for any interface with [RfAuthorize]. (09)
  • [ ] Test components with a fake of the interface, not with a server. (09)
  • [ ] Run the fixture with EnvironmentName = "Production", so unexpected errors are tested the way users see them. (09)

Build settings

  • [ ] Set <WarningsAsErrors>$(WarningsAsErrors);RFAA009;RFAA010</WarningsAsErrors> so the two warnings fail the build. (10)
  • [ ] When something looks wrong, set <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> and read the generated endpoints and client. (10)
An unhandled error has occurred. Reload 🗙