Skip to content

0.6.0

Choose a tag to compare

@pepone pepone released this 05 Jun 16:01
· 153 commits to main since this release
b61a64a

What's Changed

0.6.0 is a major release that updates IceRPC's transport defaults and overhauls the Slice toolchain. The three biggest changes are:

QUIC is now the default multiplexed transport

QUIC is now the default transport for the icerpc protocol. You no longer need to construct and configure a transport to get a QUIC connection:

// Uses QUIC, the default transport.
await using var connection = new ClientConnection(new Uri("icerpc://localhost"));

QUIC always uses TLS, so a QUIC server needs a server certificate, and a QUIC client needs the matching trust/authentication options unless the server presents a publicly trusted certificate:

using X509Certificate2 serverCertificate = X509CertificateLoader.LoadPkcs12FromFile(
    "server.p12", password: null, keyStorageFlags: X509KeyStorageFlags.Exportable);

// SslServerAuthenticationOptions is a standard .NET type (System.Net.Security).
var serverAuthenticationOptions = new SslServerAuthenticationOptions
{
    ServerCertificateContext = SslStreamCertificateContext.Create(
        serverCertificate,
        additionalCertificates: null)
};

// Uses QUIC, the default transport.
await using var server = new Server(new Chatbot(), serverAuthenticationOptions);
server.Listen();

See the Greeter example for the complete client and server setup. QUIC is available wherever .NET's QUIC support is available.

You can easily use the previous default transport, Slic over TCP, by setting transport=tcp in the server address—there's no need to build a SlicClientTransport/SlicServerTransport over a TCP transport yourself:

// Uses Slic over TCP instead of the default, QUIC.
await using var connection = new ClientConnection(new Uri("icerpc://localhost?transport=tcp"));

See the TcpFallback example if you want to accept both QUIC and TCP connections.

Direct support for Ice's Slice files

You can now compile Ice's Slice files (.ice files) directly and generate IceRPC integration code from them—there's no need to rewrite your Ice definitions in the new Slice syntax. Add your .ice files to your project with the SliceCompile item type, reference the new ZeroC.Ice.Slice.Tools and IceRpc.Ice packages, and you're done:

<ItemGroup>
    <SliceCompile Include="Greeter.ice" IceRpc="true" />
    <PackageReference Include="ZeroC.Ice.Slice.Tools"
                      Version="[3.8.2,3.9.0)"
                      PrivateAssets="All" />
    <PackageReference Include="IceRpc.Ice" />
</ItemGroup>

The new IceRpc.Ice package provides the integration between the IceRPC core APIs and the code generated from your .ice files, and makes it easy to communicate with Ice applications over the ice protocol. See the Ice Greeter and IceGrid examples.

The IceRpc.Templates package now includes Ice project templates, alongside the existing Slice and Protobuf templates, to help you get started quickly: icerpc-ice-client, icerpc-ice-server, icerpc-ice-di-client, and icerpc-ice-di-server (the -di- variants use Microsoft's DI container). For example:

dotnet new install IceRpc.Templates
dotnet new icerpc-ice-server -o MyServer

New Slice compiler toolchain

The Slice compiler toolchain has been re-architected. The previous all-in-one slicec-cs compiler has been replaced by the slicec compiler driving separate code generator plug-ins:

  • slicec parses and validates your Slice files and feeds the result to code generators.
  • ZeroC.Slice.Generator generates C# code for Slice structs and enums.
  • IceRpc.Slice.Generator generates C# code for Slice interfaces.

The code generators are now regular .NET assemblies written in C# rather than part of a monolithic Rust compiler, and the plug-in interface means a code generator can be written in any language with Slice support. See the IceRpc.Slice.Tools README for details.

Upgrade notes and breaking changes

  • Default multiplexed transport: existing code should switch to QUIC or add transport=tcp to keep using TCP (see above).
  • QUIC transport package removed: IceRpc.Transports.Quic no longer exists as a separate package—QUIC support is now built into the core IceRpc package. Remove any <PackageReference Include="IceRpc.Transports.Quic" /> from your project.
  • New service attribute: the [ProtobufService] and [SliceService] attributes were replaced by a single attribute, [Service]. Update your code accordingly. The ServiceAttribute class is in the IceRpc namespace.
  • New default access for generated code: the C# code generated from Slice definitions is now internal by default. You can use the [cs::public] attribute to switch back to public access.
  • Slice attribute: the cs::namespace attribute has been replaced by cs::identifier.
  • Slice modes removed: the Slice1 mode was replaced by the Ice Slice support. Remove mode = Slice1 from your Slice files and revert to the original Slice syntax in .ice files.
  • Slice typed stream mapping: when you receive a typed stream—a stream return value through a proxy, or a stream parameter in a service implementation—it is now an IceRpc.IAsyncStream<T> instead of an IAsyncEnumerable<T>. IAsyncStream<T> derives from IAsyncEnumerable<T> (so await foreach is unchanged) and also implements IDisposable: it owns the underlying transport stream, so dispose it—for example with a using declaration—when you don't iterate it to completion. The sending side is unchanged (IAsyncEnumerable<T>), and byte streams (stream uint8) still map to PipeReader.

The Stream example shows this change on the receiving (client) side:

// 0.5.x: the proxy returns a plain IAsyncEnumerable<int>.
IAsyncEnumerable<int> numbers = await generatorProxy.GenerateNumbersAsync();

// 0.6.0: the proxy returns an IAsyncStream<int> that owns the transport
// stream; dispose it with `using` when you don't read it to the end.
using IAsyncStream<int> numbers = await generatorProxy.GenerateNumbersAsync();
  • Protobuf streaming RPC mapping: the same change applies to Protobuf streaming RPCs. When you receive a stream of messages—the response of a server-streaming RPC through the generated client, or the request parameter of a client-streaming RPC in your service implementation—it is now an IceRpc.IAsyncStream<TMessage> instead of an IAsyncEnumerable<TMessage>. As with Slice, IAsyncStream<T> derives from IAsyncEnumerable<T> (so await foreach is unchanged) and adds IDisposable: it owns the underlying transport stream, so dispose it—for example with a using declaration—when you don't iterate it to completion. The sending side—the messages you return from a server-streaming RPC, or pass to a client-streaming RPC—still uses IAsyncEnumerable<TMessage>.

The Protobuf Stream example shows this change on the receiving (client) side:

// 0.5.x: a server-streaming RPC returns a plain IAsyncEnumerable<T>.
IAsyncEnumerable<GenerateResponse> messages = await generator.GenerateNumbersAsync(new Empty());

// 0.6.0: it returns an IAsyncStream<T> that owns the transport stream;
// dispose it with `using` when you don't read it to the end.
using IAsyncStream<GenerateResponse> messages = await generator.GenerateNumbersAsync(new Empty());

Other notable changes

  • Added a more convenient Map extension method for Router, which does not require an explicit type.
  • You can now set OutgoingRequest.IsOneway in an interceptor; this allows you to send a one-way request independently of the IDL you use. For example, you can send a one-way request with Protobuf.
  • [Protobuf] Added support for Protobuf editions.

Full Changelog

v0.5.2...v0.6.0