Skip to content

Commit 688a754

Browse files
author
Meir Kriheli
committed
Client proxy emitter now also considering the Inherited interfaces, so they methods will be included based on main service's name (which inherits those interfaces.
1 parent 388ecb9 commit 688a754

File tree

3 files changed

+192
-2
lines changed

3 files changed

+192
-2
lines changed

src/protobuf-net.Grpc/Configuration/ServiceBinder.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,5 +220,28 @@ public virtual IList<object> GetMetadata(MethodInfo method, Type contractType, T
220220
}
221221
return null;
222222
}
223+
224+
/// <summary>
225+
///
226+
/// </summary>
227+
/// <param name="type"></param>
228+
/// <param name="typesToSearchIn"></param>
229+
/// <param name="serviceName"></param>
230+
/// <returns></returns>
231+
public bool TryFindInheritedService(Type type, IEnumerable<Type> typesToSearchIn, out string? serviceName)
232+
{
233+
foreach (var potentialServiceContract in typesToSearchIn)
234+
{
235+
if (potentialServiceContract != type
236+
&& type.IsAssignableFrom(potentialServiceContract)
237+
&& IsServiceContract(potentialServiceContract, out serviceName))
238+
{
239+
return true;
240+
}
241+
}
242+
243+
serviceName = null;
244+
return false;
245+
}
223246
}
224247
}

src/protobuf-net.Grpc/Internal/ProxyEmitter.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ FieldBuilder Marshaller(Type forType)
184184
}
185185

