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
andIServiceClientSync
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
andIHasVersion
Request DTOs - Batched API support via
SendAll/Async
andPublishAll/Async
APIs - Global and Instance Request/Response Filters to apply generic custom logic on each request
- C# 8
await foreach
friendlyasync 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'sDateTime
orDateTimeOffset
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 without0
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'sDecimal
,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 toIRequest.QueryString
form.
- added toIRequest.FormData
cookie.
- added toIRequest.Cookies
header.
- added toIRequest.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) |