ServiceStack gRPC

gRPC Logo

ServiceStack's gRPC support enables a highly productive development environment for developing high-performance gRPC HTTP/2 Services by making ServiceStack's existing typed Services available from ASP.NET's gRPC endpoints. In addition to offering superior value in developing gRPC Services on the Server, ServiceStack also offers a simplified development model for gRPC Clients for streamlined end-to-end productivity.

Getting Started

The easiest way to get started is to start from a new grpc template that's a copy of the empty web project template pre-configured with gRPC support:

x new grpc MyGrpcProject

ServiceStack Services are gRPC Services

Whilst Protocol Buffers imposes additional restrictions on the Types that can be returned, in general the only change to ServiceStack Services following our recommended API Design are to add .NET's [DataContract] and [DataMember] attributes on each DTO member assigning unique field index to each property, i.e. what's required to support existing ProtoBuf Services or used to customize XML wire format in ServiceStack's XML or SOAP Services.

For an example, here's the complete annotated GetTodos Service from todo-world gRPC Service:

[DataContract]
public class GetTodos : IReturn<GetTodosResponse> {}

[DataContract]
public class GetTodosResponse
{
    [DataMember(Order = 1)]
    public List<Todo> Results { get; set; }

    [DataMember(Order = 2)]
    public ResponseStatus ResponseStatus { get; set; }
}

[DataContract]
public class Todo
{
    [DataMember(Order = 1)]
    public long Id { get; set; }

    [DataMember(Order = 2)]
    public string Title { get; set; }

    [DataMember(Order = 3)]
    public int Order { get; set; }

    [DataMember(Order = 4)]
    public bool Completed { get; set; }
}

public class TodoServices : Service
{
    public static List<Todo> Todos { get; } = new List<Todo>();
    public object Get(GetTodos request) => new GetTodosResponse { Results = Todos };
}

As gRPC mandates a static service contract (i.e. only returns the same Response DTO Type) Request DTOs are required to adhere to ServiceStack best practices and be annotated with either IReturn<TResponse> or IReturnVoid interfaces.

INFO

Only services annotated with IReturn* interfaces and member indexes (as above) will be registered as gRPC Services

Trying to call a Service without these annotations will result in an error that the Service you're trying to call doesn't exist.

Enable gRPC Services in existing .NET Core 3 projects

By default this Service is available in all of ServiceStack's supported formats, to also make it available via ASP.NET Core's gRPC Endpoints you can mix it into Modular Startup projects with:

x mix grpc

Which applies this modular ConfigureGrpc configuration to your project.

Or to manually configure gRPC support, add a reference to the .NET Core 3 ServiceStack.Extensions NuGet package:

dotnet add package ServiceStack.Extensions

Add the necessary dependencies:

public void Configure(IServiceCollection services)
{
    services.AddServiceStackGrpc();
}

Then register the GrpcFeature Plugin in your AppHost:

Plugins.Add(new GrpcFeature(App));

Which registers all applicable ServiceStack Services with ASP.NET Core's gRPC Endpoint and provides an auto-generated Add ServiceStack Reference /types/proto metadata endpoint that Google's protoc generated clients can use to generate typed client proxies in each of the languages Google's gRPC supports.

Advantages of ServiceStack gRPC

ServiceStack's code-first message-based development approach offers a number of advantages over most gRPC Service frameworks which rely on manually maintaining a separate plain-text .proto IDL files for defining your gRPC Services and reliance on external tooling and binding to foreign code-generated classes.

With ServiceStack gRPC, there's no additional complexity, no manual maintenance of .proto files, no reliance on external tooling, code-gen, etc is required to maintain its simple code-first development model utilizing clean POCOs which is still able to retain its ideal end-to-end Typed API utilizing smart, rich .NET generic Service Clients:

//IRestServiceClient client = new JsonServiceClient(BaseUrl);
IRestServiceClient client = new GrpcServiceClient(BaseUrl);
var response = await client.GetAsync(new GetTodos());

Like other .NET Service Clients, GrpcServiceClient is a substitutable full-featured Service Client should consumers prefer to revert to using other more versatile, interoperable, debuggable and ubiquitous serialization formats.

Maximize reuse of Knowledge and Investments

Likely the biggest advantage in using ServiceStack to develop gRPC Services is being able to (without additional knowledge) leverage your existing investments and knowledge of building HTTP Services which in most cases (after applying above annotations) are automatically made available as gRPC Services. This maximizes utility of your Services which can be simultaneously made available via ASP.NET's gRPC Endpoints and ServiceStack's HTTP and MQ Endpoints alleviating the need to fork or duplicate your Services logic across multiple implementations, or worse, taking the leap to develop gRPC-only services and shutting out clients and environments that can't make use of gRPC HTTP/2 endpoints.

ServiceStack enables the best of both worlds, you can take the risk-free step of making your Services available via highly efficient and performant gRPC HTTP/2 Services for clients and environments that can take advantage of it whilst (in the same App) continuing to make them available via the ubiquitous JSON HTTP/1.1 APIs or in any of their preferred formats.

gRPC code-first Development

By allowing the use of idiomatic C# POCOs to define your Services contract, code-first always enables a superior development experience which avoids having to rely on external build tools to generate foreign implementation-encumbered types limited in capability by what's generated by its opaque tooling where it emits single-purpose Types limited for usage in gRPC Services - restricted to the lowest common denominator capabilities of .proto files.

By contrast with a code-first approach your idiomatic C# POCOs remain the master authority for your Services contract in which you retain full control over, that can be further enhanced with attributes to enlist declarative behavior and shared interfaces and are in general inherently easier to develop genericized behavior around. As Service Models doesn't have any implementation dependencies they can be easily shared and referenced in any .NET Project and as clean POCOs they can serve as multi-purpose models enabling maximum reuse where they can be utilized in all ServiceStack's POCO libraries as well being supported in all of ServiceStack's supported formats.

Code-First gRPC Services

ServiceStack's code-first gRPC Services enabled by protobuf-net.Grpc where instead of imposing the high maintenance burden of manually authoring .proto to define gRPC Services on the developer and resulting in awkward generated classes in both the C# Service implementation as well as the protoc generated clients.

A code-first development approach allows use of the higher-level & more expressive power of C# & its rich static analysis to intuitively declare exactly the Service you want to provide.

E.g. an AutoQuery Service uses both inheritance and generic response Types is simply declared in a single C# Request DTO with exactly what querying features you want to be discoverable for this Service:

public class QueryCategory : QueryDb<Category>
{
    public int Id { get; set; }
    public string CategoryName { get; set; }
}

Which you could call in an end-to-end API without code-gen, using the smart C# Generic gRPC Service Client which supports protobuf-net high-level retrofitted support for both inheritance and generic responses:

var response = await client.GetAsync(new QueryCategory { CategoryName = "Vegetables" });

But as .proto doesn't natively support either inheritance or generic classes the proto clients generates an unusable and awkward has vs is a base for the retrofitted inheritance message hack requiring every base message type to define every possible subtype message. The pursuit of a better dev UX inspired the creation of the Dynamic gRPC Requests feature, enabling the more natural and UX-friendly way to invoke Services using a flattened unstructured string Dictionary, akin to a ?QueryString in HTTP Requests:

var response = await client.GetDynamicQueryCategoryAsync(new DynamicRequest {
    Params = {
        { "CategoryName", "Vegetables" }, 
        { "OrderBy", "Id" }, 
        { "Include", "Total" },
    }
});

This feature also allows you to construct a generic DynamicRequest Request Message that can invoke any Service making it useful in scenarios where you want to dynamically construct & invoke different Services like in a Request Query Builder as done in Studio and SharpData UIs. s

Flattened Request Hierarchy's

To improve support for protoc generated Service Clients Request DTOs automatically flatten multiple inheritance hierarchy's into a single message type in the dynamically generated .proto gRPC Services description so the C# QueryCategory Service above will elide the inheritance tree and expose it as a flattened Service message containing both implicit base functionality available to all AutoQuery Services in combination with the explicit Querying functionality specific for each AutoQuery Service.

So the above QueryCategory AutoQuery Service above are defined in the generated gRPC .proto as:

service GrpcServices {
    rpc GetQueryCategory(QueryCategory) returns (QueryResponse_Category) {}
}

message QueryCategory {
   int32 Skip = 1;
   int32 Take = 2;
   string OrderBy = 3;
   string OrderByDesc = 4;
   string Include = 5;
   string Fields = 6;
   map<string,string> Meta = 7;
   int64 Id = 201;
   string CategoryName = 202;
}

message QueryResponse_Category {
   int32 Offset = 1;
   int32 Total = 2;
   repeated Category Results = 3;
   map<string,string> Meta = 4;
   ResponseStatus ResponseStatus = 5;
}

This enables protoc generated clients with the more optimal generated typed API for calling ServiceStack's AutoQuery Services down to:

// Dart
var response = await client.getQueryCategory(QueryCategory()..categoryName='Vegetables');
print(response.results);

No additional complexity or artificial machinery

At its most productive usage in .NET Apps, ServiceStack offers the same highly-productive friction-free development experience that's enjoyed in its other .NET Generic Service Clients - requiring no tooling, code-generation or any other artificial machinery, as the same clean POCO DTOs used to define your Services can be reused on the client where by preserving any additional annotations or interfaces that can allow for a richer client development experience.

An alternative to sharing your ServiceModel.dll with .NET clients is for them to use Add ServiceStack Reference to generate .NET DTOs locally which they can easily do using the x dotnet tool, e.g:

x csharp https://todoworld.servicestack.net

This offers the same behavior as sharing ServiceModel.dll binary where they can be used in any .NET Generic Service Client, but also allows for clients to customize DTO generation to suit their own preferences.

Smart, Substitutable, Generic GrpcServiceClient

Despite requiring much less complexity, the use of a generic Service Client offers a superior development model then what's available in Google's protoc generated Service Clients that's also able to be enjoyed by VB.NET and F# App developers for whom previously there was no planned gRPC support.

The new GrpcServiceClient can be used in .NET Standard 2.1 and .NET Core 3 .NET Clients by adding a reference to:

dotnet add package ServiceStack.GrpcClient

This is a full-featured generic Service Client that provides a nicer and cleaner API than what's possible with protoc generated clients and contains most of the built-in functionality of other C#/.NET Typed Generic Clients, namely:

  • Substitutable IRestServiceClient, IServiceClientAsync and IServiceClientSync interfaces
  • Rich Detailed and Structured Typed Exceptions
  • Built-in Credentials, JWT, API Key and Session Authentication
    • Transparent auto retry on expired JWT Bearer Tokens after retrieving new JWT from configured Refresh Token
  • Auto populates Version/Auth info in IHasSessionId, IHasBearerToken and IHasVersion Request DTOs
  • Batched API support via SendAll/Async and PublishAll/Async APIs
  • Global and Instance Request/Response Filters to apply generic custom logic on each request
  • C# 8 await foreach friendly async IAsyncEnumerable<TResponse> Stream() APIs for Server Stream gRPC Services

Importantly GrpcServiceClient implements the same interfaces shared by other C#/.NET Typed Generic Clients allowing development of higher-level shared client logic and libraries decoupled from concrete implementations, improved testability, easy substitution to other clients for improved debuggability or working around limitations in protocol buffers, as well as parallel client/server development where devs can temporarily bind to mock clients before server APIs are implemented.

The predetermined Generic API Surface area of the service client interfaces also makes it easier to develop enhanced functionality like Cache Aware Service Clients in future that existing Apps will be able to utilize via a minimally disruptive "drop-in" implementation.

Prefer Async

As all .NET gRPC Clients are inherently built on an async implementation, clients should prefer *Async APIs wherever possible as both protoc and GrpcServiceClient only offer nonoptimal "sync over async" APIs.

ServiceStack Interceptor for protoc generated clients

As GrpcServiceClient offers a nicer UX its usage over protoc generated clients should generally be preferred, one area where you'll want to consider using Google's protoc generated clients is in AOT environments like Xamarin.iOS as the protoc tooling emits AOT-friendly Protocol Buffer serialization implementations within its code generated Types.

To better accommodate scenarios where protoc clients are used you can use the ServiceStackClientInterceptor (also in ServiceStack.GrpcClient) to replicate most of the rich ServiceStack integration features that's possible to implement using an Interceptor.

ServiceStack's Interceptor can be registered using GrpcServiceStack.Client() when creating the protoc GrpcServicesClient:

// Insecure plain-text example
GrpcClientFactory.AllowUnencryptedHttp2 = true;
var client = new GrpcServices.GrpcServicesClient(
    GrpcServiceStack.Client("http://todoworld.servicestack.net:5054"));

// SSL Example
var client = new GrpcServices.GrpcServicesClient(
    GrpcServiceStack.Client("https://todoworld.servicestack.net:50051", 
        new X509Certificate2("grpc.crt"),
        GrpcUtils.AllowSelfSignedCertificatesFrom("todoworld.servicestack.net")));

// Optional (avoid protobuf-net deserialization)
GrpcServiceStack.ParseResponseStatus = bytes => ResponseStatus.Parser.ParseFrom(bytes);

Preserve rich Semantics and API Design

gRPC Services enables an efficient long-lived HTTP/2 multiplexed channel with performant Protocol Buffer serialization and a vast code-generation framework allowing us to provide Typed clients for most major programming languages.

An area that could see a regression which all RPC frameworks suffer from is the erosion of the Goals of Service Design and loss of loosely-coupled resource-oriented HTTP API Design centered around applying actions (aka HTTP Verbs) to the request subjects, laying out a logical structure for your API Design that's also able to better communicate at a higher-level the commonly understood properties of each HTTP method.

Forcing the usage of messages in gRPC Service Requests partially mitigates against the fragile usage of chatty client-specific method signatures plagued by most RPC frameworks like WCF, but still makes it easy for API designs to descend into a logically unstructured "free-for-all" surface area adopting non-standard conventions making it harder and requiring more effort to convey understanding to your API consumers.

