Awesome
<!-- markdownlint-disable-next-line MD033 MD041 --> <div align="center"> <!-- markdownlint-disable-next-line MD033 --> <img src="https://github.com/icerpc/icerpc-csharp/raw/main/.github/assets/icerpc-banner.svg" alt="IceRPC" height="100" width="100" /> </div>IceRPC for C#
Getting started | Examples | NuGet packages | Documentation | API reference | Building from source
IceRPC is a modular RPC framework that helps you build networked applications with minimal effort.
Built for QUIC
IceRPC was built from the ground up to take advantage of QUIC, the new multiplexed transport that underpins HTTP/3.
QUIC is ideally suited for RPCs: an RPC maps to a request/response pair carried by a bidirectional QUIC stream. Multiple request/response pairs can proceed in parallel inside the same QUIC connection without interfering with each other.
IceRPC uses its own application protocol, icerpc
, to exchange connection settings, transmit
requests and responses, and ensure an orderly shutdown. This new RPC-focused protocol is a thin layer over QUIC.
Not only for QUIC
The primary transport for IceRPC is QUIC, but we're still in the early days of QUIC, so being QUIC-only is not practical.
To bridge this gap, IceRPC provides a multiplexing adapter called Slic. Slic implements a QUIC-like multiplexed transport over any duplex transport such as TCP. This way, you can use IceRPC with QUIC, with TCP (via Slic), and with various other traditional transports such as Bluetooth and named pipes1.
Modern C# and .NET
IceRPC for C# takes full advantage of the latest C# syntax and .NET features to offer a truly modern C# API.
Chief among these features is async/await. Async/await allows you to utilize threads efficiently when making calls that
wait for I/O, and RPCs are all about network I/O. Async/await also makes your code easier to read and maintain: you can
see immediately when you make an RPC versus a local synchronous call since all RPC calls have Async
APIs that are
usually awaited. For example:
// Synchronous code (old RPC style)
// It's unclear if this is a remote call that takes milliseconds or a local call that takes
// at most a few microseconds. In any case, this call is holding onto its thread until it
// completes.
string greeting = greeter.Greet(name);
// Asynchronous code with await (modern RPC style)
// We see it's a special call thanks to await and the Async suffix. GreetAsync releases the
// thread while waiting for the response from the peer and it's just as easy to write as
// the synchronous version.
string greeting = await greeter.GreetAsync(name);
With IceRPC, all calls that make network I/O are Async and only Async. IceRPC does not provide a parallel blocking synchronous API.
IceRPC leverages System.IO.Pipelines for maximum efficiency. This allows IceRPC to rent all its byte buffers from the same configurable memory pool.
IceRPC supports cancellation just like all modern C# libraries, with trailing cancellation token parameters. This cancellation works "across the wire": when you cancel an outstanding RPC invocation, the remote service is notified and can in turn cancel further processing.
Modular and extensible
When you make an RPC with IceRPC, your request and response travel through an invocation pipeline (on the client side) and a dispatch pipeline (on the server side):
---
title: Client-side
---
flowchart LR
subgraph pipeline[Invocation pipeline]
direction LR
di[Deadline<br>interceptor] --> ri[Retry<br>interceptor] --> connection[network<br>connection] --> ri --> di
end
client -- request --> di
client -- response --- di
---
title: Server-side
---
flowchart LR
subgraph pipeline [Dispatch pipeline]
direction LR
lm[Logger<br>middleware] --> dm[Deadline<br>middleware] --> service --> dm --> lm
end
connection[network<br>connection] -- request --> lm
connection -- response --- lm
These pipelines intercept your requests and responses and you decide what they do with them. If you want to log your requests and responses, add the Logger interceptor to your invocation pipeline or the Logger middleware to your dispatch pipeline. If you want to retry automatically failed requests that can be retried, add the Retry interceptor to your invocation pipeline. IceRPC provides a number of interceptors and middleware for compression, deadlines, logging, metrics, OpenTelemetry integration, and more. You can also easily create and install your own interceptors or middleware to customize these pipelines.
Since all this functionality is optional and not hard-coded inside IceRPC, you can choose exactly the behavior you want. For example, you don't need the Compress interceptor if you're not compressing anything: if you don't install this interceptor, there is no compression code at all. Less code means simpler logic, fewer dependencies, faster execution and fewer bugs.
This modularity and extensibility is everywhere in IceRPC. You can easily implement a new duplex or multiplexed transport and then plug it in IceRPC. All the transport interfaces are public and fully documented.
And you can use IceRPC with a DI container—or not. It's all opt-in.
Choose your IDL
IceRPC provides a first-class byte-oriented API that allows you to make RPCs with the IDL and serialization format of your choice.
IceRPC comes with full support for two IDLs: Slice (described below) and Protobuf. You can use either Slice or Protobuf to define the contract between your clients and servers.
Slice
The Slice IDL and serialization format help you define RPCs in a clear and concise manner, with just the right feature set. Slice itself is not tied to IceRPC: you can use Slice without any RPC framework, or with a different RPC framework.
This repository provides an IceRPC + Slice integration that allows you to use IceRPC and Slice together seamlessly.
Defining the customary Greeter
interface in Slice is straightforward:
// Interface Greeter is implemented by a service hosted in a server.
interface Greeter {
// The greet request carries the name of the person to greet and
// the greet response carries the greeting created by the service
// that implements Greeter.
greet(name: string) -> string
}
You don't need to craft special request and reply message types: you can specify all your parameters inline.
The Slice compiler for C# then generates readable and succinct C# code from this Greeter
interface:
- a client-side
IGreeter
interface with a singleGreetAsync
method. - a client-side
GreeterProxy
that implementsIGreeter
by sending requests / receiving responses with IceRPC - a server-side
IGreeterService
interface that you use as a template when writing the service that implements Greeter
Slice also supports streaming in both directions. For example:
interface Generator {
// Returns a (possibly infinite) stream of int32
generateNumbers() -> stream int32
}
interface Uploader {
// Uploads an image (can be very large)
uploadImage(image: stream uint8)
}
A stream of uint8
is mapped to a C# PipeReader
while a stream of any other type is mapped to an
IAsyncEnumerable<T>
.
Slice provides common primitives types with easy-to-understand names:
- string
- bool
- fixed-size integral types (int8, int16, int32, int64, uint8, uint16, uint32, uint64)
- variable-size integral types (varint32, varint62, varuint32, varuint62)
- floating point types (float32, float64)
You can define new types with struct
, enum
, and custom
. You can construct collections with Sequence<T>
and
Dictionary<Key, Value>
. And you can have your Slice operation return different types upon success or failure with
Result<Success, Failure>
.
custom allows you to send any C# type you wish through Slice, in keeping with IceRPC's mantra of modularity and extensibility. You just need to provide methods to encode and decode instances of your custom type.
Protobuf
Protocol Buffers, or Protobuf for short, is a popular IDL and serialization format developed by Google. It's the preferred IDL for a number of RPC frameworks, including gRPC.
The IceRPC + Protobuf integration allows you to call and implement Protobuf services using IceRPC with only a few lines of code.
Ice interop
IceRPC for C# provides a high level of interoperability with Ice. You can use IceRPC to write a new C# client for your Ice server, and you can call services hosted by an IceRPC server from an Ice client.
IceRPC for Ice users provides all the details.
Build telemetry
IceRPC collects anonymous telemetry data when you compile Slice files or Protobuf files. You can find detailed information about what data is collected and how to opt-out in the READMEs for IceRpc.Slice.Tools and IceRpc.Protobuf.Tools.
License
IceRPC is licensed under the Apache License version 2.0, a permissive open-source license.
This license allows you to use IceRPC in both open-source and closed source applications, free of charge. Please refer to the license for the full terms and conditions.
Footnotes
-
IceRPC for C# currently provides two duplex transport implementations: TCP (with or without TLS), and Coloc (an in-memory transport for testing). Future releases may add additional transports. ↩