Skip to content

Commit 3052d9f

Browse files
committed
add example of custom ad-hoc bindings, client and server
1 parent 5148278 commit 3052d9f

File tree

3 files changed

+182
-26
lines changed

3 files changed

+182
-26
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using ProtoBuf.Grpc.Client;
2+
using ProtoBuf.Grpc.Configuration;
3+
using System;
4+
using System.Collections.Concurrent;
5+
using System.Reflection;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Xunit;
9+
10+
namespace protobuf_net.Grpc.Test.Issues
11+
{
12+
public class Issue224
13+
{
14+
[Fact]
15+
public async Task CustomBinderExample()
16+
{
17+
var binder = new MyBinder();
18+
binder.Add(typeof(IMyService), "Foo");
19+
binder.Add(typeof(IMyService).GetMethod(nameof(IMyService.SomeMethodAsync)), "Bar");
20+
21+
// server
22+
var serverBinder = new TestServerBinder();
23+
Assert.Equal(1, serverBinder.Bind<IMyService>(null!, binderConfiguration: binder.BinderConfiguration));
24+
Assert.Equal("/Foo/Bar", Assert.Single(serverBinder.Methods));
25+
26+
// client
27+
var channel = new TestChannel("abc");
28+
IMyService service = channel.CreateGrpcService<IMyService>(binder.ClientFactory);
29+
await service.SomeMethodAsync();
30+
Assert.Equal("abc:/Foo/Bar", Assert.Single(channel.Calls));
31+
}
32+
public interface IMyService
33+
{
34+
ValueTask SomeMethodAsync(CancellationToken cancellation = default);
35+
}
36+
sealed class MyBinder : ServiceBinder
37+
{
38+
public MyBinder()
39+
{
40+
BinderConfiguration = BinderConfiguration.Create(binder: this);
41+
ClientFactory = ClientFactory.Create(BinderConfiguration);
42+
}
43+
44+
readonly ConcurrentDictionary<Type, string> _services = new();
45+
readonly ConcurrentDictionary<MethodInfo, string> _operations = new();
46+
47+
public override bool IsServiceContract(Type contractType, out string? name)
48+
=> _services.TryGetValue(contractType, out name);
49+
public override bool IsOperationContract(MethodInfo method, out string? name)
50+
=> _operations.TryGetValue(method, out name);
51+
52+
public bool Add(Type service, string name)
53+
=> _services.TryAdd(service, name);
54+
public bool Add(MethodInfo operation, string name)
55+
=> _operations.TryAdd(operation, name);
56+
57+
public BinderConfiguration BinderConfiguration { get; }
58+
public ClientFactory ClientFactory { get; }
59+
}
60+
}
61+
}

tests/protobuf-net.Grpc.Test/Issues/Issue87.cs

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,19 @@
1-
using Grpc.Core;
2-
using ProtoBuf.Grpc.Configuration;
1+
using ProtoBuf.Grpc.Configuration;
32
using System;
4-
using System.Collections.Generic;
53
using Xunit;
4+
using Xunit.Abstractions;
65

