RemoteService.Net.Server 1.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package RemoteService.Net.Server --version 1.1.0
                    
NuGet\Install-Package RemoteService.Net.Server -Version 1.1.0
                    
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="RemoteService.Net.Server" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RemoteService.Net.Server" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="RemoteService.Net.Server" />
                    
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 RemoteService.Net.Server --version 1.1.0
                    
#r "nuget: RemoteService.Net.Server, 1.1.0"
                    
#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 RemoteService.Net.Server@1.1.0
                    
#: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=RemoteService.Net.Server&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=RemoteService.Net.Server&version=1.1.0
                    
Install as a Cake Tool

RemoteService.Net

NuGet NuGet NuGet License: MIT

Call your server services from Blazor WebAssembly as if they were local — no controllers, no HttpClient boilerplate, no duplicated DTO plumbing.

Define a C# service interface once. RemoteService.Net's Roslyn source generator does the rest:

  • On the server (prerendering, InteractiveServer, or InteractiveAuto running server-side) the interface resolves to your real implementation and is called in-process.
  • In the browser (InteractiveWebAssembly, or InteractiveAuto after WASM takes over) the interface resolves to a generated HTTP proxy that calls generated Minimal API endpoints on the server.

Your components don't know the difference — they just inject the interface.

public interface IProductService : IRemoteService
{
    Task<Product[]> SearchAsync(string query, CancellationToken cancellationToken = default);
}
@inject IProductService Products

@code {
    private Product[]? _results;

    private async Task Search(string query)
        => _results = await Products.SearchAsync(query);
}

That's it. No API controller, no route constants, no JsonSerializer calls, no manual DI wiring per service. Everything is generated at compile time — fully typed, refactor-safe, and AOT/trimming friendly.

Why?

Blazor Web Apps with WebAssembly interactivity have a well-known friction point: components run in two places. During prerendering they execute on the server where they could call services directly; after hydration they execute in the browser where every data access must go over HTTP. The standard answer is to hand-write an API controller and a typed HttpClient wrapper and keep both in sync with the service interface — for every service.

RemoteService.Net removes that entire layer:

Hand-rolled approach RemoteService.Net
Write API controller per service Generated Minimal API endpoints
Write HttpClient wrapper per service Generated proxy per interface
Register each client + endpoint manually One AddRemoteServiceClients() / MapRemoteServices()
Routes/DTOs drift out of sync Compile-time generated from a single interface
Errors arrive as raw HttpRequestException Typed exceptions round-trip (validation, 404, …)

Installation

RemoteService.Net ships as three NuGet packages — one per layer of a typical Blazor Web App solution. Install each package in the project it belongs to.

Package Install in Contains
NuGet Shared contracts class library IRemoteService marker, attributes, exception types
NuGet Blazor WebAssembly project (*.Client) Client runtime + source generator (emits HTTP proxies)
NuGet ASP.NET Core host project Server runtime + source generator (emits endpoints)

The contracts library is referenced by both the client and the server project, so a single interface definition is shared across the whole solution.

Option A — .NET CLI

Run each command from the directory of the project it applies to:

# Shared contracts project (class library)
dotnet add package RemoteService.Net.Abstractions

# Blazor WebAssembly client project
dotnet add package RemoteService.Net.Client

# ASP.NET Core server project
dotnet add package RemoteService.Net.Server

Option B — PackageReference in the .csproj

