欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-09 21:54:43
GRPC 是谷歌发布的一个开源、高性能、通用RPC服务,尽管大部分 RPC 框架都使用 TCP 协议,但其实 UDP 也可以,而 gRPC 干脆就用了 HTTP2。还有就是它具有跨平台、跨语言 等特性,这里就不再说明RPC是啥。 在写项目当中,grp服务过多会非常头疼,那么我们分析一下如果解决这个问 ......

  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<tservice>(this iendpointroutebuilder builder) where tservice : class;
    }

那我们就可以通过反射调用这个方法来进行服务批量注册,看方法的样子我们只需要将我们的服务对应 tservice 以及将我们的 endpointbuilder 传入即可,我们看下源码是不是就像我所说的那样?

    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来生成的。

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转入了我们的终点对象。

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.

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);
                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();

启动项目试一试效果:

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

示例代码:传送门