在.NET Core中批量注入Grpc服务
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();
启动项目试一试效果:
示例代码:传送门
推荐阅读
-
微服务统计,分析,图表,监控一体化的HttpReports项目在.Net Core 中的使用
-
详解ASP.NET Core 在 JSON 文件中配置依赖注入
-
在ASP.NET Core中实现自动注入、批量注入
-
如何在.NET Core中为gRPC服务设计消息文件(Proto)
-
在ASP.NET Core中创建内部使用Scoped服务的Quartz.NET宿主服务
-
.Net Core在程序的任意位置使用和注入服务的方法
-
在.NET Core中批量注入Grpc服务
-
ASP.NET Core Web 应用程序系列(二)- 在ASP.NET Core中使用Autofac替换自带DI进行批量依赖注入(MVC当中应用)
-
详解ASP.NET Core 在 JSON 文件中配置依赖注入
-
在.net core中实现字段和属性注入的示例代码