If you prefer editing project files directly (or use Visual Studio's NuGet UI), add the reference to the matching project:


<ItemGroup>
  <PackageReference Include="RemoteService.Net.Abstractions" Version="1.1.0" />
</ItemGroup>

<ItemGroup>
  <PackageReference Include="RemoteService.Net.Client" Version="1.1.0" />
  <ProjectReference Include="..\MyApp.Contracts\MyApp.Contracts.csproj" />
</ItemGroup>

<ItemGroup>
  <PackageReference Include="RemoteService.Net.Server" Version="1.1.0" />
  <ProjectReference Include="..\MyApp.Contracts\MyApp.Contracts.csproj" />
</ItemGroup>

Using Central Package Management? Put the <PackageVersion Include="RemoteService.Net.Client" Version="1.1.0" /> entries in Directory.Packages.props and drop the Version attribute from the PackageReference elements above.

Visual Studio

Right-click the project → Manage NuGet Packages…Browse → search for RemoteService.Net → install the package listed for that project in the table above.

Notes

  • Requires .NET 10.
  • The source generator ships inside the Client and Server packages and detects its role automatically from which package a project references — no configuration needed. Override with the RemoteServiceNetRole MSBuild property if you ever need to (Client, Server, or Client,Server).
  • RemoteService.Net.Abstractions is pulled in transitively by both the Client and Server packages, so you only need to reference it explicitly in the shared contracts project.

Quickstart — Blazor Web App

Works with the standard Blazor Web App template using WebAssembly or Auto interactivity:

dotnet new blazor -int WebAssembly --all-interactive   # or -int Auto

1. Define the contract (shared project)

using RemoteService.Net;

public sealed record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary);

public interface IWeatherService : IRemoteService
{
    Task<WeatherForecast[]> GetForecastAsync(int days, CancellationToken cancellationToken = default);
}

The only requirement: inherit the IRemoteService marker interface and return Task/Task<T>/ValueTask/ValueTask<T>.

2. Implement it (server project)

public sealed class ServerWeatherService : IWeatherService
{
    public async Task<WeatherForecast[]> GetForecastAsync(int days, CancellationToken ct = default)
    {
        // Talk to your database, other services, etc.
    }
}
// Program.cs (server)
builder.Services.AddScoped<IWeatherService, ServerWeatherService>();

var app = builder.Build();
// ...
app.MapRemoteServices(); // generated: maps POST /_rpc/{Interface}/{Method}

MapRemoteServices() returns the route group holding every generated endpoint, so you can chain the usual Minimal API conventions to apply them to all RPC endpoints at once:

app.MapRemoteServices()
   .RequireAuthorization()          // protect every RemoteService endpoint
   .RequireRateLimiting("rpc")
   .WithMetadata(new SomeAttribute());

Per-method [RemoteAllowAnonymous] still opts individual endpoints out of a group-level .RequireAuthorization().

3. Register the proxies (client project)

// Program.cs (client)
builder.Services.AddScoped(_ => new HttpClient
{
    BaseAddress = new Uri(builder.HostEnvironment.BaseAddress),
});
builder.Services.AddRemoteServiceClients(); // generated: registers a proxy per interface

4. Use it from any component

@page "/weather"
@inject IWeatherService WeatherService

@code {
    [PersistentState] // .NET 10: reuse prerendered data instead of fetching twice
    public WeatherForecast[]? Forecasts { get; set; }

    protected override async Task OnInitializedAsync()
        => Forecasts ??= await WeatherService.GetForecastAsync(days: 5);
}

During prerendering this calls ServerWeatherService directly. In the browser it becomes POST /_rpc/IWeatherService/GetForecastAsync. Combined with [PersistentState], the service is called exactly once per navigation.

Render mode compatibility

Render mode What happens
Static SSR / prerendering In-process call to the implementation
InteractiveServer In-process call to the implementation
InteractiveWebAssembly HTTP call via generated proxy
InteractiveAuto In-process while server-side, HTTP once WASM takes over — automatically

A complete runnable demo (weather + todos with validation) lives in samples/.

Errors that behave like exceptions

Throw a typed exception on the server; catch the same typed exception in the browser. On the wire it's a standard RFC 7807 ProblemDetails.

