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

启动项目试一试效果:

示例代码:传送门