InterfaceRpc.Client
3.0.0
dotnet add package InterfaceRpc.Client --version 3.0.0
NuGet\Install-Package InterfaceRpc.Client -Version 3.0.0
<PackageReference Include="InterfaceRpc.Client" Version="3.0.0" />
<PackageVersion Include="InterfaceRpc.Client" Version="3.0.0" />
<PackageReference Include="InterfaceRpc.Client" />
paket add InterfaceRpc.Client --version 3.0.0
#r "nuget: InterfaceRpc.Client, 3.0.0"
#:package InterfaceRpc.Client@3.0.0
#addin nuget:?package=InterfaceRpc.Client&version=3.0.0
#tool nuget:?package=InterfaceRpc.Client&version=3.0.0
InterfaceRpc
Turn a C# interface into an HTTP API and a typed client, generated at compile time.
Write an interface once and share it. The server gets ASP.NET Core minimal API endpoints for every method, and the client gets an implementation that calls them over HTTP. Both are generated when you build.
β¨ Features
- β‘ No runtime reflection. Source generators write the endpoints and the client during the build.
- π§© Built on the platform. Minimal APIs,
IHttpClientFactory, dependency injection and System.Text.Json. Nothing custom to learn. - π Standard authorization.
[Authorize],[AllowAnonymous], policies and route group conventions all work. - π‘οΈ Errors at compile time. Contracts that can't work over HTTP fail the build with a clear message.
- π Plain HTTP and JSON. Any language or tool can call your service.
- βοΈ Trim and AOT friendly. Both libraries are annotated for trimming and native AOT.
π Quick start
1. Install
| Package | Add it to |
|---|---|
| InterfaceRpc.Service | The ASP.NET Core app that hosts the service |
| InterfaceRpc.Client | Any app that calls the service |
dotnet add package InterfaceRpc.Service --version 3.0.0
dotnet add package InterfaceRpc.Client --version 3.0.0
2. Define a contract
Put the interface in a class library that both apps reference. It can target netstandard2.0.
public interface IGreeter
{
Task<string> GreetAsync(string name);
}
3. π₯οΈ Host it
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IGreeter, Greeter>();
var app = builder.Build();
app.MapRpcService<IGreeter>(); // POST /GreetAsync
app.Run();
4. π» Call it
builder.Services.AddRpcClient<IGreeter>(c => c.BaseAddress = new Uri("https://api.example"));
// Then inject IGreeter anywhere:
var message = await greeter.GreetAsync("Rush");
That's it. Calling GreetAsync on the client sends POST /GreetAsync to the server.
π How it works
IGreeter (shared contract library)
β β
AddRpcClient<IGreeter>() MapRpcService<IGreeter>()
β β
v v
ββββββββββββββββββββββ POST ββββββββββββββββββββββ
β generated client β βββββ> β generated endpoint β
β HttpClient + JSON β <βββββ β minimal API + DI β
ββββββββββββββββββββββ JSON ββββββββββββββββββββββ
client app server app
The generators run in the project that calls AddRpcClient<T>(), RpcClient.Create<T>() or MapRpcService<T>(),
so the interface can live in any referenced assembly. Generated code registers itself when your app starts, so the
library finds it without reflection.
π‘ Tip: To read the generated code, expand Dependencies βΊ Analyzers in Visual Studio, or add
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>to your project file.
π‘ Wire format
Nothing is hidden: each method is an ordinary JSON endpoint.
POST /GreetAsync HTTP/1.1
Content-Type: application/json
{ "name": "Rush" }
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
"Hello, Rush!"
| Topic | Behavior |
|---|---|
| Route | POST {prefix}/{MethodName}. Method names ignore case. |
| Request body | A JSON object with one property per parameter. Omitted for methods without parameters. |
| Missing arguments | Take the parameter's default value. |
CancellationToken |
Not sent. The service receives HttpContext.RequestAborted. |
| Result | 200 with the JSON result (null for a null result). void, Task and ValueTask methods return 204. |
| Errors | Malformed requests get a 400 or 415 problem details response. The client throws HttpRequestException with StatusCode set. |
βοΈ Configuration
Server
| I want to⦠| Do this |
|---|---|
| Put the endpoints under a prefix | app.MapRpcService<IGreeter>("/rpc") |
| Require auth for every method | app.MapRpcService<IGreeter>().RequireAuthorization() |
| Protect or open up one method | [Authorize(Roles = "Admin")] or [AllowAnonymous] on the interface method |
| Add rate limiting, tags, CORS⦠| Chain it: MapRpcService returns the RouteGroupBuilder |
| Change JSON settings | builder.Services.ConfigureHttpJsonOptions(o => ...) |
The implementation is resolved from the request's services on every call, so any lifetime works.
π Note: Any ASP.NET Core attribute on the interface or its methods (
[Authorize],[AllowAnonymous],[EnableRateLimiting],[Tags]β¦) becomes endpoint metadata.[Authorize]can only be applied to methods. To cover a whole interface, use.RequireAuthorization()or a subclass ofAuthorizeAttributewhoseAttributeUsageallows interfaces.
Client
| I want to⦠| Do this |
|---|---|
| Set the server address | AddRpcClient<IGreeter>(c => c.BaseAddress = new Uri(...)) |
| Add auth headers, logging, retries | Chain handlers: .AddHttpMessageHandler<BearerTokenHandler>(), .AddStandardResilienceHandler() |
| Change JSON settings | .ConfigureRpcClient(o => o.JsonSerializerOptions.Converters.Add(...)) |
| Skip dependency injection | RpcClient.Create<IGreeter>(new HttpClient { BaseAddress = ... }) |
builder.Services.AddRpcClient<IGreeter>(c => c.BaseAddress = new Uri("https://api.example/rpc"))
.AddHttpMessageHandler<BearerTokenHandler>()
.AddStandardResilienceHandler()
.ConfigureRpcClient(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
π Note: Synchronous interface methods block on the asynchronous pipeline, so every message handler still runs. Prefer
Task-returning methods where you can.
Native AOT
Add a JsonSerializerContext for your contract types on both sides, with ConfigureHttpJsonOptions on the server
and ConfigureRpcClient on the client.
β Supported contracts
Works:
- Return types
void,T,Task,Task<T>,ValueTaskandValueTask<T> - Parameters of any type System.Text.Json can serialize, including
in,paramsand default values CancellationTokenparameters- Methods inherited from base interfaces
Doesn't work (and fails the build):
- Overloaded methods (methods are routed by name)
- Generic methods,
refandoutparameters, ref structs such asSpan<T> - Properties, events and
IAsyncEnumerable<T>
Compiler diagnostics
Problems are reported at the AddRpcClient, RpcClient.Create or MapRpcService call.
| ID | Severity | Meaning |
|---|---|---|
IRPC001 |
β Error | The type argument is not an interface. |
IRPC002 |
β Error | A member can't be called remotely (see the list above). |
IRPC003 |
β Error | Two methods share a name. Overloads aren't supported. |
IRPC004 |
β οΈ Warning | The type argument is a generic type parameter, so no code can be generated at that call. |
IRPC005 |
β Error | Generated code can't access the interface (for example, a private nested interface). |
IRPC006 |
β οΈ Warning | A tuple type is used. System.Text.Json serializes tuples as {} unless IncludeFields is enabled; use a record. |
β¬οΈ Upgrading from 2.x
β οΈ Important: Version 3 changes the wire format (arguments are now a named JSON object), so upgrade clients and services together.
| 2.x | 3.x |
|---|---|
app.UseRpcService<T>(o => o.ServiceFactory = ...) |
Register T with dependency injection, then app.MapRpcService<T>() |
RpcServiceOptions.Prefix |
app.MapRpcService<T>("/prefix") |
AuthorizationScope.Required |
app.MapRpcService<T>().RequireAuthorization() |
AuthorizationScope.AdHoc with [Authorize] on the implementation |
[Authorize] on the interface methods |
AuthorizationHandler |
ASP.NET Core authorization policies |
RpcClient<T>.Create(url) |
services.AddRpcClient<T>(...) or RpcClient.Create<T>(httpClient) |
RpcClientOptions.Extensions |
DelegatingHandlers via AddHttpMessageHandler |
RpcClient.SetAuthorization / SetAuthorizationHeaderAction |
A DelegatingHandler, or HttpClient.DefaultRequestHeaders |
| SerializerDotNet (JSON, Protobuf) | System.Text.Json |
π οΈ Building from source
dotnet build
dotnet test
dotnet pack -c Release # writes both packages to ./nupkg/<version>
The Examples folder has a demo service and a
console client. Start InterfaceRpcDemoService, then run InterfaceRpcDemoClient.
The version comes from <Version> in Directory.Build.props. Building updates the install commands in this README
to match.
π¬ Feedback
Found a bug or have an idea? Open an issue.
π License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. 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. |
-
net10.0
- Microsoft.Extensions.Http (>= 8.0.1)
-
net8.0
- Microsoft.Extensions.Http (>= 8.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 35 | 9/26/2026 |
| 2.2.3 | 1,222 | 9/14/2022 |
| 2.2.2 | 668 | 9/14/2022 |
| 2.2.1 | 665 | 9/13/2022 |
| 2.2.0 | 669 | 9/13/2022 |
| 2.1.2 | 1,143 | 6/10/2021 |
| 2.1.1 | 1,222 | 9/10/2020 |
| 2.1.0 | 1,553 | 10/30/2019 |
| 2.0.0 | 847 | 9/26/2019 |
| 2.0.0-n | 909 | 7/11/2019 |
| 2.0.0-m | 649 | 7/11/2019 |
| 2.0.0-l | 678 | 7/11/2019 |
| 2.0.0-k | 691 | 7/10/2019 |
| 2.0.0-j | 654 | 7/10/2019 |
| 2.0.0-i | 668 | 7/10/2019 |
| 2.0.0-h | 686 | 6/23/2019 |
| 2.0.0-g | 689 | 6/20/2019 |
| 2.0.0-f | 692 | 6/19/2019 |
| 2.0.0-e | 643 | 6/18/2019 |
| 2.0.0-d | 662 | 6/18/2019 |