By continuing to develop your ServiceStack Services as a good HTTP First citizens using coarse-grained loosely-coupled messages oriented around resources, a lot of the rich HTTP semantics is preserved where each Verb remains accessible where its prefixed at the start of the rpc Method name:

rpc [Verb][RequestType](RequestType) returns (ResponseType) {}

The original HTTP Status Code remains accessible from the httpstatus gRPC Metadata Response Header that continues to be populated in the WebServiceException.StatusCode thrown in GrpcServiceClient or protoc clients configured with the ServiceStack Interceptor. Any Custom HTTP Headers added by your Services are also returned in gRPC headers including your Services detailed structured Exception information stored as a serialized ResponseStatus message in the responsestatus-bin Header that continues to be available in WebServiceException.ResponseStatus property allowing gRPC client Apps to develop rich form validation bindings that's otherwise not possible in gRPC's failed responses containing just an error code and simple text description.

Offer best API for every platform

Given the trade-offs of gRPC we still expect the traditional and more ubiquitous HTTP/REST API endpoints to be more widely utilized in areas where you don't control both client and server Apps, where simplicity and interoperability is more important than maximum performance, when needing to support Ajax requests in Web Apps where HTTP/JSON is the lingua franca with rich support baked into many JS libraries or if needing to support environments where deeper integration of different formats is preferred, e.g. utilizing CSV Format in Excel based workflows or importing datasets into Databases where it's natively supported in most RDBMS's tooling.

With the additional "complexity tax" for adopting a gRPC-based solution workflow, many App developers are still going to prefer the simplicity of consuming a HTTP/JSON API from a URL and HTTP API docs.

As gRPC is just another endpoint for your ServiceStack Services you don't have to take the risk of committing to one scenario at the expense of all others and can continue to serve all client consumers with the best API for every platform simultaneously.

Architecture

Unlike in other Services where ServiceStack handles writing the response directly to the HttpResponse, ServiceStack's gRPC Services are the implementation for ASP.NET Core gRPC Endpoint requests where it makes use of Marc Gravell's code-first protobuf-net.Grpc library to enable ServiceStack's code-first development model of using typed Request/Response DTOs to implement and consume gRPC Services.

Requests are executed using the RpcGateway which provides a pure object model for executing the full HTTP Request pipeline which returns the Response DTO back to ASP .NET Core gRPC which handles sending the response back to the HTTP/2 connected client.

Limitations

Protocol Buffers have a number of restrictions in Types it supports:

Inheritance

In its pursuit of defining a universal service description that can be both implemented and consumed by each major programming language, Google's gRPC .proto and Protocol Buffers suffers from the limitations of needing to abide by the lowest common denominator functionality available in all languages that's limited to use Protocol Buffers built-in and Well Known Types which is much more restrictive than schema-less formats like JSON where the Type definition is retained in native POCOs used to de/serialize JSON.

The Types of limitations applicable when building gRPC Services in .NET include:

  • No support for Generic Types
  • No proper support for Inheritance
  • All enums need a zero value
  • All top-level enum names to be globally unique across all enums used in your Services
  • No built-in Types that can accommodate .NET's Decimal, Guid Types
  • Loss of precision when using the built-in Timestamp to serialize .NET's DateTime or DateTimeOffset

The protobuf-net library used in both Server and GrpcServiceClient implementations does its best to transparently work around above limitations, e.g:

  • Creates multiple messages using reified generic type names for each concrete Generic Type used
  • Allows defining layout of base Types to embed ("has a") relation of known sub types
  • Emits ZERO enum value for enums without 0 default values
    • Due to unique naming enum scoping rules it's an error to do this for more than 1 Enum
  • Utilizing custom Types defined in an accompanying bcl.proto to support .NET's Decimal, Guid Types
  • Other BCL Types like DateTimeOffset can be supported via surrogates

Features

In addition to its code-first development model ServiceStack gRPC adds a number of useful features to simplify and provide a richer gRPC Services development experience:

protobuf-net Inheritance

As it's important to best provide a seamless out-of-the-box solution to alleviate as much friction as possible, Request DTOs flatten their inheritance hierarchy in order to provide the optimal typed API for .proto generated clients.

Inheritance in Request DTOs can provide a natural way to compose functionality, otherwise using inheritance in DTOs is not recommended to avoid runtime serialization & interoperability issues and define more explicit Service Contracts.

In order to support inheritance protobuf-net has to retrofit its support by embedding sub classes as different fields in the base class type. This is awkward since all known sub classes needs to be defined upfront on the base type using a consistent and non-conflicting numerical field id.

