ServiceModel.Grpc
Code-first for gRPC
Install / Use
/learn @max-ieremenko/ServiceModel.GrpcREADME
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# and grpc-dotnet.
Links
- ServiceModel.Grpc at a glance
- NuGet feed
- Benchmarks
- docs
- service and operation names
- service and operation bindings
- client configuration
- client code generation
- client dependency injection
- server code generation
- operations
- ASP.NET Core server configuration
- Grpc.Core server configuration
- exception handling general information
- global exception handling
- client filters
- server filters
- compatibility with native gRPC
- migrate from WCF to a gRPC
- migrate from WCF FaultContract to a gRPC global error handling
- examples
ServiceModel.Grpc at a glance <a name="intro"></a>
Declare a service contract
[ServiceContract]
public interface ICalculator
{
[OperationContract]
Task<long> Sum(long x, int y, int z, CancellationToken token = default);
[OperationContract]
ValueTask<(int Multiplier, IAsyncEnumerable<int> Values)> MultiplyBy(IAsyncEnumerable<int> values, int multiplier, CancellationToken token = default);
}
Client call (Reflection.Emit)
A proxy for the ICalculator service will be generated on demand via Reflection.Emit.
PS> Install-Package ServiceModel.Grpc
// 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<ICalculator>(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.
PS> Install-Package ServiceModel.Grpc.DesignTime
// 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<ServiceModelGrpcClientOptions> 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<ICalculator>(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
internal sealed class Calculator : ICalculator
{
public Task<long> Sum(long x, int y, int z, CancellationToken token) => x + y + z;
public ValueTask<(int Multiplier, IAsyncEnumerable<int> Values)> MultiplyBy(IAsyncEnumerable<int> values, int multiplier, CancellationToken token)
{
var multiplicationResult = DoMultiplication(values, multiplier, token);
return new ValueTask<(int, IAsyncEnumerable<int>)>((multiplier, multiplicationResult));
}
private static async IAsyncEnumerable<int> DoMultiplication(IAsyncEnumerable<int> 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
PS> Install-Package ServiceModel.Grpc.AspNetCore
var builder = WebApplication.CreateBuilder();
// enable ServiceModel.Grpc
builder.Services.AddServiceModelGrpc();
var app = builder.Build();
// bind Calculator service
app.MapGrpcService<Calculator>();
Integrate with Swagger, see example

Host the service in Grpc.Core.Server
PS> Install-Package ServiceModel.Grpc.SelfHost
var server = new Grpc.Core.Server
{
Ports = { new ServerPort("localhost", 5000, ...) }
};
// bind Calculator service
server.Services.AddServiceModelTransient(() => new Calculator());
Server filters
see example
var builder = WebApplication.CreateBuilder();
// setup filter life time
builder.Services.AddSingleton<LoggingServerFilter>();
// attach the filter globally
builder.Services.AddServiceModelGrpc(options =>
{
options.Filters.Add(1, provider => provider.GetRequiredService<LoggingServerFilter>());
});
internal sealed class LoggingServerFilter : IServerFilter
{
private readonly ILoggerFactory _loggerFactory;
public LoggingServerFilter(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public async ValueTask InvokeAsync(IServerFilterContext context, Func<ValueTask> 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);
}
}
}
NuGet feed <a name="nuget"></a>
Name | Package | Description
-----| :------ | :----------
ServiceModel.Grpc | | main functionality, basic Grpc.Core.Api extensions and ClientFactory. ClientFactory is fully compatible with Grpc.Net.Client.
ServiceModel.Grpc.Client.DependencyInjection |
| Dependency injection extensions for ClientFactory and Grpc.Net.ClientFactory
ServiceModel.Grpc.AspNetCore |
| Grpc.AspNetCore.Server extensions
ServiceModel.Grpc.AspNetCore.Swashbuckle |
| Swagger integration, based on Swashbuckle.AspNetCore
ServiceModel.Grpc.AspNetCore.NSwag |
| Swagger integration, based on NSwag
ServiceModel.Grpc.SelfHost | [.
commit-push-pr
83.9kCommit, push, and open a PR