76
namespace protobuf_net.Grpc.Test.Issues
87
{
9-
class TestServerBinder : ServerBinder // just tracks what methods are observed
10-
{
11-
public HashSet<string> Methods { get; } = new HashSet<string>();
12-
public List<string> Warnings { get; } = new List<string>();
13-
public List<string> Errors { get; } = new List<string>();
14-
protected override bool TryBind<TService, TRequest, TResponse>(ServiceBindContext bindContext, Method<TRequest, TResponse> method, MethodStub<TService> stub)
15-
{
16-
Methods.Add(method.Name);
17-
return true;
18-
}
19-
protected internal override void OnWarn(string message, object?[]? args = null)
20-
=> Warnings.Add(string.Format(message, args ?? Array.Empty<object>()));
21-
protected internal override void OnError(string message, object?[]? args = null)
22-
=> Errors.Add(string.Format(message, args ?? Array.Empty<object>()));
23-
}
8+
249

2510
public class Issue87
2611
{
12+
public Issue87(ITestOutputHelper log)
13+
=> _log = log;
14+
private readonly ITestOutputHelper _log;
15+
private void Log(string message) => _log?.WriteLine(message);
16+
2717
[Theory]
2818
[InlineData(typeof(MyService), null)]
2919
[InlineData(typeof(MyServiceBase), null)]
@@ -50,21 +40,28 @@ public void IsService(Type type, string name)
5040
}
5141

5242
[Theory]
53-
[InlineData(typeof(Foo), new[] { nameof(Foo.PublicDerived), nameof(FooBase.PublicBase), nameof(FooBase.PublicPolymorphic) })]
54-
[InlineData(typeof(FooBase), new string[] { })]
55-
[InlineData(typeof(Bar), new[] { nameof(Bar.PublicDerived), nameof(BarBase.PublicBase), nameof(BarBase.PublicPolymorphic) })]
56-
[InlineData(typeof(BarBase), new[] { nameof(BarBase.PublicBase), nameof(BarBase.PublicPolymorphic) })]
57-
[InlineData(typeof(MyServiceBase), new[] { nameof(IBaseService.BaseServiceMethodExplicit), nameof(IBaseService.BaseServiceMethodImplicit) })]
58-
[InlineData(typeof(MyService), new[] { nameof(IBaseService.BaseServiceMethodExplicit), nameof(IBaseService.BaseServiceMethodImplicit), nameof(IDerivedService.DerivedServiceMethodExplicit), nameof(IDerivedService.DerivedServiceMethodImplicit) })]
59-
public void CanSeeCorrectMethods(Type type, string[] methods)
43+
[InlineData(typeof(Foo), "/Foo/", new[] { nameof(Foo.PublicDerived), nameof(FooBase.PublicBase), nameof(FooBase.PublicPolymorphic) })]
44+
[InlineData(typeof(FooBase), "/FooBase/", new string[] { })]
45+
[InlineData(typeof(Bar), "/Bar/", new[] { nameof(Bar.PublicDerived), nameof(BarBase.PublicBase), nameof(BarBase.PublicPolymorphic) })]
46+
[InlineData(typeof(BarBase), "/BarBase/", new[] { nameof(BarBase.PublicBase), nameof(BarBase.PublicPolymorphic) })]
47+
[InlineData(typeof(MyServiceBase), "/protobuf_net.Grpc.Test.Issues.BaseService/", new[] { nameof(IBaseService.BaseServiceMethodExplicit), nameof(IBaseService.BaseServiceMethodImplicit) })]
48+
[InlineData(typeof(MyService), "/protobuf_net.Grpc.Test.Issues.", new[] {
49+
"BaseService/" + nameof(IBaseService.BaseServiceMethodExplicit), "BaseService/" + nameof(IBaseService.BaseServiceMethodImplicit),
50+
"DerivedService/" + nameof(IDerivedService.DerivedServiceMethodExplicit), "DerivedService/" + nameof(IDerivedService.DerivedServiceMethodImplicit)
51+
})]
52+
public void CanSeeCorrectMethods(Type type, string prefix, string[] methods)
6053
{
6154
var binder = new TestServerBinder();
6255
int count = binder.Bind(this, type);
56+
foreach (var bound in binder.Methods)
57+
{
58+
Log(bound);
59+
}
6360
Assert.Equal(methods.Length, count);
6461
Assert.Equal(methods.Length, binder.Methods.Count);
6562
foreach (var method in methods)
6663
{
67-
Assert.Contains(method, binder.Methods);
64+
Assert.Contains(prefix + method, binder.Methods);
6865
}
6966

7067
Assert.Empty(binder.Warnings);
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Grpc.Core;
2+
using ProtoBuf.Grpc.Configuration;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace protobuf_net.Grpc.Test
9+
{
10+
class TestServerBinder : ServerBinder // just tracks what methods are observed
11+
{
12+
public HashSet<string> Methods { get; } = new HashSet<string>();
13+
public List<string> Warnings { get; } = new List<string>();
14+
public List<string> Errors { get; } = new List<string>();
15+
protected override bool TryBind<TService, TRequest, TResponse>(ServiceBindContext bindContext, Method<TRequest, TResponse> method, MethodStub<TService> stub)
16+
{
17+
Methods.Add(method.FullName);
18+
return true;
19+
}
20+
protected internal override void OnWarn(string message, object?[]? args = null)
21+
=> Warnings.Add(string.Format(message, args ?? Array.Empty<object>()));
22+
protected internal override void OnError(string message, object?[]? args = null)
23+
=> Errors.Add(string.Format(message, args ?? Array.Empty<object>()));
24+
}
25+
26+
class TestChannel : ChannelBase
27+
{
28+
public TestChannel(string target) : base(target) { }
29+
public override CallInvoker CreateCallInvoker()
30+
=> new TestInvoker(this);
31+
32+
public HashSet<string> Calls { get; } = new();
33+
34+
private void Call(IMethod method)
35+
=> Calls.Add(Target + ":" + method.FullName);
36+
37+
class TestInvoker : CallInvoker
38+
{
39+
public TestChannel Channel { get; }
40+
public TestInvoker(TestChannel channel)
41+
=> Channel = channel;
42+
43+
public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
44+
{
45+
Channel.Call(method);
46+
return default!;
47+
}
48+
49+
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
50+
{
51+
Channel.Call(method);
52+
return new AsyncUnaryCall<TResponse>(Task.FromResult<TResponse>(default!),
53+
Task.FromResult(Metadata.Empty), () => Status.DefaultSuccess, () => Metadata.Empty, () => { });
54+
}
55+
56+
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
57+
{
58+
Channel.Call(method);
59+
return new AsyncServerStreamingCall<TResponse>(NulStream<TResponse>.Default,
60+
Task.FromResult(Metadata.Empty), () => Status.DefaultSuccess, () => Metadata.Empty, () => { });
61+
}
62+
63+
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
64+
{
65+
Channel.Call(method);
66+
return new AsyncClientStreamingCall<TRequest, TResponse>(NulStream<TRequest>.Default, Task.FromResult<TResponse>(default!),
67+
Task.FromResult(Metadata.Empty), () => Status.DefaultSuccess, () => Metadata.Empty, () => { });
68+
}
69+
70+
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
71+
{
72+
Channel.Call(method);
73+
return new AsyncDuplexStreamingCall<TRequest, TResponse>(NulStream<TRequest>.Default, NulStream<TResponse>.Default,
74+
Task.FromResult(Metadata.Empty), () => Status.DefaultSuccess, () => Metadata.Empty, () => { });
75+
}
76+
77+
private sealed class NulStream<T> : IAsyncStreamReader<T>, IClientStreamWriter<T>
78+
{
79+
public static NulStream<T> Default { get; } = new();
80+
81+
T IAsyncStreamReader<T>.Current => default!;
82+
83+
WriteOptions IAsyncStreamWriter<T>.WriteOptions { get; set; } = WriteOptions.Default;
84+
85+
private NulStream() { }
86+
87+
Task<bool> IAsyncStreamReader<T>.MoveNext(CancellationToken cancellationToken)
88+
=> Task.FromResult(false);
89+
90+
Task IAsyncStreamWriter<T>.WriteAsync(T message)
91+
=> Task.CompletedTask;
92+
93+
Task IClientStreamWriter<T>.CompleteAsync()
94+
=> Task.CompletedTask;
95+
}
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)