To support inheritance in gRPC, ServiceStack pre-configures a numerical index on all known sub types in your Services Contract with a generated Murmur2 hash (best overall for speed/randomness) of the Types Name that's modded to within the 2^29-1 range of valid field ids in protocol buffers - resulting in a low (but still possible) index collision.

To instead use your own user-defined field id for inherited classes you can use the [Id] attribute, for AutoQuery Services it should at least start from 10 to avoid conflicts with its base class properties, e.g:

[DataContract, Id(10)]
public class Rockstar : RockstarBase
{
    [DataMember(Order = 1)]
    public Guid Id { get; set; }
}

The [Id] needs to be unique across all Sub Types and must not conflict with base class field ids, given AutoQuery Services share the same base-class, any user-defined [Id(N)] used needs to be unique across all your AutoQuery Services.

Both implicit or explicit [Id] inherited Types works nicely with the generic GrpcServiceClient where it allows calling the base and Sub Type properties, e.g:

var response = await client.GetAsync(new QueryRockstars { Age = 27, Include = "Total" });

Unfortunately usage of inheritance is currently not compatible with protoc clients so in order to call AutoQuery Services from protoc generated clients you can utilize Dynamic gRPC Requests to execute any Service from an loosely-typed string dictionary.

Dynamic gRPC Requests

Dynamic Requests lets you to populate Request DTOs of gRPC Services with a DynamicRequest DTO in the same way as QueryString and FormData is used to populate a Request DTO, in this case DynamicRequest is just a Request DTO containing a string dictionary:

[DataContract]
public class DynamicRequest
{
    [DataMember(Order = 1)]
    public Dictionary<string, string> Params { get; set; }
}

Which just like QueryStrings/FormData are also able to populate deeply nested object graphs from a JSV string

By default ServiceStack only generates Dynamic Services for Services annotated with the [Tag("Dynamic")] attribute, e.g:

[Tag(Keywords.Dynamic)]
[DataContract]
public class QueryRockstars : QueryDb<Rockstar>
{
    [DataMember(Order = 1)]
    public int? Age { get; set; }
}

This generates an additional Service that uses a DynamicRequest DTO input, in the format:

rpc [Verb]Dynamic[RequestType](DynamicRequest) returns (ResponseType) {}

For the QueryRockstars above, it generates:

rpc GetDynamicQueryRockstars(DynamicRequest) returns (QueryResponse_Rockstar) {}

Dynamic Requests are useful when you'd prefer to be able to populate Requests from an untyped string Dictionary such as implementing a dynamic Query Builder UI which would require significantly less effort and boilerplate then trying to map dynamic rules into populating a typed Request.

They're also required for calling Services that doesn't have an explicit Service contract like any untyped AutoQuery Services utilizing implicit conventions, e.g:

public class QueryRockstars : QueryDb<Rockstar> {}

Until protoc clients are compatible with protobuf-net inheritance they can be used as an alternative for calling inheritance-based Request DTOs like AutoQuery.

The CreateDynamicService predicate determines which Services should have dynamic requests generated for it, by default it's limited to Request DTOs annotated with [Tag("Dynamic")], but can also be made to have dynamic requests generated for all AutoQuery Services with:

Plugins.Add(new GrpcFeature(App) {
    CreateDynamicService = GrpcConfig.AutoQueryOrDynamicAttribute
});

When configured each AutoQuery Service will have an additional Service starting with GetDynamic* available that can be called with a DynamicRequest, in fact the same DynamicRequest can be used to call either Typed or Untyped AutoQuery Services, e.g:

var response = await client.GetDynamicQueryRockstarsAsync(new DynamicRequest {
    Params = {
        { "Age", "27" }, 
        { "OrderBy", "FirstName" },
        { "Include", "Total" },
    }
});

In addition to populating the Request DTO each dynamic param will also be available to your services via the IRequest.QueryString collection.

Simulate HTTP Requests

Similar to DynamicRequest even normal typed gRPC Service requests can be augmented with gRPC Metadata Request Headers where they can be used to be able to simulate an HTTP Request where headers starting with:

  • query. - added to IRequest.QueryString
  • form. - added to IRequest.FormData
  • cookie. - added to IRequest.Cookies
  • header. - added to IRequest.Headers (default)
  • Remaining headers without above prefixes are added to IRequest.Headers as-is.

This can be used to simulate a valid HTTP Request for Filters, Plugins and HTTP APIs that are specific in how they analyze HTTP Requests.

So just like DynamicRequest you can use dynamic gRPC Metadata Headers to populated typed gRPC Service requests, e.g:

