Skip to content

Commit

Permalink
Merge pull request #864 from Cysharp/feature/ReworkBinder
Browse files Browse the repository at this point in the history
Rework gRPC method binding logic
  • Loading branch information
mayuki authored Oct 24, 2024
2 parents 4102683 + d089d66 commit 267055c
Show file tree
Hide file tree
Showing 60 changed files with 3,208 additions and 1,599 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public class ApplicationInformation
public string? BenchmarkerVersion { get; } = typeof(ApplicationInformation).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;

#if SERVER
public string? TagMagicOnionVersion { get; } = RemoveHashFromVersion(typeof(MagicOnion.Server.MagicOnionEngine).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion);
public string? MagicOnionVersion { get; } = typeof(MagicOnion.Server.MagicOnionEngine).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
public string? TagMagicOnionVersion { get; } = RemoveHashFromVersion(typeof(MagicOnion.Server.ServiceContext).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion);
public string? MagicOnionVersion { get; } = typeof(MagicOnion.Server.ServiceContext).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
public string? GrpcNetVersion { get; } = typeof(Grpc.AspNetCore.Server.GrpcServiceOptions).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
#elif CLIENT
public string? TagMagicOnionVersion { get; } = RemoveHashFromVersion(typeof(MagicOnion.Client.MagicOnionClient).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion);
Expand Down
1 change: 0 additions & 1 deletion samples/ChatApp/ChatApp.Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
endpointOptions.Protocols = HttpProtocols.Http2;
});
});
builder.Services.AddGrpc(); // MagicOnion depends on ASP.NET Core gRPC service.
builder.Services.AddMagicOnion();

var app = builder.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ public static T FromRaw<TRaw, T>(TRaw obj)

