Neatoo.RemoteFactory 1.10.9

dotnet add package Neatoo.RemoteFactory --version 1.10.9
                    
NuGet\Install-Package Neatoo.RemoteFactory -Version 1.10.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Neatoo.RemoteFactory" Version="1.10.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Neatoo.RemoteFactory" Version="1.10.9" />
                    
Directory.Packages.props
<PackageReference Include="Neatoo.RemoteFactory" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Neatoo.RemoteFactory --version 1.10.9
                    
#r "nuget: Neatoo.RemoteFactory, 1.10.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Neatoo.RemoteFactory@1.10.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Neatoo.RemoteFactory&version=1.10.9
                    
Install as a Cake Addin
#tool nuget:?package=Neatoo.RemoteFactory&version=1.10.9
                    
Install as a Cake Tool

Neatoo RemoteFactory

One domain model on the client and the server, with no DTOs and no controllers between them

When the library that defines your objects is loaded on both tiers, as it is with Blazor WebAssembly, the client/server seam needs no DTOs and no API controllers. RemoteFactory is a Roslyn source generator that makes that practical: it generates the factories and the serialization that carry your objects and their operations across the wire, through one endpoint the library maps. Persistence routing (Create, Fetch, then Save to Insert, Update or Delete) is one use of that.

The Opportunity

With Blazor WebAssembly, the same .NET library can run on both client and server. This changes everything.

The old world (JavaScript SPA + ASP.NET Core): Your domain model lived on the server. To get data to the browser, you serialized to JSON, wrote DTOs to receive it, and mapped back and forth. Two representations of the same thing.

The new world (Blazor WASM + ASP.NET Core): Your domain model can run in the browser. The same Order is built and edited on the client and loaded and saved on the server. No translation layer needed.

RemoteFactory makes this practical. It generates the factories, serialization, and HTTP plumbing so your domain methods "just work" across the wire. One source of truth, zero boilerplate.

Why RemoteFactory?

Traditional 3-tier architecture:

  • Write domain model methods
  • Create DTOs to transfer state
  • Build factories to map between DTOs and domain models
  • Write API controllers to expose operations
  • Maintain all four layers as requirements change

With RemoteFactory:

  • Write domain model methods
  • Add attributes ([Factory], [Remote], [Create], [Fetch], [Insert], [Update], [Delete], etc.)
  • Done. Generator creates factories, serialization, and endpoints.

Quick Example

The domain model's public interface, which is what the client holds:

<a id='snippet-order-interface'></a>

public interface IOrder : IFactorySaveMeta
{
    int Id { get; set; }
    string CustomerName { get; set; }
    OrderStatus Status { get; set; }
    IOrderLineList Lines { get; set; }
    Money Total { get; }
    void AddLine(IOrderLine line);
    void RemoveLine(IOrderLine line);
    bool CanSubmit { get; }
    void Submit();
    void Cancel();
    void MarkDeleted();
}

<sup><a href='/src/Design/Design.Domain/Aggregates/Order.cs#L37-L52' title='Snippet source file'>snippet source</a> | <a href='#snippet-order-interface' title='Start of snippet'>anchor</a></sup>

Its factory operations. Create runs wherever it is called; the [Remote] ones cross to the server, where their [Service] parameters are resolved:

<a id='snippet-readme-domain-model'></a>

/// <summary>
/// Creates a new Order. Local: runs on the tier that calls it.
/// </summary>
[Create]
public void Create(
    string customerName,
    [Service] IOrderLineListFactory lineListFactory)
{
    CustomerName = customerName;
    Lines = lineListFactory.Create();
    Status = OrderStatus.Draft;
    IsNew = true;
}

/// <summary>
/// Loads an existing Order and its lines; false when there is none.
/// </summary>
[Remote, Fetch]
internal async Task<bool> Fetch(
    int id,
    [Service] IOrderDbContext db,
    [Service] IOrderLineListFactory lineListFactory)
{
    var row = await db.FindAsync(id);
    if (row == null)
        return false;

    LoadFrom(row, lineListFactory);
    return true;
}

/// <summary>
/// Inserts a new Order and its lines.
/// </summary>
[Remote, Insert]
internal async Task Insert(
    [Service] IOrderDbContext db,
    [Service] IOrderLineListFactory lineListFactory)
{
    var row = new OrderRow();
    MapTo(row);
    db.Add(row);
    lineListFactory.Save(Lines, row.Lines);
    await db.SaveChangesAsync();
    Id = row.Id;
}

/// <summary>
/// Updates an existing Order and its lines.
/// </summary>
[Remote, Update]
internal async Task Update(
    [Service] IOrderDbContext db,
    [Service] IOrderLineListFactory lineListFactory)
{
    var row = await db.FindAsync(Id)
        ?? throw new KeyNotFoundException($"Order {Id} not found");
    MapTo(row);
    lineListFactory.Save(Lines, row.Lines);
    await db.SaveChangesAsync();
}

/// <summary>
/// Deletes an Order. Its line rows go with its row.
/// </summary>
[Remote, Delete]
internal async Task Delete([Service] IOrderDbContext db)
{
    var row = await db.FindAsync(Id)
        ?? throw new KeyNotFoundException($"Order {Id} not found");
    db.Remove(row);
    await db.SaveChangesAsync();
}

<sup><a href='/src/Design/Design.Domain/Aggregates/Order.cs#L234-L318' title='Snippet source file'>snippet source</a> | <a href='#snippet-readme-domain-model' title='Start of snippet'>anchor</a></sup>