// Server implementation
public Task<TodoItem> AddAsync(string title, CancellationToken ct = default)
{
    if (string.IsNullOrWhiteSpace(title))
        throw new RemoteValidationException("Validation failed.",
            new Dictionary<string, string[]> { ["title"] = ["Title must not be empty."] });
    // ...
}
// Component — identical whether the call was in-process or HTTP
try
{
    await TodoService.AddAsync(title);
}
catch (RemoteValidationException ex)
{
    _errors = ex.Errors;
}
Throw on server HTTP status Caught in client
RemoteValidationException 400 RemoteValidationException (with Errors)
RemoteUnauthorizedException 401 RemoteUnauthorizedException
RemoteNotFoundException 404 RemoteNotFoundException
any other exception 500 RemoteServerException (detail only in Development)

Endpoint conventions

MapRemoteServices() returns the RouteGroupBuilder that owns every generated RPC endpoint. Because it implements IEndpointConventionBuilder, all the familiar Minimal API extension methods can be chained onto it and apply to every endpoint at once — exactly like MapGet/MapPost:

app.MapRemoteServices()
   .RequireAuthorization()              // every RPC endpoint requires an authenticated user
   .RequireRateLimiting("rpc")
   .RequireCors("client")
   .WithTags("rpc")
   .AddEndpointFilter<AuditFilter>()
   .CacheOutput();                      // …and any other IEndpointConventionBuilder extension

The return value is also an IEndpointRouteBuilder, so existing code that ignores it (app.MapRemoteServices();) keeps working unchanged.

Securing everything by default

app.MapRemoteServices().RequireAuthorization();

That single line protects all current and future remote services — no attribute to forget when a new interface is added. Individual methods can still opt out:

public interface ISessionService : IRemoteService
{
    Task<UserInfo> GetCurrentUserAsync(CancellationToken ct = default); // requires auth

    [RemoteAllowAnonymous]
    Task<bool> IsAliveAsync(CancellationToken ct = default);            // reachable anonymously
}

Group conventions are applied before the generated per-endpoint metadata, so [RemoteAllowAnonymous] wins over a group-level .RequireAuthorization(), and [RemoteAuthorize] adds its policy on top of it.

Authorization

Interfaces can't carry [Authorize], so RemoteService.Net provides mirrored attributes that the generator translates to endpoint authorization:

[RemoteAuthorize(Roles = "admin")]
public interface IAdminService : IRemoteService
{
    Task<AuditEntry[]> GetAuditLogAsync(CancellationToken ct = default);

    [RemoteAllowAnonymous]
    Task<bool> PingAsync(CancellationToken ct = default);
}

Policy, Roles, and AuthenticationSchemes are supported, on the interface or per method. To require authorization for everything without annotating each interface, see Endpoint conventions above.

The protocol, briefly

  • Every method maps to POST /_rpc/{InterfaceName}/{MethodName} — override with [RemoteRoute] / [RemoteMethod].
  • Request body is a JSON object with one property per parameter. Generated types never appear on the wire, so AOT users only need a JsonSerializerContext for their own DTOs.
  • Task<T>200 (including JSON null); Task204. CancellationToken flows to HttpContext.RequestAborted.
  • A required custom header (X-RemoteService-Call) blocks classic CSRF against cookie-authenticated endpoints.
  • A protocol version header detects stale cached WASM clients after a deployment and surfaces a clear RemoteProtocolException instead of confusing serialization errors.
  • Overloads and generic interfaces/methods are rejected at compile time with descriptive diagnostics (RSN001RSN008).

Building from source

dotnet build
dotnet test
dotnet pack -c Release -o artifacts/packages

License

MIT

Product Compatible and additional computed target framework versions.
.NET 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 (1)

Showing the top 1 NuGet packages that depend on RemoteService.Net.Server:

Package Downloads
RemoteService.Net.Mediator.Server

Mediator server runtime and source generator for RemoteService.Net. Reference this from your ASP.NET Core Blazor server project; Minimal API endpoints for your IRemoteRequest types are generated at compile time and mapped with MapRemoteMediator(), with handlers registered via AddRemoteMediatorHandlers().

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 41 9/1/2026
1.1.0 39 9/1/2026
1.0.1-preview.0.2 30 9/1/2026
1.0.1-preview.0.1 30 9/1/2026
1.0.0 33 9/1/2026