var client = new GrpcServiceClient(baseUrl) {
    RequestFilter = ctx => {
        ctx.RequestHeaders.Add("UserAgent", "Googlebot/2.1 (+http://www.google.com/bot.html)"); //impersonate
        ctx.RequestHeaders.Add("query.Age", "27");
        ctx.RequestHeaders.Add("query.OrderBy", "FirstName");
        ctx.RequestHeaders.Add("query.Include", "Total");
    }
};
var response = await client.GetAsync(new QueryRockstars());

Where each matching property will populate the Request DTO with the string value using the conversion rules in ServiceStack's Auto Mapping.

If you don't wish for Headers to be able to populate Typed gRPC Requests, it can be disabled with:

Plugins.Add(new GrpcFeature(App) {
    DisableRequestParamsInHeaders = true
});

Server Stream gRPC Services

All gRPC Services we've seen so far are what gRPC Refers to as Unary RPC, i.e. where clients sends a single request to the server and gets a single response back. Another very useful communication style supported by gRPC is Server streaming:

the client sends a request to the server and gets a stream to read a sequence of messages back. The client reads from the returned stream until there are no more messages. gRPC guarantees message ordering within an individual RPC call.

StreamServerEvents

There are a couple of scenarios in ServiceStack where this communication channel is especially useful such as Server Events which operates in a similar style with clients connecting to a long-lived HTTP connection that streams back "real-time Events" over the light and efficient SSE standard natively supported in modern browsers.

Although as HTTP Requests are not normally used for maintaining long-lived connections they're susceptible to issues like buffering from App Servers, middleware and proxies and require implementing a bespoke health-check and auto-reconnect solution in order to maintain interrupted service.

As a first class supported communication channel clients can instead leverage gRPC's library infrastructure which is perfectly suited for streaming real-time Server Events over an efficient persistent HTTP/2 channel that's available from the StreamServerEvents gRPC Service:

rpc ServerStreamServerEvents(StreamServerEvents) returns (stream StreamServerEventsResponse) {}

Which gives all protoc supported languages a Typed Client for consuming your Server Events.

GrpcServiceClient Streams

When using the generic GrpcServiceClient you're able to take advantage of C#'s 8 new await foreach syntax sugar for consuming gRPC Server Streams.

Its usage is analogous to all Server Events clients where your initial connection contains the channels you want to subscribe to receive notifications from, e.g:

var stream = client.StreamAsync(new StreamServerEvents {
    Channels = new[] { "todos" }
});

Then you can use await foreach to consume an endless stream of Server Events. Use Selector to identify the type of Server Event whilst the complex-type body of each event message can be parsed from its JSON body, e.g:

await foreach (var msg in stream)
{
    if (msg.Selector.StartsWith("todos.")) //custom todos.* events
    {
        var obj = JSON.parse(msg.Json); //body of message in JSON
        if (obj is Dictionary<string, object> map)
        {
            //todos.create + todos.update properties
            var id = map["id"];
            var title = map["title"];
            $"EVENT {msg.Selector} [{msg.Channel}]: #{id} {title}".Print(); 
        }
        else
        {
            //todos.delete id
            $"EVENT {msg.Selector} [{msg.Channel}]: {obj}".Print();
        }
    }
    else
    {
        // general server events, e.g cmd.onConnect, cmd.onJoin, cmd.onLeave
        $"EVENT {msg.Selector} [{msg.Channel}]: #{msg.UserId} {msg.DisplayName}".Print();
    }
}

If connected whilst running the TodoWorld CRUD Example this stream will output something similar to:

EVENT cmd.onConnect []: #-1 user1
EVENT cmd.onJoin [todos]: #-1 user1
EVENT todos.create [todos]: #1 ServiceStack
EVENT todos.update [todos]: #1 gRPC
EVENT todos.delete [todos]: 1

protoc Dart Streams

Other protoc languages will require using their own language constructs for consuming gRPC Streams, here's the example for Dart that also has a pleasant API for consuming Server Streams:

var stream = client.serverStreamServerEvents(StreamServerEvents()..channels.add('todos'));
await for (var r in stream) {
    var obj = jsonDecode(r.json);
    if (r.selector.startsWith('todos')) {
        if (obj is Map) {
            print('EVENT ${r.selector} [${r.channel}]: #${obj['id']} ${obj['title']}');
        } else {
            print('EVENT ${r.selector} [${r.channel}]: ${obj}');
        }
    } else {
        print('EVENT ${r.selector} ${r.channels}: #${obj['userId']} ${obj['displayName']}');
    }
}

Implementing Server Stream Services

As they're not your typical unary-style Request/Response service, Server Streams are handled and implemented a little differently where in addition to inheriting ServiceStack's base Service class you'll need to implement the IStreamService<TRequest,TResponse> interface and implement its Stream() method for your Server Stream implementation.

Here's the implementation of ServiceStack's built-in StreamFiles Service which accepts multiple virtual paths of files and returns the File contents and metadata in the same order:

