Skip to content

Commit 94b0aef

Browse files
committed
Merge branch 'meirkr-meirk/reflection_inherited_and_generics_methods'
2 parents e199bb6 + c59f41e commit 94b0aef

File tree

9 files changed

+525
-41
lines changed

9 files changed

+525
-41
lines changed

src/protobuf-net.Grpc.Reflection/SchemaGenerator.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using ProtoBuf.Meta;
55
using System;
66
using System.Collections.Generic;
7+
using System.Linq;
78
using System.Reflection;
89

910
namespace ProtoBuf.Grpc.Reflection
@@ -78,7 +79,8 @@ public string GetSchema(params Type[] contractTypes)
7879
{
7980
Name = name
8081
};
81-
var ops = contractType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
82+
83+
var ops = GetMethodsRecursively(binder, contractType);
8284
foreach (var method in ops)
8385
{
8486
if (method.DeclaringType == typeof(object))
@@ -134,5 +136,16 @@ static Type ApplySubstitutes(Type type)
134136
return type;
135137
}
136138
}
139+
140+
private static MethodInfo[] GetMethodsRecursively(ServiceBinder serviceBinder, Type contractType)
141+
{
142+
var includingInheritedInterfaces = ContractOperation.ExpandWithInterfacesMarkedAsSubService(serviceBinder, contractType);
143+
144+
var inheritedMethods = includingInheritedInterfaces
145+
.SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Instance))
146+
.ToArray();
147+
148+
return inheritedMethods;
149+
}
137150
}
138151
}

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

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,57 +24,77 @@ are observed and respected
2424
/// <summary>
2525
/// Initiate a bind operation, causing all service methods to be crawled for the provided type
2626
/// </summary>
27-
public int Bind<TService>(object state, BinderConfiguration? binderConfiguration = null, TService? service = null)
27+
public int Bind<TService>(object state, BinderConfiguration? binderConfiguration = null,
28+
TService? service = null)
2829
where TService : class
2930
=> Bind(state, typeof(TService), binderConfiguration, service);
3031

