GRPC 是谷歌发布的一个开源、高性能、通用RPC服务,尽管大部分 RPC 框架都使用 TCP 协议,但其实 UDP 也可以,而 gRPC 干脆就用了 HTTP2。还有就是它具有跨平台、跨语言 等特性,这里就不再说明RPC是啥。
在写项目当中,grp服务过多会非常头疼,那么我们分析一下如果解决这个问题。我们都知道在grpc注入到.NET Core 中使用的方法是 MapGrpcService 方法,是一个泛型方法。
[NullableAttribute(0)] [NullableContextAttribute(1)] public static class GrpcEndpointRouteBuilderExtensions { public static GrpcServiceEndpointConventionBuilder MapGrpcService(this IEndpointRouteBuilder builder) where TService : class; }
那我们就可以通过反射调用这个方法来进行服务批量注册,看方法的样子我们只需要将我们的服务对应 TService 以及将我们的 endpointBuilder 传入即可,我们看下源码是不是就像我所说的那样?
public static class GrpcEndpointRouteBuilderExtensions { public static GrpcServiceEndpointConventionBuilder MapGrpcService(this IEndpointRouteBuilder builder) where TService : class { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } ValidateServicesRegistered(builder.ServiceProvider); var serviceRouteBuilder = builder.ServiceProvider.GetRequiredService >(); var endpointConventionBuilders = serviceRouteBuilder.Build(builder); return new GrpcServiceEndpointConventionBuilder(endpointConventionBuilders); } private static void ValidateServicesRegistered(IServiceProvider serviceProvider) { var marker = serviceProvider.GetService(typeof(GrpcMarkerService)); if (marker == null) { throw new InvalidOperationException("Unable to find the required services. Please add all the required services by calling " + "'IServiceCollection.AddGrpc' inside the call to 'ConfigureServices(...)' in the application startup code."); } } }
ok,看样子没什么问题就像我刚才所说的那样做。现在我们准备一个proto以及一个Service.这个就在网上找个吧..首先定义一个proto,它是grpc中的协议,也就是每个消费者遵循的。
syntax = "proto3"; option csharp_namespace = "Grpc.Server"; package Greet; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; enum Laguage{ en_us =0 ; zh_cn =1 ; } Laguage LaguageEnum = 2; } message HelloReply { string message = 1; int32 num = 2; int32 adsa =3; }
随后定义Service,当然非常简单, Greeter.GreeterBase 是重新生成项目根据proto来生成的。
public class GreeterService : Greeter.GreeterBase { public override TaskSayHello(HelloRequest request, ServerCallContext context) { var greeting = string.Empty; switch (request.LaguageEnum) { case HelloRequest.Types.Laguage.EnUs: greeting = "Hello"; break; case HelloRequest.Types.Laguage.ZhCn: greeting = "你好"; break; } return Task.FromResult(new HelloReply { Message = $"{greeting} {request.Name}", Num = new Random().Next() }); } }
此时我们需要自定义一个中间件,来批量注入grpc服务,其中我们获取了类型为 GrpcEndpointRouteBuilderExtensions ,并获取了它的方法,随后传入了他的TService,最后通过Invoke转入了我们的终点对象。
public static class GrpcServiceExtension { public static void Add_Grpc_Services(IEndpointRouteBuilder builder) { Assembly assembly = Assembly.GetExecutingAssembly(); foreach (var item in ServicesHelper.GetGrpcServices("Grpc.Server")) { Type mytype = assembly.GetType(item.Value + "."+item.Key); var method = typeof(GrpcEndpointRouteBuilderExtensions).GetMethod("MapGrpcService").MakeGenericMethod(mytype); method.Invoke(null, new[] { builder }); }; } public static void useMyGrpcServices(this IApplicationBuilder app) { app.UseEndpoints(endpoints => { Add_Grpc_Services(endpoints); }); } }
在 ServicesHelper 中通过反射找到程序集当中的所有文件然后判断并返回。
public static class ServicesHelper { public static Dictionary<string,string> GetGrpcServices(string assemblyName) { if (!string.IsNullOrEmpty(assemblyName)) { Assembly assembly = Assembly.Load(assemblyName); Listts = assembly.GetTypes().ToList(); var result = new Dictionary<string, string>(); foreach (var item in ts.Where(u=>u.Namespace == "Grpc.Server.Services")) { result.Add(item.Name,item.Namespace); } return result; } return new Dictionary<string, string>(); } }
这样子我们就注入了所有命名空间为Grpc.Server.Services的服务,但这样好像无法达到某些控制,我们应当如何处理呢,我建议携程Attribute的形式,创建一个Flag.
public class GrpcServiceAttribute : Attribute { public bool IsStart { get; set; } }
将要在注入的服务商添加该标识,例如这样。
[GrpcService] public class GreeterService : Greeter.GreeterBase {...}
随后根据反射出来的值找到 AttributeType 的名称进行判断即可。
public static Dictionary<string,string> GetGrpcServices(string assemblyName) { if (!string.IsNullOrEmpty(assemblyName)) { Assembly assembly = Assembly.Load(assemblyName); Listts = assembly.GetTypes().ToList(); var result = new Dictionary<string, string>(); foreach (var item in ts.Where(u=>u.CustomAttributes.Any(a=>a.AttributeType.Name == "GrpcServiceAttribute"))) { result.Add(item.Name,item.Namespace); } return result; } return new Dictionary<string, string>(); }
随后我们的批量注入在Starup.cs中添加一行代码即可。
app.useMyGrpcServices();
启动项目试一试效果:
示例代码:传送门