在.NET Core中批量注入Grpc服务

  GRPC 是谷歌发布的一个开源、高性能、通用RPC服务,尽管大部分 RPC 框架都使用 TCP 协议,但其实 UDP 也能够,而 gRPC 干脆就用了 HTTP2。还有就是它具备跨平台、跨语言 等特性,这里就再也不说明RPC是啥。git

  在写项目当中,grp服务过多会很是头疼,那么咱们分析一下若是解决这个问题。咱们都知道在grpc注入到.NET Core 中使用的方法是 MapGrpcService 方法,是一个泛型方法。github

    [NullableAttribute(0)]
    [NullableContextAttribute(1)]
    public static class GrpcEndpointRouteBuilderExtensions
    {
        public static GrpcServiceEndpointConventionBuilder MapGrpcService<TService>(this IEndpointRouteBuilder builder) where TService : class;
    }

那咱们就能够经过反射调用这个方法来进行服务批量注册,看方法的样子咱们只须要将咱们的服务对应 TService 以及将咱们的 endpointBuilder 传入便可,咱们看下源码是否是就像我所说的那样?app

    public static class GrpcEndpointRouteBuilderExtensions
    {
        public static GrpcServiceEndpointConventionBuilder MapGrpcService<TService>(this IEndpointRouteBuilder builder) where TService : class
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            ValidateServicesRegistered(builder.ServiceProvider);

            var serviceRouteBuilder = builder.ServiceProvider.GetRequiredService<ServiceRouteBuilder<TService>>();
            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来生成的。dom

public class GreeterService : Greeter.GreeterBase
    {
        public override Task<HelloReply> SayHello(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转入了咱们的终点对象。ide

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);
                List<Type> ts = 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.ui

public class GrpcServiceAttribute : Attribute
{
        public bool IsStart { get; set; }
}

将要在注入的服务商添加该标识,例如这样。this

 [GrpcService]
    public class GreeterService : Greeter.GreeterBase
    {...}    

随后根据反射出来的值找到 AttributeType 的名称进行判断便可。spa

 public static Dictionary<string,string> GetGrpcServices(string assemblyName)
        {
            if (!string.IsNullOrEmpty(assemblyName))
            {
                Assembly assembly = Assembly.Load(assemblyName);
                List<Type> ts = 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();

启动项目试一试效果:

示例代码:传送门

相关文章
相关标签/搜索