public static Method<Box<Nil>, TRawResponse> CreateMethod<TResponse, TRawResponse>(MethodType methodType, string serviceName, string name, IMagicOnionSerializer messageSerializer)
where TRawResponse : class
{
return CreateMethod<TResponse, TRawResponse>(methodType, serviceName, name, null, messageSerializer);
}
public static Method<Box<Nil>, TRawResponse> CreateMethod<TResponse, TRawResponse>(MethodType methodType, string serviceName, string name, MethodInfo? methodInfo, IMagicOnionSerializer messageSerializer)
where TRawResponse : class
{
// WORKAROUND: Prior to MagicOnion 5.0, the request type for the parameter-less method was byte[].
// DynamicClient sends byte[], but GeneratedClient sends Nil, which is incompatible,
Expand All @@ -49,12 +44,6 @@ public static Method<Box<Nil>, TRawResponse> CreateMethod<TResponse, TRawRespons
public static Method<TRawRequest, TRawResponse> CreateMethod<TRequest, TResponse, TRawRequest, TRawResponse>(MethodType methodType, string serviceName, string name, IMagicOnionSerializer messageSerializer)
where TRawRequest : class
where TRawResponse : class
{
return CreateMethod<TRequest, TResponse, TRawRequest, TRawResponse>(methodType, serviceName, name, null, messageSerializer);
}
public static Method<TRawRequest, TRawResponse> CreateMethod<TRequest, TResponse, TRawRequest, TRawResponse>(MethodType methodType, string serviceName, string name, MethodInfo? methodInfo, IMagicOnionSerializer messageSerializer)
where TRawRequest : class
where TRawResponse : class
{
var isMethodRequestTypeBoxed = typeof(TRequest).IsValueType;
var isMethodResponseTypeBoxed = typeof(TResponse).IsValueType;
Expand Down
11 changes: 0 additions & 11 deletions src/MagicOnion.Internal/GrpcMethodHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ public static T FromRaw<TRaw, T>(TRaw obj)

public static Method<Box<Nil>, TRawResponse> CreateMethod<TResponse, TRawResponse>(MethodType methodType, string serviceName, string name, IMagicOnionSerializer messageSerializer)
where TRawResponse : class
{
return CreateMethod<TResponse, TRawResponse>(methodType, serviceName, name, null, messageSerializer);
}
public static Method<Box<Nil>, TRawResponse> CreateMethod<TResponse, TRawResponse>(MethodType methodType, string serviceName, string name, MethodInfo? methodInfo, IMagicOnionSerializer messageSerializer)
where TRawResponse : class
{
// WORKAROUND: Prior to MagicOnion 5.0, the request type for the parameter-less method was byte[].
// DynamicClient sends byte[], but GeneratedClient sends Nil, which is incompatible,
Expand All @@ -49,12 +44,6 @@ public static Method<Box<Nil>, TRawResponse> CreateMethod<TResponse, TRawRespons
public static Method<TRawRequest, TRawResponse> CreateMethod<TRequest, TResponse, TRawRequest, TRawResponse>(MethodType methodType, string serviceName, string name, IMagicOnionSerializer messageSerializer)
where TRawRequest : class
where TRawResponse : class
{
return CreateMethod<TRequest, TResponse, TRawRequest, TRawResponse>(methodType, serviceName, name, null, messageSerializer);
}
public static Method<TRawRequest, TRawResponse> CreateMethod<TRequest, TResponse, TRawRequest, TRawResponse>(MethodType methodType, string serviceName, string name, MethodInfo? methodInfo, IMagicOnionSerializer messageSerializer)
where TRawRequest : class
where TRawResponse : class
{
var isMethodRequestTypeBoxed = typeof(TRequest).IsValueType;
var isMethodResponseTypeBoxed = typeof(TResponse).IsValueType;
Expand Down
19 changes: 19 additions & 0 deletions src/MagicOnion.Server/Binder/IMagicOnionGrpcMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Grpc.Core;
using MagicOnion.Server.Internal;

namespace MagicOnion.Server.Binder;

public interface IMagicOnionGrpcMethod
{
MethodType MethodType { get; }
Type ServiceImplementationType { get; }
string ServiceName { get; }
string MethodName { get; }
MethodHandlerMetadata Metadata { get; }
}

public interface IMagicOnionGrpcMethod<TService> : IMagicOnionGrpcMethod
where TService : class
{
void Bind(IMagicOnionGrpcMethodBinder<TService> binder);
}
19 changes: 19 additions & 0 deletions src/MagicOnion.Server/Binder/IMagicOnionGrpcMethodBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace MagicOnion.Server.Binder;

public interface IMagicOnionGrpcMethodBinder<TService>
where TService : class
{
void BindUnary<TRequest, TResponse, TRawRequest, TRawResponse>(IMagicOnionUnaryMethod<TService, TRequest, TResponse, TRawRequest, TRawResponse> method)
where TRawRequest : class
where TRawResponse : class;
void BindClientStreaming<TRequest, TResponse, TRawRequest, TRawResponse>(MagicOnionClientStreamingMethod<TService, TRequest, TResponse, TRawRequest, TRawResponse> method)
where TRawRequest : class
where TRawResponse : class;
void BindServerStreaming<TRequest, TResponse, TRawRequest, TRawResponse>(MagicOnionServerStreamingMethod<TService, TRequest, TResponse, TRawRequest, TRawResponse> method)
where TRawRequest : class
where TRawResponse : class;
void BindDuplexStreaming<TRequest, TResponse, TRawRequest, TRawResponse>(MagicOnionDuplexStreamingMethod<TService, TRequest, TResponse, TRawRequest, TRawResponse> method)
where TRawRequest : class
where TRawResponse : class;
void BindStreamingHub(MagicOnionStreamingHubConnectMethod<TService> method);
}
64 changes: 64 additions & 0 deletions src/MagicOnion.Server/Binder/IMagicOnionGrpcMethodProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;

namespace MagicOnion.Server.Binder;

public interface IMagicOnionGrpcMethodProvider
{
void MapAllSupportedServiceTypes(MagicOnionGrpcServiceMappingContext context);
IReadOnlyList<IMagicOnionGrpcMethod> GetGrpcMethods<TService>() where TService : class;
IReadOnlyList<IMagicOnionStreamingHubMethod> GetStreamingHubMethods<TService>() where TService : class;
}

public class MagicOnionGrpcServiceMappingContext(IEndpointRouteBuilder builder) : IEndpointConventionBuilder
{
readonly List<MagicOnionServiceEndpointConventionBuilder> innerBuilders = new();

public void Map<T>()
where T : class, IServiceMarker
{
innerBuilders.Add(new MagicOnionServiceEndpointConventionBuilder(builder.MapGrpcService<T>()));
}

public void Map(Type t)
{
VerifyServiceType(t);

innerBuilders.Add(new MagicOnionServiceEndpointConventionBuilder((GrpcServiceEndpointConventionBuilder)typeof(GrpcEndpointRouteBuilderExtensions)
.GetMethod(nameof(GrpcEndpointRouteBuilderExtensions.MapGrpcService))!
.MakeGenericMethod(t)
.Invoke(null, [builder])!));
}

static void VerifyServiceType(Type type)
{
if (!typeof(IServiceMarker).IsAssignableFrom(type))
{
throw new InvalidOperationException($"Type '{type.FullName}' is not marked as MagicOnion service or hub.");
}
if (!type.GetInterfaces().Any(x => x.IsGenericType && (x.GetGenericTypeDefinition() == typeof(IService<>) || x.GetGenericTypeDefinition() == typeof(IStreamingHub<,>))))
{
throw new InvalidOperationException($"Type '{type.FullName}' has no implementation for Service or StreamingHub");
}
if (type.IsAbstract)
{
throw new InvalidOperationException($"Type '{type.FullName}' is abstract. A service type must be non-abstract class.");
}
if (type.IsInterface)
{
throw new InvalidOperationException($"Type '{type.FullName}' is interface. A service type must be class.");
}
if (type.IsGenericType && type.IsGenericTypeDefinition)
{
throw new InvalidOperationException($"Type '{type.FullName}' is generic type definition. A service type must be plain or constructed-generic class.");
}
}

void IEndpointConventionBuilder.Add(Action<EndpointBuilder> convention)
{
foreach (var innerBuilder in innerBuilders)
{
innerBuilder.Add(convention);
}
}
}
Loading

0 comments on commit 267055c

Please sign in to comment.