Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/max-ieremenko/servicemodel.grpc
Code-first for gRPC
https://github.com/max-ieremenko/servicemodel.grpc
code-first csharp grpc grpc-dotnet servicemodel swagger wcf
Last synced: 3 days ago
JSON representation
Code-first for gRPC
- Host: GitHub
- URL: https://github.com/max-ieremenko/servicemodel.grpc
- Owner: max-ieremenko
- License: apache-2.0
- Created: 2020-04-10T09:22:41.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2024-10-16T05:54:15.000Z (29 days ago)
- Last Synced: 2024-10-17T21:33:49.388Z (27 days ago)
- Topics: code-first, csharp, grpc, grpc-dotnet, servicemodel, swagger, wcf
- Language: C#
- Homepage: https://max-ieremenko.github.io/ServiceModel.Grpc/
- Size: 14.7 MB
- Stars: 90
- Watchers: 12
- Forks: 11
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ServiceModel.Grpc
`ServiceModel.Grpc` enables applications to communicate with gRPC services using a code-first approach (no .proto files), helps to get around limitations of gRPC protocol like "only reference types", "exact one input", "no nulls", "no value-types". Provides exception handling. Helps to migrate existing WCF solution to gRPC with minimum effort.
The library supports lightweight runtime proxy generation via Reflection.Emit and C# source code generation.
The solution is built on top of [gRPC C#](https://github.com/grpc/grpc/tree/master/src/csharp) and [grpc-dotnet](https://github.com/grpc/grpc-dotnet).
## Links
- [ServiceModel.Grpc at a glance](#intro)
- [NuGet feed](#nuget)
- [Benchmarks](#Benchmarks)
- [docs](https://max-ieremenko.github.io/ServiceModel.Grpc/)
- [service and operation names](https://max-ieremenko.github.io/ServiceModel.Grpc/ServiceAndOperationName.html)
- [service and operation bindings](https://max-ieremenko.github.io/ServiceModel.Grpc/ServiceAndOperationBinding.html)
- [client configuration](https://max-ieremenko.github.io/ServiceModel.Grpc/ClientConfiguration.html)
- [client code generation](https://max-ieremenko.github.io/ServiceModel.Grpc/client-code-generation.html)
- [client dependency injection](https://max-ieremenko.github.io/ServiceModel.Grpc/client-dependency-injection.html)
- [server code generation](https://max-ieremenko.github.io/ServiceModel.Grpc/server-code-generation.html)
- operations
- [unary](https://max-ieremenko.github.io/ServiceModel.Grpc/operation-unary.html)
- [client streaming](https://max-ieremenko.github.io/ServiceModel.Grpc/operation-client-streaming.html)
- [server streaming](https://max-ieremenko.github.io/ServiceModel.Grpc/operation-server-streaming.html)
- [duplex streaming](https://max-ieremenko.github.io/ServiceModel.Grpc/operation-duplex-streaming.html)
- [ASP.NET Core server configuration](https://max-ieremenko.github.io/ServiceModel.Grpc/ASPNETCoreServerConfiguration.html)
- [Grpc.Core server configuration](https://max-ieremenko.github.io/ServiceModel.Grpc/GrpcCoreServerConfiguration.html)
- [exception handling general information](https://max-ieremenko.github.io/ServiceModel.Grpc/error-handling-general.html)
- [global exception handling](https://max-ieremenko.github.io/ServiceModel.Grpc/global-error-handling.html)
- [client filters](https://max-ieremenko.github.io/ServiceModel.Grpc/client-filters.html)
- [server filters](https://max-ieremenko.github.io/ServiceModel.Grpc/server-filters.html)
- [compatibility with native gRPC](https://max-ieremenko.github.io/ServiceModel.Grpc/CompatibilityWithNativegRPC.html)
- [migrate from WCF to a gRPC](https://max-ieremenko.github.io/ServiceModel.Grpc/MigrateWCFServiceTogRPC.html)
- [migrate from WCF FaultContract to a gRPC global error handling](https://max-ieremenko.github.io/ServiceModel.Grpc/migrate-wcf-faultcontract-to-global-error-handling.html)
- [examples](Examples)## ServiceModel.Grpc at a glance
### Declare a service contract
``` c#
[ServiceContract]
public interface ICalculator
{
[OperationContract]
Task Sum(long x, int y, int z, CancellationToken token = default);[OperationContract]
ValueTask<(int Multiplier, IAsyncEnumerable Values)> MultiplyBy(IAsyncEnumerable values, int multiplier, CancellationToken token = default);
}
```### Client call (Reflection.Emit)
A proxy for the ICalculator service will be generated on demand via `Reflection.Emit`.
```powershell
PS> Install-Package ServiceModel.Grpc
`````` c#
// create a channel
var channel = new Channel("localhost", 5000, ...);// create a client factory
var clientFactory = new ClientFactory();// request the factory to generate a proxy for ICalculator service
var calculator = clientFactory.CreateClient(channel);// call Sum: sum == 6
var sum = await calculator.Sum(1, 2, 3);// call MultiplyBy: multiplier == 2, values == [] {2, 4, 6}
var (multiplier, values) = await calculator.MultiplyBy(new[] {1, 2, 3}, 2);
```### Client call (source code generation)
A proxy for the ICalculator service will be generated in the source code.
```powershell
PS> Install-Package ServiceModel.Grpc.DesignTime
`````` c#
// request ServiceModel.Grpc to generate a source code for ICalculator service proxy
[ImportGrpcService(typeof(ICalculator))]
internal static partial class MyGrpcServices
{
// generated code ...
public static IClientFactory AddCalculatorClient(this IClientFactory clientFactory, Action configure = null) {}
}// create a channel
var channel = new Channel("localhost", 5000, ...);// create a client factory
var clientFactory = new ClientFactory();// register ICalculator proxy generated by ServiceModel.Grpc.DesignTime
clientFactory.AddCalculatorClient();// create a new instance of the proxy
var calculator = clientFactory.CreateClient(channel);// call Sum: sum == 6
var sum = await calculator.Sum(1, 2, 3);// call MultiplyBy: multiplier == 2, values == [] {2, 4, 6}
var (multiplier, values) = await calculator.MultiplyBy(new[] {1, 2, 3}, 2);
```### Implement a service
``` c#
internal sealed class Calculator : ICalculator
{
public Task Sum(long x, int y, int z, CancellationToken token) => x + y + z;public ValueTask<(int Multiplier, IAsyncEnumerable Values)> MultiplyBy(IAsyncEnumerable values, int multiplier, CancellationToken token)
{
var multiplicationResult = DoMultiplication(values, multiplier, token);
return new ValueTask<(int, IAsyncEnumerable)>((multiplier, multiplicationResult));
}private static async IAsyncEnumerable DoMultiplication(IAsyncEnumerable values, int multiplier, [EnumeratorCancellation] CancellationToken token)
{
await foreach (var value in values.WithCancellation(token))
{
yield return value * multiplier;
}
}
}
```### Host the service in the asp.net core application
```powershell
PS> Install-Package ServiceModel.Grpc.AspNetCore
`````` c#
var builder = WebApplication.CreateBuilder();// enable ServiceModel.Grpc
builder.Services.AddServiceModelGrpc();var app = builder.Build();
// bind Calculator service
app.MapGrpcService();
```Integrate with Swagger, see [example](Examples/Swagger)
![UI demo](Examples/Swagger/calculator-swagger-ui.png)
### Host the service in Grpc.Core.Server
```powershell
PS> Install-Package ServiceModel.Grpc.SelfHost
`````` c#
var server = new Grpc.Core.Server
{
Ports = { new ServerPort("localhost", 5000, ...) }
};// bind Calculator service
server.Services.AddServiceModelTransient(() => new Calculator());
```### Server filters
see [example](Examples/ServerFilters)
``` c#
var builder = WebApplication.CreateBuilder();// setup filter life time
builder.Services.AddSingleton();// attach the filter globally
builder.Services.AddServiceModelGrpc(options =>
{
options.Filters.Add(1, provider => provider.GetRequiredService());
});internal sealed class LoggingServerFilter : IServerFilter
{
private readonly ILoggerFactory _loggerFactory;public LoggingServerFilter(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}public async ValueTask InvokeAsync(IServerFilterContext context, Func next)
{
// create logger with a service name
var logger = _loggerFactory.CreateLogger(context.ServiceInstance.GetType().Name);// log input
logger.LogInformation("begin {0}", context.ContractMethodInfo.Name);
foreach (var entry in context.Request)
{
logger.LogInformation("input {0} = {1}", entry.Key, entry.Value);
}try
{
// invoke all other filters in the stack and the service method
await next().ConfigureAwait(false);
}
catch (Exception ex)
{
// log exception
logger.LogError("error {0}: {1}", context.ContractMethodInfo.Name, ex);
throw;
}// log output
logger.LogInformation("end {0}", context.ContractMethodInfo.Name);
foreach (var entry in context.Response)
{
logger.LogInformation("output {0} = {1}", entry.Key, entry.Value);
}
}
}
```Name | Package | Description
-----| :------ | :----------
ServiceModel.Grpc | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.svg)](https://www.nuget.org/packages/ServiceModel.Grpc) | main functionality, basic Grpc.Core.Api extensions and ClientFactory. ClientFactory is fully compatible with Grpc.Net.Client.
ServiceModel.Grpc.Client.DependencyInjection | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.Client.DependencyInjection.svg)](https://www.nuget.org/packages/ServiceModel.Grpc.Client.DependencyInjection) | Dependency injection extensions for ClientFactory and Grpc.Net.ClientFactory
ServiceModel.Grpc.AspNetCore | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.AspNetCore.svg)](https://www.nuget.org/packages/ServiceModel.Grpc.AspNetCore) | Grpc.AspNetCore.Server extensions
ServiceModel.Grpc.AspNetCore.Swashbuckle | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.AspNetCore.Swashbuckle.svg)](https://www.nuget.org/packages/ServiceModel.Grpc.AspNetCore.Swashbuckle) | Swagger integration, based on [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)
ServiceModel.Grpc.AspNetCore.NSwag | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.AspNetCore.NSwag.svg)](https://www.nuget.org/packages/ServiceModel.Grpc.AspNetCore.NSwag) | Swagger integration, based on [NSwag](https://github.com/RicoSuter/NSwag)
ServiceModel.Grpc.SelfHost | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.SelfHost.svg)](https://www.nuget.org/packages/ServiceModel.Grpc.SelfHost) | Grpc.Core extensions for self-hosted Grpc.Core.Server
ServiceModel.Grpc.DesignTime | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.DesignTime.svg)](https://www.nuget.org/packages/ServiceModel.Grpc.DesignTime) | C# code generator
ServiceModel.Grpc.MessagePackMarshaller | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.MessagePackMarshaller.svg)](https://www.nuget.org/packages/ServiceModel.Grpc.MessagePackMarshaller) | marshaller factory, based on [MessagePack serializer](https://www.nuget.org/packages/MessagePack)
ServiceModel.Grpc.ProtoBufMarshaller | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.ProtoBufMarshaller.svg)](https://www.nuget.org/packages/ServiceModel.Grpc.ProtoBufMarshaller) | marshaller factory, based on [protobuf-net serializer](https://www.nuget.org/packages/protobuf-net/)
ServiceModel.Grpc.MemoryPackMarshaller | [![Version](https://img.shields.io/nuget/vpre/ServiceModel.Grpc.MemoryPackMarshaller.svg)](https://www.nuget.org/packages/ServiceModel.Grpc.MemoryPackMarshaller) | marshaller factory, based on [MemoryPack serializer](https://www.nuget.org/packages/MemoryPack)## Benchmarks
ServiceModel.Grpc is a tiny layer on top of grpc-dotnet, which helps to adapt code-first to gRPC protocol. A serializer makes a picture of the performance.
Benchmark code is available [here](/Benchmarks).
The following benchmarks show the performance for `unary call` on client and server.
``` c#
[ServiceContract]
public interface ITestService
{
[OperationContract]
Task PingPong(SomeObject value);
}value = new SomeObject
{
StringScalar = "some meaningful text",
Int32Scalar = 1,
DateScalar = DateTime.UtcNow,
SingleScalar = 1.1f,
Int32Array = new int[100],
SingleArray = new float[100],
DoubleArray = new double[100]
};
```- `ServiceModelGrpc.DataContract` test uses DataContractSerializer
- `ServiceModelGrpc.Protobuf` test uses protobuf-net serializer
- `ServiceModelGrpc.MessagePack` test uses MessagePack serializer
- `ServiceModelGrpc.proto-emulation` test uses Google protobuf serialization, the same as `grpc-dotnet`. This test is designed to compare numbers between `ServiceModelGrpc` and `grpc-dotnet` without the influence of a serializer.- `grpc-dotnet` is a baseline:
``` proto
service TestServiceNative {
rpc PingPong (SomeObjectProto) returns (SomeObjectProto);
}message SomeObjectProto {
string stringScalar = 1;
google.protobuf.Timestamp dateScalar = 2;
float singleScalar = 3;
int32 int32Scalar = 4;
repeated float singleArray = 5 [packed=true];
repeated int32 int32Array = 6 [packed=true];
repeated double doubleArray = 7 [packed=true];
}
```### Client async unary call, server is stub
```
BenchmarkDotNet v0.13.10, Ubuntu 22.04.3 LTS (Jammy Jellyfish)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 8.0.100
[Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2
ShortRun : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2Job=ShortRun Platform=X64 Force=True
Server=False IterationCount=15 LaunchCount=1
RunStrategy=Throughput WarmupCount=3```
| Method | Mean | Error | StdDev | Op/s | Ratio | RatioSD | Message size | Gen0 | Allocated | Alloc Ratio |
|--------------------------------- |-----------:|-----------:|-----------:|----------:|------:|--------:|------------- |-------:|----------:|------------:|
| ServiceModelGrpc.DataContract | 131.854 μs | 14.1959 μs | 12.5843 μs | 7,584.2 | 21.45 | 2.07 | 6.55 KB | - | 51.85 KB | 7.76 |
| ServiceModelGrpc.Protobuf | 12.382 μs | 0.0817 μs | 0.0724 μs | 80,760.2 | 2.01 | 0.01 | 1.33 KB | 0.1068 | 9.07 KB | 1.36 |
| ServiceModelGrpc.MessagePack | 7.079 μs | 0.0272 μs | 0.0241 μs | 141,262.9 | 1.15 | 0.01 | 1.52 KB | 0.1221 | 10.06 KB | 1.51 |
| grpc-dotnet | 6.147 μs | 0.0251 μs | 0.0223 μs | 162,690.8 | 1.00 | 0.00 | 1.32 KB | 0.0763 | 6.68 KB | 1.00 |
| ServiceModelGrpc.proto-emulation | 6.383 μs | 0.0444 μs | 0.0394 μs | 156,667.2 | 1.04 | 0.01 | 1.32 KB | 0.0763 | 6.8 KB | 1.02 |### Server async unary call, client is stub
```
BenchmarkDotNet v0.13.10, Ubuntu 22.04.3 LTS (Jammy Jellyfish)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 8.0.100
[Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2
ShortRun : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2Job=ShortRun Platform=X64 Force=True
Server=False IterationCount=15 LaunchCount=1
RunStrategy=Throughput WarmupCount=3```
| Method | Mean | Error | StdDev | Op/s | Ratio | RatioSD | Message size | Allocated | Alloc Ratio |
|--------------------------------- |----------:|----------:|----------:|---------:|------:|--------:|------------- |----------:|------------:|
| ServiceModelGrpc.DataContract | 215.75 μs | 34.677 μs | 30.740 μs | 4,635.0 | 4.44 | 0.62 | 6.55 KB | 60.76 KB | 3.86 |
| ServiceModelGrpc.Protobuf | 65.24 μs | 2.010 μs | 1.569 μs | 15,327.2 | 1.34 | 0.07 | 1.33 KB | 18.11 KB | 1.15 |
| ServiceModelGrpc.MessagePack | 62.09 μs | 17.085 μs | 15.982 μs | 16,105.7 | 1.29 | 0.33 | 1.52 KB | 19.1 KB | 1.21 |
| grpc-dotnet | 48.57 μs | 1.943 μs | 1.723 μs | 20,589.0 | 1.00 | 0.00 | 1.32 KB | 15.76 KB | 1.00 |
| ServiceModelGrpc.proto-emulation | 48.03 μs | 1.581 μs | 1.402 μs | 20,821.3 | 0.99 | 0.05 | 1.32 KB | 15.89 KB | 1.01 |### Client plus server async unary call, without stubs
```
BenchmarkDotNet v0.13.10, Ubuntu 22.04.3 LTS (Jammy Jellyfish)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 8.0.100
[Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2
ShortRun : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2Job=ShortRun Platform=X64 Force=True
Server=False IterationCount=15 LaunchCount=1
RunStrategy=Throughput WarmupCount=3```
| Method | Mean | Error | StdDev | Op/s | Ratio | RatioSD | Message size | Allocated | Alloc Ratio |
|--------------------------------- |----------:|----------:|----------:|---------:|------:|--------:|------------- |----------:|------------:|
| ServiceModelGrpc.DataContract | 382.49 μs | 62.427 μs | 55.340 μs | 2,614.4 | 5.90 | 0.79 | 6.55 KB | 98.45 KB | 5.14 |
| ServiceModelGrpc.Protobuf | 90.63 μs | 2.444 μs | 1.908 μs | 11,033.3 | 1.41 | 0.12 | 1.33 KB | 23.88 KB | 1.25 |
| ServiceModelGrpc.MessagePack | 68.14 μs | 3.780 μs | 2.951 μs | 14,676.7 | 1.06 | 0.10 | 1.52 KB | 25.48 KB | 1.33 |
| grpc-dotnet | 64.90 μs | 5.299 μs | 4.697 μs | 15,407.8 | 1.00 | 0.00 | 1.32 KB | 19.14 KB | 1.00 |
| ServiceModelGrpc.proto-emulation | 63.76 μs | 4.117 μs | 3.214 μs | 15,683.4 | 1.00 | 0.12 | 1.32 KB | 19.38 KB | 1.01 |