3132
/// <summary>
3233
/// Initiate a bind operation, causing all service methods to be crawled for the provided type
3334
/// </summary>
34-
public int Bind(object state, Type serviceType, BinderConfiguration? binderConfiguration = null, object? service = null)
35+
public int Bind(object state, Type serviceType, BinderConfiguration? binderConfiguration = null,
36+
object? service = null)
3537
{
3638
int totalCount = 0;
3739
object?[]? argsBuffer = null;
3840
Type[] typesBuffer = Array.Empty<Type>();
39-
string? serviceName;
40-
if (binderConfiguration == null) binderConfiguration = BinderConfiguration.Default;
41-
var serviceContracts = typeof(IGrpcService).IsAssignableFrom(serviceType)
42-
? new HashSet<Type> { serviceType }
41+
binderConfiguration ??= BinderConfiguration.Default;
42+
var potentialServiceContracts = typeof(IGrpcService).IsAssignableFrom(serviceType)
43+
? new HashSet<Type> {serviceType}
4344
: ContractOperation.ExpandInterfaces(serviceType);
4445

4546
bool serviceImplSimplifiedExceptions = serviceType.IsDefined(typeof(SimpleRpcExceptionsAttribute));
46-
foreach (var serviceContract in serviceContracts)
47+
foreach (var potentialServiceContract in potentialServiceContracts)
4748
{
48-
if (!binderConfiguration.Binder.IsServiceContract(serviceContract, out serviceName)) continue;
49+
if (!binderConfiguration.Binder.IsServiceContract(potentialServiceContract, out var serviceName)) continue;
50+
51+
// now that we know it is a service contract type for sure
52+
var serviceContract = potentialServiceContract;
53+
54+
var typesToBeIncludedInMethodsBinding =
55+
ContractOperation.ExpandWithInterfacesMarkedAsSubService(binderConfiguration.Binder, serviceContract);
56+
57+
// Per service contract, we will collect all the methods of sub-service interfaces
58+
// and bind them as they were defined in the service contract itself.
59+
// (their binding key will be based on the service contract and not based on the base-interfaces).
4960

50-
var serviceContractSimplifiedExceptions = serviceImplSimplifiedExceptions || serviceContract.IsDefined(typeof(SimpleRpcExceptionsAttribute));
5161
int svcOpCount = 0;
52-
var bindCtx = new ServiceBindContext(serviceContract, serviceType, state, binderConfiguration.Binder);
53-
foreach (var op in ContractOperation.FindOperations(binderConfiguration, serviceContract, this))
62+
foreach (var typeToBindItsMethods in typesToBeIncludedInMethodsBinding)
5463
{
55-
if (ServerInvokerLookup.TryGetValue(op.MethodType, op.Context, op.Arg, op.Result, op.Void, out var invoker)
56-
&& AddMethod(op.From, op.To, op.Name, op.Method, op.MethodType, invoker, bindCtx,
57-
serviceContractSimplifiedExceptions || op.Method.IsDefined(typeof(SimpleRpcExceptionsAttribute))
58-
))
64+
var serviceContractSimplifiedExceptions = serviceImplSimplifiedExceptions ||
65+
typeToBindItsMethods.IsDefined(
66+
typeof(SimpleRpcExceptionsAttribute));
67+
var bindCtx = new ServiceBindContext(serviceContract, serviceType, state, binderConfiguration.Binder);
68+
foreach (var op in ContractOperation.FindOperations(binderConfiguration, typeToBindItsMethods, this))
5969
{
60-
// yay!
61-
totalCount++;
62-
svcOpCount++;
70+
if (ServerInvokerLookup.TryGetValue(op.MethodType, op.Context, op.Arg, op.Result, op.Void, out var invoker)
71+
&& AddMethod(serviceName, op.From, op.To, op.Name, op.Method, op.MethodType, invoker, bindCtx,
72+
serviceContractSimplifiedExceptions || op.Method.IsDefined(typeof(SimpleRpcExceptionsAttribute))
73+
))
74+
{
75+
// yay!
76+
totalCount++;
77+
svcOpCount++;
78+
}
6379
}
6480
}
81+
6582
OnServiceBound(state, serviceName!, serviceType, serviceContract, svcOpCount);
6683
}
84+
6785
return totalCount;
6886

69-
bool AddMethod(Type @in, Type @out, string on, MethodInfo m, MethodType t,
70-
Func<MethodInfo, ParameterExpression[], Expression>? invoker, ServiceBindContext bindContext, bool simplifiedExceptionHandling)
87+
bool AddMethod(string? serviceName, Type @in, Type @out, string on, MethodInfo m, MethodType t,
88+
Func<MethodInfo, ParameterExpression[], Expression>? invoker, ServiceBindContext bindContext,
89+
bool simplifiedExceptionHandling)
7190
{
7291
try
7392
{
7493
if (typesBuffer.Length == 0)
7594
{
76-
typesBuffer = new Type[] { serviceType, typeof(void), typeof(void) };
95+
typesBuffer = new Type[] {serviceType, typeof(void), typeof(void)};
7796
}
97+
7898
typesBuffer[1] = @in;
7999
typesBuffer[2] = @out;
80100

@@ -84,6 +104,7 @@ bool AddMethod(Type @in, Type @out, string on, MethodInfo m, MethodType t,
84104
argsBuffer[6] = binderConfiguration!.MarshallerCache;
85105
argsBuffer[7] = service is null ? null : Expression.Constant(service, serviceType);
86106
}
107+
87108
argsBuffer[0] = serviceName;
88109
argsBuffer[1] = on;
89110
argsBuffer[2] = m;
@@ -93,7 +114,7 @@ bool AddMethod(Type @in, Type @out, string on, MethodInfo m, MethodType t,
93114
// 6, 7 set during array initialization
94115
argsBuffer[8] = simplifiedExceptionHandling;
95116

96-
return (bool)s_addMethod.MakeGenericMethod(typesBuffer).Invoke(this, argsBuffer)!;
117+
return (bool) s_addMethod.MakeGenericMethod(typesBuffer).Invoke(this, argsBuffer)!;
97118
}
98119
catch (Exception fail)
99120
{
@@ -128,7 +149,8 @@ protected readonly struct MethodStub<TService>
128149
/// </summary>
129150
public MethodInfo Method { get; }
130151

131-
internal MethodStub(Func<MethodInfo, Expression[], Expression>? invoker, MethodInfo method, ConstantExpression? service, bool simpleExceptionHandling)
152+
internal MethodStub(Func<MethodInfo, Expression[], Expression>? invoker, MethodInfo method,
153+
ConstantExpression? service, bool simpleExceptionHandling)
132154
{
133155
_simpleExceptionHandling = simpleExceptionHandling;
134156
_invoker = invoker;
@@ -158,7 +180,7 @@ public TDelegate CreateDelegate<TDelegate>()
158180
else
159181
{
160182
// basic - direct call
161-
return (TDelegate)Delegate.CreateDelegate(typeof(TDelegate), _service, Method);
183+
return (TDelegate) Delegate.CreateDelegate(typeof(TDelegate), _service, Method);
162184
}
163185
}
164186
else
@@ -167,7 +189,8 @@ public TDelegate CreateDelegate<TDelegate>()
167189

168190
Expression[] mapArgs;
169191
if (_service is null)
170-
{ // if no service object, then the service is part of the signature, i.e. (svc, req) => svc.Blah();
192+
{
193+
// if no service object, then the service is part of the signature, i.e. (svc, req) => svc.Blah();
171194
mapArgs = lambdaArgs;
172195
}
173196
else
@@ -184,6 +207,7 @@ public TDelegate CreateDelegate<TDelegate>()
184207
{
185208
body = ApplySimpleExceptionHandling(body);
186209
}
210+
187211
var lambda = Expression.Lambda<TDelegate>(body, lambdaArgs);
188212

189213
return lambda.Compile();
@@ -199,17 +223,19 @@ static Expression ApplySimpleExceptionHandling(Expression body)
199223
}
200224
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>))
201225
{
202-
body = Expression.Call(s_ReshapeWithSimpleExceptionHandling[1].MakeGenericMethod(type.GetGenericArguments()), body);
226+
body = Expression.Call(
227+
s_ReshapeWithSimpleExceptionHandling[1].MakeGenericMethod(type.GetGenericArguments()), body);
203228
}
229+
204230
return body;
205231
}
206232
}
207233