public class StreamFileService : Service, IStreamService<StreamFiles,FileContent>
{
    public async IAsyncEnumerable<FileContent> Stream(StreamFiles request, CancellationToken cancel = default)
    {
        var i = 0;
        var paths = request.Paths ?? TypeConstants.EmptyStringList;
        while (!cancel.IsCancellationRequested)
        {
            var file = VirtualFileSources.GetFile(paths[i]);
            var bytes = file?.GetBytesContentsAsBytes();
            var to = file != null
                ? new FileContent {
                    Name = file.Name,
                    Type = MimeTypes.GetMimeType(file.Extension),
                    Body = bytes,
                    Length = bytes.Length,
                }
                : new FileContent {
                    Name = paths[i],
                    ResponseStatus = new ResponseStatus {
                        ErrorCode = nameof(HttpStatusCode.NotFound),
                        Message = "File does not exist",
                    }
                };
            
            yield return to;

            if (++i >= paths.Count)
                yield break;
        }
    }
}

Although StreamFiles is already pre-registered, to register your own gRPC Stream Service add them to the RegisterServices collection:

Plugins.Add(new GrpcFeature(App) {
    RegisterServices = {
        typeof(StreamFileService)
    }
});

INFO

Or remove the pre-registered StreamFileService and SubscribeServerEventsService services to disable them

Clients can use StreamFiles to efficiently download multiple files over a single gRPC HTTP/2 Server Stream connection in their preferred order:

var request = new StreamFiles {
    Paths = new List<string> {
        "/js/ss-utils.js",
        "/js/hot-loader.js",
        "/js/not-exists.js",
        "/js/hot-fileloader.js",
    }
};

var files = new List<FileContent>();
await foreach (var file in client.StreamAsync(request))
{
    files.Add(file);
}

With each FileContent result containing either the file contents and metadata or an error response for missing files, e.g:

// files[0].Name = 'ss-utils.js'
// files[1].Name = 'hot-loader.js'
// files[2].ResponseStatus.ErrorCode = NotFound
// files[3].Name = 'hot-fileloader.js'

protoc Dart Example

Example of server streaming of files from a protoc generated Dart client:

var stream = client.serverStreamFiles(StreamFiles()..paths.addAll([
  '/js/ss-utils.js',
  '/js/hot-loader.js',
  '/js/hot-fileloader.js',
]));

await for (var file in stream) {
  var text = utf8.decode(file.body);
  print('FILE ${file.name} (${file.length}): ${text.substring(0, text.length < 50 ? text.length : 50)} ...');
}

SSL Certificate Configuration

Please see the gRPC SSL docs for information on how to secure your gRPC connections including scripts for creating custom self-signed certificates and hosting public gRPC Services behind nginx reverse proxies.

gRPC Clients

Visit todoworld.servicestack.net to explore how easy it is to consume ServiceStack gRPC Services in different languages.

For most .NET Clients we recommend using our generic GrpcServiceClient with deeper ServiceStack integration that can be used with the richer Add ServiceStack Reference POCO DTOs that implements the same shared Service Client interfaces adopted by all ServiceStack's .NET Service Clients:

protoc generated clients

For all non .NET Clients and AOT environments like Xamarin.iOS we recommend using Google's protoc generated clients:

gRPC Web

As it's impossible to implement the HTTP/2 gRPC spec in the browser, in order to be able to consume gRPC services from a browser a gRPC Web Proxy is needed.

The current recommendation from the gRPC Web team is to Configure the Envoy Proxy to forward gRPC browser requests to the native gRPC endpoint, however as it adds more moving parts and additional complexity, if you're not already using envoyproxy we instead recommended using ServiceStack HTTP JSON Services, made possible since ServiceStack's gRPC Service implementations are also made available over REST-ful HTTP APIs - i.e. the lingua franca of the web.

If ASP.NET Core adds native gRPC Web support then using gRPC clients may provide a more appealing option although it won't have a clean, versatile and rich API as TypeScript Add ServiceStack Reference.

x dotnet tool gRPC Web support

If wanting to evaluate using a gRPC Web Proxy you can use generate different TypeScript and JavaScript clients using the commands below:

$ x proto-ts <url>             # TypeScript + gRPC Web Text
$ x proto-ts-binary <url>      # TypeScript + gRPC Web Binary
$ x proto-js-closure <url>     # Google Closure + gRPC Web Text
$ x proto-js-commonjs <url>    # Common JS + gRPC Web Text

Or if preferred you can use the online UI or HTTP API for generating Protocol Buffers and gRPC client proxies at grpc.servicestack.net.

gRPC Configuration

There are a number of additional configuration options for customizing and extending ServiceStack's gRPC support:

Protobuf Serialization