186186
int fieldIndex = 0;
187-
foreach (var iType in ContractOperation.ExpandInterfaces(typeof(TService)))
187+
var contractExpandInterfaces = ContractOperation.ExpandInterfaces(typeof(TService))
188+
.ToArray();
189+
foreach (var iType in contractExpandInterfaces)
188190
{
189191
bool isService = binderConfig.Binder.IsServiceContract(iType, out var serviceName);
190192

@@ -202,11 +204,26 @@ FieldBuilder Marshaller(Type forType)
202204
type.DefineMethodOverride(impl, iMethod);
203205

204206
var il = impl.GetILGenerator();
205-
if (!(isService && ContractOperation.TryIdentifySignature(iMethod, binderConfig, out var op, null)))
207+
208+
// check whether the method belongs to [ServiceInherited] interface
209+
var isMethodInherited =
210+
iMethod.DeclaringType?.IsDefined(typeof(ServiceInheritableAttribute)) ?? false;
211+
var shallMethodBeImplemented = isService || isMethodInherited;
212+
if (!(shallMethodBeImplemented && ContractOperation.TryIdentifySignature(iMethod, binderConfig, out var op, null)))
206213
{
207214
il.ThrowException(typeof(NotSupportedException));
208215
continue;
209216
}
217+
218+
// in case method belongs to an inherited interface, we have to find the service contract name inheriting it
219+
if (isMethodInherited)
220+
{
221+
if (!binderConfig.Binder.TryFindInheritedService(iType, contractExpandInterfaces, out serviceName))
222+
{
223+
il.ThrowException(typeof(NotSupportedException));
224+
continue;
225+
}
226+
}
210227

211228
Type[] fromTo = new Type[] { op.From, op.To };
212229
// private static Method<from, to> s_{i}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
using Grpc.Core;
2+
using ProtoBuf;
3+
using ProtoBuf.Grpc.Client;
4+
using ProtoBuf.Grpc.Configuration;
5+
using ProtoBuf.Grpc.Server;
6+
using ProtoBuf.Meta;
7+
using System;
8+
using System.Buffers;
9+
using System.Collections.Generic;
10+
using System.IO;
11+
using System.Runtime.InteropServices;
12+
using System.Runtime.Serialization;
13+
using System.Threading.Tasks;
14+
using ProtoBuf.Grpc;
15+
using Xunit;
16+
using Xunit.Abstractions;
17+
18+
#nullable disable
19+
20+
namespace protobuf_net.Grpc.Test.Integration
21+
{
22+
public class ClientProxyTests : IClassFixture<ClientProxyTests.ClientProxyTestsServerFixture>
23+
{
24+
25+
[DataContract]
26+
public class MyRequest
27+
{
28+
[DataMember(Order = 1)]
29+
public int Id { get; set; }
30+
}
31+
32+
[DataContract]
33+
public class MyResponse
34+
{
35+
[DataMember(Order = 1)]
36+
public string? Value { get; set; }
37+
}
38+
39+
40+
/// <summary>
41+
/// An interface which is not marked with [Service] attribute.
42+
/// Its methods' proxy-implementations are expected to throw unsupported exception.
43+
/// </summary>
44+
public interface INotAService
45+
{
46+
ValueTask<MyResponse> NotAServiceUnary(MyRequest request, CallContext callContext = default);
47+
}
48+
49+
[ServiceInheritable]
50+
public interface ISomeInheritableBaseGenericService<in TGenericRequest, TGenericResult>
51+
{
52+
ValueTask<TGenericResult> BaseGenericUnary(TGenericRequest request, CallContext callContext = default);
53+
}
54+
55+
[Service]
56+
public interface IMyDerivedService : ISomeInheritableBaseGenericService<MyRequest, MyResponse>, INotAService
57+
{
58+
ValueTask<MyResponse> Derived(MyRequest request, CallContext callContext = default);
59+
}
60+
61+
#pragma warning disable IDE0079 // Remove unnecessary suppression
62+
#pragma warning disable IDE0051, IDE0052 // "unused" things; they are, but it depends on the TFM
63+
private readonly ITestOutputHelper _log;
64+
private readonly ClientProxyTestsServerFixture _server;
65+
private void Log(string message) => _log?.WriteLine(message);
66+
#pragma warning restore IDE0051, IDE0052
67+
#pragma warning restore IDE0079 // Remove unnecessary suppression
68+
69+
private int Port => _server.Port;
70+
71+
public ClientProxyTests(ClientProxyTestsServerFixture server, ITestOutputHelper log)
72+
{
73+
_server = server;
74+
_log = log;
75+
GrpcClientFactory.AllowUnencryptedHttp2 = true;
76+
}
77+
78+
public class ClientProxyTestsServerFixture : IMyDerivedService, IAsyncDisposable
79+
{
80+
public int Port { get; } = PortManager.GetNextPort();
81+
82+
public async ValueTask DisposeAsync()
83+
{
84+
if (_server != null)
85+
await _server.KillAsync();
86+
}
87+
88+
private readonly Server? _server;
89+
public ClientProxyTestsServerFixture()
90+
{
91+
_server = new Server
92+
{
93+
Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) }
94+
};
95+
_server.Services.AddCodeFirst(this);
96+
_server.Start();
97+
}
98+
99+
public async ValueTask<MyResponse> NotAServiceUnary(MyRequest request, CallContext callContext = default)
100+
{
101+
// we expect this won't be called through GRPC
102+
return await Task.FromResult(new MyResponse());
103+
}
104+
105+
public async ValueTask<MyResponse> Derived(MyRequest request, CallContext callContext = default)
106+
{
107+
return await Task.FromResult(new MyResponse());
108+
}
109+
110+
public async ValueTask<MyResponse> BaseGenericUnary(MyRequest request, CallContext callContext = default)
111+
{
112+
return await Task.FromResult(new MyResponse());
113+
}
114+
}
115+
116+
117+
#if !(NET461 || NET472)
118+
[Fact]
119+
public async Task ClientProxyTests_WhenCalledToDerivedInterfaceMethod_NoException()
120+
{
121+
using var http = global::Grpc.Net.Client.GrpcChannel.ForAddress($"http://localhost:{Port}");
122+
var client = http.CreateGrpcService<IMyDerivedService>();
123+
var obj = await client.Derived(new MyRequest());
124+
}
125+
126+
[Fact]
127+
public async Task ClientProxyTests_WhenCalledToBaseInheritableMethod_NoException()
128+
{
129+
using var http = global::Grpc.Net.Client.GrpcChannel.ForAddress($"http://localhost:{Port}");
130+
var client = http.CreateGrpcService<IMyDerivedService>();
131+
var obj = await client.BaseGenericUnary(new MyRequest());
132+
}
133+
134+
135+
[Fact]
136+
public async Task ClientProxyTests_WhenCalledToBaseNonInheritableMethod_ThrowsUnsupportedException()
137+
{
138+
using var http = global::Grpc.Net.Client.GrpcChannel.ForAddress($"http://localhost:{Port}");
139+
var client = http.CreateGrpcService<IMyDerivedService>();
140+
141+
await Assert.ThrowsAsync<NotSupportedException>(async () =>
142+
{
143+
// we expect an exception from the client proxy
144+
var obj = await client.NotAServiceUnary(new MyRequest());
145+
});
146+
}
147+
#endif
148+
}
149+
150+
}

0 commit comments

Comments
 (0)