Client code calls the generated factory:

<a id='snippet-readme-client-usage'></a>

// The customer's draft: the stored one, or a new one. One server call.
IOrder? order = await orderFactory.OpenDraft("Customer_42");

// Build a line on the client and add it. No server call.
order.AddLine(lineFactory.Create("Widget C", 12.50m, 2));

// Save sends the whole order; keep what it returns.
order = await orderFactory.Save(order);

<sup><a href='/src/Design/Design.Tests/FactoryTests/OrderUsageTests.cs#L31-L40' title='Snippet source file'>snippet source</a> | <a href='#snippet-readme-client-usage' title='Start of snippet'>anchor</a></sup>

Server automatically exposes the endpoint at /api/neatoo. No controllers needed.

Key Features

  • Zero boilerplate: No DTOs, no manual mapping, no controllers
  • Type-safe: Roslyn generates strongly-typed factories from your domain methods
  • DI integration: Inject services into factory methods with [Service] attribute
  • Authorization: Built-in support for custom auth or ASP.NET Core policies
  • Compact serialization: Ordinal format reduces payloads by 40-50%
  • Lifecycle hooks: IFactoryOnStart, IFactoryOnComplete, IFactoryOnCancelled
  • Domain events: Transactional events via [FactoryEventHandler<T>] + IFactoryEvents.Raise, with client-side IFactoryEventRelay
  • Flexible modes: Server, Remote, or Logical runtime modes

Installation

Install NuGet packages:

Server project:

dotnet add package Neatoo.RemoteFactory
dotnet add package Neatoo.RemoteFactory.AspNetCore

Client project (Blazor WASM, etc.):

dotnet add package Neatoo.RemoteFactory

Shared project (domain models):

dotnet add package Neatoo.RemoteFactory

For reducing client bundle size, see IL Trimming — it removes server-only code at publish time.

Getting Started

Server setup (ASP.NET Core): register RemoteFactory with the assembly that holds your factories:

<a id='snippet-readme-server-setup'></a>

services.AddNeatooAspNetCore(typeof(IOrder).Assembly);

<sup><a href='/src/Design/Design.Server/ServerServices.cs#L74-L76' title='Snippet source file'>snippet source</a> | <a href='#snippet-readme-server-setup' title='Start of snippet'>anchor</a></sup>

That call sits in AddDesignServer(), the example set's one composition method, beside the server-only services its factory methods take as [Service] parameters. Program calls that method and adds authentication and the endpoint to the pipeline:

<a id='snippet-getting-started-server-program'></a>

var builder = WebApplication.CreateBuilder(args);

// RemoteFactory plus the server-only services [Service] parameters resolve (ServerServices.cs)
builder.Services.AddDesignServer();

var app = builder.Build();

// Serve the hosted Blazor WASM client
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

// Sign the caller in before the endpoint runs: the authorization checks read HttpContext.User
app.UseAuthentication();
app.UseAuthorization();

// The RemoteFactory endpoint: one route for every factory call, no controllers
app.UseNeatoo();

app.MapFallbackToFile("index.html");

await app.RunAsync();

<sup><a href='/src/Design/Design.Server/Program.cs#L61-L83' title='Snippet source file'>snippet source</a> | <a href='#snippet-getting-started-server-program' title='Start of snippet'>anchor</a></sup>

Client setup (Blazor WASM):

<a id='snippet-getting-started-client-program'></a>

// The factory proxies: calls marked [Remote] go to the server
builder.Services.AddNeatooRemoteFactory(NeatooFactory.Remote, typeof(IOrder).Assembly);

// The HttpClient RemoteFactory sends them with: the server that hosts this client
builder.Services.AddKeyedScoped(
    RemoteFactoryServices.HttpClientKey,
    (sp, key) => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

<sup><a href='/src/Design/Design.Client.Blazor/Program.cs#L65-L73' title='Snippet source file'>snippet source</a> | <a href='#snippet-getting-started-client-program' title='Start of snippet'>anchor</a></sup>

For authorization, see Authorization. See Getting Started for a complete walkthrough.

Documentation

Supported Frameworks

  • .NET 9.0 (STS)
  • .NET 10.0 (LTS)

Examples

The compiled, tested source of every example on this page is src/Design (the Order domain). Complete applications are in src/Examples/:

  • Person - Simple Blazor WASM CRUD application
  • OrderEntry - Order entry system with aggregate roots

License

MIT License - see LICENSE for details.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Neatoo.RemoteFactory:

Package Downloads
Neatoo

A .NET Domain Models framework for Blazor and WPF powered by Roslyn source generators.

Neatoo.RemoteFactory.AspNetCore

Asp.Net Core Server Library for Neatoo.RemoteFactory

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.10.9 94 10/16/2026
1.10.8 99 10/16/2026
1.10.7 100 10/16/2026
1.10.6 98 10/13/2026
1.10.5 88 10/13/2026
1.10.4 92 10/13/2026
1.10.3 90 10/13/2026
1.10.2 87 10/12/2026
1.10.1 94 10/9/2026
1.10.0 95 10/9/2026
1.9.0 238 9/8/2026
1.8.1 138 9/2/2026
1.8.0 122 9/1/2026
1.7.0 142 8/14/2026
1.6.1 394 5/25/2026
1.6.0 154 4/25/2026
1.5.0 246 4/16/2026
1.1.0 392 4/11/2026
1.0.0 156 4/10/2026
0.29.0 226 4/6/2026
Loading failed