Behavior of how protobuf-net de/serializes .NET Models can be customized by modifying GrpcConfig.TypeModel where any customizations should be applied to both server and client apps if using C# generic GrpcServiceClient.

Any Methods

ServiceStack lets you define Any() services which can be invoked when called on each HTTP Method. To minimize gRPC surface area pollution, by default ServiceStack only generates different rpc endpoints for HTTP's primary Get*, Post*, Put* and Delete* verbs.

To change this for all Any services specify which methods you want generated in DefaultMethodsForAny list, e.g. you can specify to only generate Get* and Post* methods with:

Plugins.Add(new GrpcFeature(App) {
    DefaultMethodsForAny = new List<string> { 
        HttpMethods.Get,
        HttpMethods.Post,
    }
});

As AutoQuery Services are read-only queries they only default to generating Get* rpc methods, which can be changed with:

Plugins.Add(new GrpcFeature(App) {
    AutoQueryMethodsForAny = new List<string> { 
        HttpMethods.Get,
        HttpMethods.Post,
    }
});

This can be customized per service by annotating your Request DTO with IVerb marker interfaces, e.g:

[DataContract]
public class Hello : IReturn<HelloResponse>, IGet, IPost { ... }

public class MyServices : Service
{
    public object Any(Hello request) => ...;
}

Alternatively you can replace your Any() method and only implement the specific methods you want generated, e.g. Get() or Post().

For even finer-grained customization you can override the GenerateMethodsForAny predicate to adopt your own conventions.

Custom gRPC Status Codes

gRPC has are reduced number of Error Status Codes compared to what's available in HTTP, you can override ServiceStack's built-in HTTP > gRPC mapping with your own implementation by populating ToGrpcStatus, e.g:

Plugins.Add(new GrpcFeature(App) {
    ToGrpcStatus = httpRes => httpRes.StatusCode == 404
        ? new Status(StatusCode.NotFound, httpRes.StatusDescription)
        : (Status?) null // use default behavior
});

Filtered HTTP Headers

You can control which of your custom HTTP Headers you don't want to return by adding them to the IgnoreResponseHeaders collection:

Plugins.Add(new GrpcFeature(App) {
    IgnoreResponseHeaders = { HttpHeaders.ContentDisposition }
});

Proto Options

The auto-generated .proto service description ServiceStack generates at /types/proto allows for proto options to customize protoc code-generation behavior which are pre-configured with C# and PHP namespaces used in its generated proxy clients.

You can include your own proto options by registering them in the ProtoOptions collection as seen below:

Plugins.Add(new GrpcFeature(App) {
    ProtoOptions = new List<ProtoOptionDelegate> { 
        ProtoOption.CSharpNamespace,
        ProtoOption.PhpNamespace,
    }
});

public static class ProtoOption
{
    public static string CSharpNamespace(IRequest req, MetadataTypesConfig config) =>
        $"option csharp_namespace = \"{config.GlobalNamespace}\";";

    public static string PhpNamespace(IRequest req, MetadataTypesConfig config) =>
        $"option php_namespace = \"{config.GlobalNamespace}\";";
}

Public gRPC protoc Service and UI

To provide the simplest and seamless end-to-end gRPC solution we're maintaining a public gRPC protoc Service and UI which is the backend empowering our cross-platform dotnet tools to be able to generate Protocol Buffer DTOs and gRPC clients in every protoc supported language without any installation, tooling or configuration required.

This is a public service any gRPC clients using any gRPC Service framework can use as an alternative for having each client configure and maintain their build system to use protoc tooling.

Local .proto files aren't necessary for ServiceStack gRPC Services with gRPC clients only needing a URL, e.g:

x proto-<lang> https://todoworld.servicestack.net

From .proto descriptors

Other clients can generate protoc clients from either a single .proto services description:

x proto-<lang> services.proto

Or upload multiple .proto files by specifying a directory instead:

x proto-<lang> /path/to/grpc/protos

Use -out to specify a different directory to save the protoc generated classes to, e.g:

x proto-<lang> services.proto -out /path/to/dir

Using curl

Alternatively you can use curl command-line HTTP Client to download protoc generated classes in a .zip archive:

curl -F 'file1=@services.proto' https://grpc.servicestack.net/protoc/[lang]?zip -L -o grpc.zip

Below is a complete list of different languages supported by this public gRPC Service:

Lang Description
cpp C++
csharp C#
dart Dart
go Go
java Java
java-lite Java (Lite)
js-node JavaScript (node.js)
objc Objective C
php PHP
python Python
ruby Ruby
swift Swift
gRPC Web Languages
js-closure JavaScript (Closure)
js-commonjs JavaScript (CommonJS)
ts TypeScript
ts-binary TypeScript (Binary)