RemoteService.Net.Server
1.0.0
See the version list below for details.
dotnet add package RemoteService.Net.Server --version 1.0.0
NuGet\Install-Package RemoteService.Net.Server -Version 1.0.0
<PackageReference Include="RemoteService.Net.Server" Version="1.0.0" />
<PackageVersion Include="RemoteService.Net.Server" Version="1.0.0" />
<PackageReference Include="RemoteService.Net.Server" />
paket add RemoteService.Net.Server --version 1.0.0
#r "nuget: RemoteService.Net.Server, 1.0.0"
#:package RemoteService.Net.Server@1.0.0
#addin nuget:?package=RemoteService.Net.Server&version=1.0.0
#tool nuget:?package=RemoteService.Net.Server&version=1.0.0
RemoteService.Net
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, orInteractiveAutorunning server-side) the interface resolves to your real implementation and is called in-process. - In the browser (
InteractiveWebAssembly, orInteractiveAutoafter 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
Three packages, one per layer of your solution:
# Shared contracts project (class library)
dotnet add Demo.Contracts package RemoteService.Net.Abstractions
# Blazor WebAssembly client project
dotnet add Demo.Client package RemoteService.Net.Client
# ASP.NET Core server project
dotnet add Demo package RemoteService.Net.Server
| Package | Contains |
|---|---|
RemoteService.Net.Abstractions |
IRemoteService marker, attributes, exception types |
RemoteService.Net.Client |
Client runtime + source generator (emits HTTP proxies) |
RemoteService.Net.Server |
Server runtime + source generator (emits endpoints) |
The source generator ships inside the Client and Server packages and detects its role automatically from which package the project references — no configuration needed. (Override with the RemoteServiceNetRole MSBuild property if you ever need to: Client, Server, or Client,Server.)
Requires .NET 10.
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}
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) |
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.
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
JsonSerializerContextfor their own DTOs. Task<T>→200(including JSONnull);Task→204.CancellationTokenflows toHttpContext.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
RemoteProtocolExceptioninstead of confusing serialization errors. - Overloads and generic interfaces/methods are rejected at compile time with descriptive diagnostics (
RSN001–RSN008).
Building from source
dotnet build
dotnet test
dotnet pack -c Release -o artifacts/packages
License
| Product | Versions 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. |
-
net10.0
- RemoteService.Net.Abstractions (>= 1.0.0)
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 |