208234
#pragma warning disable CS0618
209235
private static readonly Dictionary<int, MethodInfo> s_ReshapeWithSimpleExceptionHandling =
210236
(from method in typeof(Reshape).GetMethods(BindingFlags.Public | BindingFlags.Static)
211-
where method.Name is nameof(Reshape.WithSimpleExceptionHandling)
212-
select method)
237+
where method.Name is nameof(Reshape.WithSimpleExceptionHandling)
238+
select method)
213239
.ToDictionary(method => method.IsGenericMethodDefinition ? method.GetGenericArguments().Length : 0);
214240
#pragma warning restore CS0618
215241

@@ -222,7 +248,8 @@ private bool AddMethod<TService, TRequest, TResponse>(
222248
where TRequest : class
223249
where TResponse : class
224250
{
225-
var grpcMethod = new Method<TRequest, TResponse>(methodType, serviceName, operationName, marshallerCache.GetMarshaller<TRequest>(), marshallerCache.GetMarshaller<TResponse>());
251+
var grpcMethod = new Method<TRequest, TResponse>(methodType, serviceName, operationName,
252+
marshallerCache.GetMarshaller<TRequest>(), marshallerCache.GetMarshaller<TResponse>());
226253
var stub = new MethodStub<TService>(invoker, method, service, simplfiedExceptionHandling);
227254
try
228255
{
@@ -235,13 +262,11 @@ private bool AddMethod<TService, TRequest, TResponse>(
235262
OnError(ex.Message);
236263
return false;
237264
}
238-
239265
}
240266

241267
void IBindContext.LogWarning(string message, object?[]? args) => OnWarn(message, args);
242268
void IBindContext.LogError(string message, object?[]? args) => OnError(message, args);
243269

244-
/// <summary>
245270
/// Describes the relationship between a service contract and a service definition
246271
/// </summary>
247272
protected internal sealed class ServiceBindContext
@@ -283,4 +308,4 @@ public IList<object> GetMetadata(MethodInfo method)
283308
=> ServiceBinder.GetMetadata(method, ContractType, ServiceType);
284309
}
285310
}
286-
}
311+
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,5 +222,28 @@ public virtual IList<object> GetMetadata(MethodInfo method, Type contractType, T
222222
}
223223
return null;
224224
}
225+
226+
/// <summary>
227+
/// Attempt to resolve a sub-service
228+
/// </summary>
229+
/// <param name="type"></param>
230+
/// <param name="typesToSearchIn"></param>
231+
/// <param name="serviceName"></param>
232+
/// <returns></returns>
233+
internal bool TryFindInheritedService(Type type, IEnumerable<Type> typesToSearchIn, out string? serviceName)
234+
{
235+
foreach (var potentialServiceContract in typesToSearchIn)
236+
{
237+
if (potentialServiceContract != type
238+
&& type.IsAssignableFrom(potentialServiceContract)
239+
&& IsServiceContract(potentialServiceContract, out serviceName))
240+
{
241+
return true;
242+
}
243+
}
244+
245+
serviceName = null;
246+
return false;
247+
}
225248
}
226249
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.ComponentModel;
3+
4+
namespace ProtoBuf.Grpc.Configuration
5+
{
6+
/// <summary>
7+
/// Indicates that this interface can be inherited by a gRPC service.
8+
/// All methods of this interface will be routed based on the top-level service name.
9+
/// </summary>
10+
/// <remarks>This is particularly useful for genenric service APIs.</remarks>
11+
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
12+
[ImmutableObject(true)]
13+
public sealed class SubServiceAttribute : Attribute
14+
{
15+
}
16+
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,47 @@ internal static ISet<Type> ExpandInterfaces(Type type)
403403
if (type.IsInterface) set.Add(type);
404404
return set;
405405
}
406+
407+
/// <summary>
408+
/// Collect all the types to be used for extracting methods for a specific Service Contract
409+
/// </summary>
410+
/// <param name="serviceBinder"></param>
411+
/// <param name="serviceContract">Must be a service contract</param>
412+
/// <returns>types to be used for extracting methods</returns>
413+
internal static ISet<Type> ExpandWithInterfacesMarkedAsSubService(ServiceBinder serviceBinder,
414+
Type serviceContract)
415+
{
416+
var set = new HashSet<Type>();
417+
418+
// first add the service contract by itself
419+
set.Add(serviceContract);
420+
421+
// now add all inherited interfaces which are marked as sub-services
422+
foreach (var t in serviceContract.GetInterfaces())
423+
{
424+
if (t.IsDefined(typeof(SubServiceAttribute)))
425+
{
426+
set.Add(t);
427+
}
428+
}
429+
430+
ValidateServiceContracts(serviceBinder, set);
431+
return set;
432+
}
433+
434+
private static void ValidateServiceContracts(ServiceBinder serviceBinder, HashSet<Type> set)
435+
{
436+
foreach (var item in set)
437+
{
438+
if (item.IsDefined(typeof(SubServiceAttribute)))
439+
{
440+
if (serviceBinder.IsServiceContract(item, out var serviceName))
441+
throw new ArgumentException(
442+
$"Bad definition for service {serviceName}: " +
443+
$"A service contract cannot be marked as a sub-service as well");
444+
}
445+
}
446+
}
406447
}
407448

408449
internal enum ContextKind

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

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

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

@@ -203,11 +205,26 @@ FieldBuilder Marshaller(Type forType)
203205
type.DefineMethodOverride(impl, iMethod);
204206

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

212229
Type[] fromTo = new Type[] { op.From, op.To };
213230
// private static Method<from, to> s_{i}

0 commit comments

Comments
 (0)