RemoteService.Net.Client
1.0.1-preview.0.1
See the version list below for details.
dotnet add package RemoteService.Net.Client --version 1.0.1-preview.0.1
NuGet\Install-Package RemoteService.Net.Client -Version 1.0.1-preview.0.1
<PackageReference Include="RemoteService.Net.Client" Version="1.0.1-preview.0.1" />
<PackageVersion Include="RemoteService.Net.Client" Version="1.0.1-preview.0.1" />
<PackageReference Include="RemoteService.Net.Client" />
paket add RemoteService.Net.Client --version 1.0.1-preview.0.1
#r "nuget: RemoteService.Net.Client, 1.0.1-preview.0.1"
#:package RemoteService.Net.Client@1.0.1-preview.0.1
#addin nuget:?package=RemoteService.Net.Client&version=1.0.1-preview.0.1&prerelease
#tool nuget:?package=RemoteService.Net.Client&version=1.0.1-preview.0.1&prerelease
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
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.
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.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="RemoteService.Net.Client" Version="1.0.0" />
<ProjectReference Include="..\MyApp.Contracts\MyApp.Contracts.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="RemoteService.Net.Server" Version="1.0.0" />
<ProjectReference Include="..\MyApp.Contracts\MyApp.Contracts.csproj" />
</ItemGroup>
Using Central Package Management? Put the
<PackageVersion Include="RemoteService.Net.Client" Version="1.0.0" />entries inDirectory.Packages.propsand drop theVersionattribute from thePackageReferenceelements 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
RemoteServiceNetRoleMSBuild property if you ever need to (Client,Server, orClient,Server). RemoteService.Net.Abstractionsis 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}
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
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- RemoteService.Net.Abstractions (>= 1.0.1-preview.0.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on RemoteService.Net.Client:
| Package | Downloads |
|---|---|
|
RemoteService.Net.Mediator.Client
Mediator client runtime and source generator for RemoteService.Net. Reference this from your Blazor WebAssembly client project; a typed IRemoteMediator that dispatches your IRemoteRequest types over HTTP is generated at compile time and registered with AddRemoteMediator(). |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.2.0 | 44 | 9/1/2026 |
| 1.1.0 | 42 | 9/1/2026 |
| 1.0.1-preview.0.2 | 32 | 9/1/2026 |
| 1.0.1-preview.0.1 | 26 | 9/1/2026 |
| 1.0.0 | 37 | 9/1/2026 |