Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework gRPC method binding logic #864

Merged
merged 29 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading