{"id":23898891,"url":"https://github.com/newshadowk/netrpc","last_synced_at":"2025-04-13T06:37:33.097Z","repository":{"id":46768426,"uuid":"186356233","full_name":"newshadowk/NetRpc","owner":"newshadowk","description":"NetRpc is a light weight rpc engine base on RabbitMQ, Grpc, Http targeting .NET 5.0/6.0/7.0/8.0. It use the simple interface to call each other, provide callback/cancel during invoking, so especially suitable for handle long running call.","archived":false,"fork":false,"pushed_at":"2025-03-14T05:12:18.000Z","size":19141,"stargazers_count":35,"open_issues_count":4,"forks_count":9,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-27T09:35:02.612Z","etag":null,"topics":["csharp","dotnet","grpc","rabbitmq","rpc","rpc-framework"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/newshadowk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-13T06:11:17.000Z","updated_at":"2025-03-14T05:12:21.000Z","dependencies_parsed_at":"2024-11-01T10:24:11.860Z","dependency_job_id":"21bbf106-4678-4f29-9832-56165c918658","html_url":"https://github.com/newshadowk/NetRpc","commit_stats":{"total_commits":508,"total_committers":10,"mean_commits":50.8,"dds":"0.12992125984251968","last_synced_commit":"741eb019dc8bea8cadc4d9fb5452d9514902a536"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/newshadowk%2FNetRpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/newshadowk%2FNetRpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/newshadowk%2FNetRpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/newshadowk%2FNetRpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/newshadowk","download_url":"https://codeload.github.com/newshadowk/NetRpc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248675352,"owners_count":21143763,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","grpc","rabbitmq","rpc","rpc-framework"],"created_at":"2025-01-04T18:14:14.530Z","updated_at":"2025-04-13T06:37:33.069Z","avatar_url":"https://github.com/newshadowk.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NetRpc\n[![NuGet Badge](https://buildstats.info/nuget/NetRpc)](https://www.nuget.org/packages/NetRpc/)\n[![GitHub license](https://img.shields.io/badge/license-Mit-blue)](https://github.com/newshadowk/NetRpc/blob/master/LICENSE.md)\n\nNetRpc is a light weight rpc engine base on **RabbitMQ**, **Grpc**, **Http** targeting .NET 5.0/6.0/7.0/8.0.  It use the simple interface to call each other, \nprovide callback/cancel during invoking, so especially suitable for handle **long running call**.\n\n## NuGet\n\nNetRpc for **Grpc** channel can be installed in your project with the following command.\n```\nPM\u003e Install-Package NetRpc.Grpc\n```\n\n\nNetRpc also supports **Http**, **RabbitMQ** channel following packages are available to install:\n```\nPM\u003e Install-Package NetRpc.Http\nPM\u003e Install-Package NetRpc.RabbitMQ\n```\n\nOthers install:\n\n```\n//Jeager tracing function.\nPM\u003e Install-Package NetRpc.Jeager\n\n//Http channel for client only.\nPM\u003e Install-Package NetRpc.Http.Client\n```\n\n|Name|||\n|-|-|-|\n|[NetRpc.Grpc](https://www.nuget.org/packages/NetRpc.Grpc/) |[![NetRpc.Grpc](https://img.shields.io/nuget/v/NetRpc.Grpc)](https://www.nuget.org/packages/NetRpc.Grpc/)|[![NetRpc.Grpc](https://img.shields.io/nuget/dt/NetRpc.Grpc)](https://www.nuget.org/packages/NetRpc.Grpc/)|\n|[NetRpc.Http](https://www.nuget.org/packages/NetRpc.Http/) |[![NetRpc.Http](https://img.shields.io/nuget/v/NetRpc.Http)](https://www.nuget.org/packages/NetRpc.Http/)|[![NetRpc.Http](https://img.shields.io/nuget/dt/NetRpc.Http)](https://www.nuget.org/packages/NetRpc.Http/)|\n|[NetRpc.Http.Client](https://www.nuget.org/packages/NetRpc.Http.Client/) |[![NetRpc.Http.Client](https://img.shields.io/nuget/v/NetRpc.Http.Client)](https://www.nuget.org/packages/NetRpc.Http.Client/)|[![NetRpc.Http.Client](https://img.shields.io/nuget/dt/NetRpc.Http.Client)](https://www.nuget.org/packages/NetRpc.Http.Client/)|\n|[NetRpc.RabbitMQ](https://www.nuget.org/packages/NetRpc.RabbitMQ/) |[![NetRpc.RabbitMQ](https://img.shields.io/nuget/v/NetRpc.RabbitMQ)](https://www.nuget.org/packages/NetRpc.RabbitMQ/)|[![NetRpc.RabbitMQ](https://img.shields.io/nuget/dt/NetRpc.RabbitMQ)](https://www.nuget.org/packages/NetRpc.RabbitMQ/)|\n|[NetRpc.Jaeger](https://www.nuget.org/packages/NetRpc.Jaeger/) |[![NetRpc.Jaeger](https://img.shields.io/nuget/v/NetRpc.Jaeger)](https://www.nuget.org/packages/NetRpc.Jaeger/)|[![NetRpc.Jaeger](https://img.shields.io/nuget/dt/NetRpc.Jaeger)](https://www.nuget.org/packages/NetRpc.Jaeger/)|\n\n## Hello world\n\nNetRpc for **Grpc** channel can be installed in your project with the following command.\n```\nPM\u003e Install-Package NetRpc.Grpc\n```\n\n```c#\n//service side\nclass Program\n{\n    static async Task Main(string[] args)\n    {\n        var host = Host.CreateDefaultBuilder()\n            .ConfigureWebHostDefaults(webBuilder =\u003e\n            {\n                webBuilder.ConfigureKestrel((_, options) =\u003e\n                    {\n                        options.ListenAnyIP(50001, listenOptions =\u003e listenOptions.Protocols = HttpProtocols.Http2);\n                    })\n                    .ConfigureServices((_, services) =\u003e\n                    {\n                        services.AddNGrpcService();\n                        services.AddNServiceContract\u003cIServiceAsync, ServiceAsync\u003e();\n                    }).Configure(app =\u003e { app.UseNGrpc(); });\n            }).Build();\n\n        await host.RunAsync();\n    }\n}\n\npublic class ServiceAsync : IServiceAsync\n{\n    public async Task\u003cstring\u003e CallAsync(string s)\n    {\n        Console.WriteLine($\"Receive: {s}\");\n        return \"call ret\";\n    }\n}\n```\n```c#\n//client side\nclass Program\n{\n    static async Task Main(string[] args)\n    {\n        //register\n        var services = new ServiceCollection();\n        services.AddNGrpcClient(options =\u003e options.Url = \"http://localhost:50001\");\n        services.AddNClientContract\u003cIServiceAsync\u003e();\n        services.AddLogging();\n        var buildServiceProvider = services.BuildServiceProvider();\n\n        //get service\n        var service = buildServiceProvider.GetService\u003cIServiceAsync\u003e();\n        Console.WriteLine(\"call: hello world.\");\n        var ret = await service.CallAsync(\"hello world.\");\n        Console.WriteLine($\"ret: {ret}\");\n    }\n}\n```\n```c#\n//datacontract is referenced by client and service\npublic interface IServiceAsync\n{\n    Task\u003cstring\u003e CallAsync(string s);\n}\n```\nCode here: [samples/HelloWorld](samples/HelloWorld).\n\nQuick start: [QuickRef](QuickRef.md) \n\n## Overall\nNetRpc provide **RabbitMQ**/**Grpc**/**Http** Channels to connect, each one has different advantages.\n* **RabbitMQ** provide load balance, queue feature.\n* **Grpc** use http2, provide all http2 advantages.\n* **Http** use webapi, also provide swagger api.\n\nAll channels use uniform contract, so easily to switch channel without modify service implementation.\n\n![](images/all1.png)\n\n## RabbitMQ/Grpc\nThere is message channel for RabbitMQ and Grpc, Http pls see topic blow.\n![](images/nrpc.png)\n\n## Initialize by DI\nThere has two ways to initialize service and client, See DI sample below:\n```c#\n//service side\nvar host = Host.CreateDefaultBuilder()\n    .ConfigureWebHostDefaults(webBuilder =\u003e\n    {\n        webBuilder.ConfigureKestrel((context, options) =\u003e\n            {\n                options.ListenAnyIP(50001, listenOptions =\u003e listenOptions.Protocols = HttpProtocols.Http2);\n            })\n            .ConfigureServices((context, services) =\u003e\n            {\n                services.AddNGrpcService();\n                services.AddNServiceContract\u003cIServiceAsync, ServiceAsync\u003e();\n            }).Configure(app =\u003e\n            {\n                app.UseNGrpc();\n            });\n    }).Build();\n```\n```c#\n//client side\n//register\nServiceCollection services = new ServiceCollection();\nservices.AddNGrpcClient(options =\u003e options.Url = \"http://localhost:50001\");\nservices.AddNClientContract\u003cIServiceAsync\u003e();\nservices.AddLogging();\nvar buildServiceProvider = services.BuildServiceProvider();\n\n//get service\nvar service = buildServiceProvider.GetService\u003cIServiceAsync\u003e();\nvar clientProxy = buildServiceProvider.GetService\u003cIClientProxy\u003cIServiceAsync\u003e\u003e();\n\n//call remote\nawait service.CallAsync(\"hello world.\");\nawait clientProxy.Proxy.CallAsync(\"hello world.\");\n```\nIf want to inject multiple **ClientProxies**, should use **IClientProxyFactory**.\n```c#\n//service side\nservices.Configure\u003cRabbitMQClientOptions\u003e(\"mq1\", context.Configuration.GetSection(\"Mq1\"));\nservices.Configure\u003cRabbitMQClientOptions\u003e(\"mq2\", context.Configuration.GetSection(\"Mq2\"));\nservices.AddNRabbitMQClient();\nservices.AddNRpcClientContract\u003cIService\u003e();\n```\n\n```c#\n//client side\npublic class MyHost : IHostedService\n{\n    private readonly IClientProxyFactory _factory;\n\n    public MyHost(IClientProxyFactory factory)  //Get IClientProxyFactory here\n    {\n        _factory = factory;\n    }\n\n    public async Task StartAsync(CancellationToken cancellationToken)\n    {\n        var clientProxy = _factory.CreateProxy\u003cIService\u003e(\"grpc1\");\n...\n```\n## Serialization\nRabbitMQ, Grpc channel base on **BinaryFormatter**, make sure all contract model mark as **[Serializable]**.  \n```c#\n[Serializable]\npublic class CustomObj\n{\n    //...\n}\n```\n**[Important]** When returned Custom object contains a **Stream**:  \nRabbitMQ, Grpc channel **Stream** mask as **[field: NonSerialized]**.  \nHttp channel **Stream** mask as **[JsonIgnore]**.  \n```c#\nTask\u003cComplexStream\u003e GetComplexStreamAsync();\n\n[Serializable]\npublic class ComplexStream\n{\n    [field: NonSerialized]       //add for RabbitMQ, Grpc channel\n    [JsonIgnore]                 //add for http channel\n    public Stream Stream { get; set; }\n\n    public string OtherInfo { get; set; }\n}\n```\n## Supported contract type\n```c#\n//Async\npublic interface IServiceAsync\n{\n    Task\u003cCustomObj\u003e SetAndGetObj(CustomObj obj);\n\n    /// \u003cexception cref=\"TaskCanceledException\"\u003e\u003c/exception\u003e\n    Task CallByCancelAsync(CancellationToken token);\n\n    Task CallByCallBackAsync(Func\u003cCustomCallbackObj, Task\u003e cb);\n\n    /// \u003cexception cref=\"NotImplementedException\"\u003e\u003c/exception\u003e\n    Task CallBySystemExceptionAsync();\n\n    /// \u003cexception cref=\"CustomException\"\u003e\u003c/exception\u003e\u003e\n    Task CallByCustomExceptionAsync();\n\n    Task\u003cStream\u003e GetStreamAsync();\n\n    Task SetStreamAsync(Stream data);\n\n    Task\u003cStream\u003e EchoStreamAsync(Stream data);\n\n    Task\u003cComplexStream\u003e GetComplexStreamAsync();\n\n    /// \u003cexception cref=\"TaskCanceledException\"\u003e\u003c/exception\u003e\n    Task\u003cComplexStream\u003e ComplexCallAsync(CustomObj obj, Stream data, Func\u003cCustomCallbackObj, Task\u003e cb, CancellationToken token);\n}\n```\n## GenericType\nMake sure the genericType in contract is mark as **[Serializable]**.\n```c#\nTask\u003cT2\u003e CallByGenericTypeAsync\u003cT1, T2\u003e(T1 obj);\n```\n## Header\nHeader is a type of **Dictionary\u003cstring, object\u003e** object, mark sure your object mark as **[Serializable]**.  \nBefore call action, client set the **Header** which mark as **AsyncLocal** that guarantee muti-threads don`t influence each other.\n```c#\n//client side\n_proxy.AdditionHeader.Add(\"k1\", \"header value\");\n_proxy.TestHeader();\n```\nService can receive the header object which client sent.\n```c#\n//service side\npublic void TestHeader()\n{\n    var h = GlobalActionExecutingContext.Context.Header;\n    //...\n}\n```\n* **AdditionHeader**  \nOn the client side, when **AdditionHeader** is not null and items count is greater than 0, **Header** will get the value of **AdditionHeader** when call the remote. This feature is usefull when you want to transfer a sessionId to service.\n```c#\n//client side\n//set the AdditionHeader with SessionId\nclient.Context.AdditionHeader = new Dictionary\u003cstring, object\u003e {{\"SessionId\", 1}};\n//will tranfer the header of SessionId to service.\nclient.Proxy.Call();\n```\n\n## Context\nOn service side, **Midderware** or **Filter** can access **ActionExecutingContext**, it is\n\n| Property           | Type | Description |\n| :-----             | :--- | :---------- |\n| Header             | Dictionary\\\u003cstring object\u003e  | Header sent from client. |\n| Target             | object                      | Service instance of invoked action.|\n| ChannelType        | ChannelType                 | Enum value: Undefined, Grpc, RabbitMQ, Http.|\n| InstanceMethodInfo | MethodInfo                  | Current invoked method.  |\n| ContractMethodInfo | MethodInfo                  | Current invoked contract method.  |\n| ActionInfo         | ActionInfo                  | Warpped info of current invoked method.  |\n| Args               | object[]                    | Args of invoked action.  |\n| PureArgs           | object[]                    | Args of invoked action without stream and action.  |\n| Callback           | Func\\\u003cobject, Task\u003e         | Callback of invoked action.  |\n| Token              | CancellationToken           | CancellationToken of invoked action.  |\n| Stream             | Stream                      | Stream of invoked action.  |\n| ServiceProvider    | IServiceProvider            | ServiceProvider of invoked action.  |\n| Contract           | Contract                    | Contract.|\n| MethodObj          | MethodObj                   | MethodObj.|\n| Result             | object                      | Result of invoked action.|\n| Properties         | Dictionary\\\u003cobject, object\u003e | A central location for sharing state between components during the invoking process.  |\n\nOn client side, **Midderware** can access **ClientActionExecutingContext**, it is\n\n| Property         | Type | Description |\n| :-----           | :--- | :---------- |\n| ServiceProvider  | IServiceProvider            | ServiceProvider of invoked action.  |\n| Result           | object                      | Result of invoked action.|\n| Header           | Dictionary\\\u003cstring object\u003e  | Header sent from client. |\n| OptionsName      | string                      | Config options name by DI. |\n| MethodInfo       | MethodInfo                  | Current invoked method.  |\n| Callback         | Func\\\u003cobject, Task\u003e         | Callback of invoked action.  |\n| Token            | CancellationToken           | CancellationToken of invoked action.  |\n| ContractInfo     | ContractInfo                | ContractInfo.|\n| MethodObj        | MethodObj                   | MethodObj.|\n| Stream           | Stream                      | Stream of invoked action.  |\n| PureArgs         | object[]                    | Args of invoked action without stream and action.  |\n| Properties       | Dictionary\\\u003cobject, object\u003e | A central location for sharing state between components during the invoking process.  |\n\n## Filter\nFilter is common function like MVC. \n```c#\n//service side\npublic class TestFilter : ActionFilterAttribute\n{\n    public override void OnActionExecuting(ActionExecutingContext context)\n    {\n        Console.Write($\"TestFilter.Execute(), context:{context}\");\n    }\n}\n\ninternal class Service : IService\n{\n    [TestFilter]\n    public void Test()\n    {\n        //...\n    }\n}\n```\n## NetRpc Middleware\nThe way use **NetRpc Middleware** and use **MVC Middleware** is same, the only difference is use **RpcContext** instead of **HttpContext**.  \nSupport DI Type and ctor args.\n```c#\n//servcie\nservices.AddNRpcMiddleware(i =\u003e i.UseMiddleware\u003cTestGlobalExceptionMiddleware\u003e(\"arg1value\"));\n\npublic class TestGlobalExceptionMiddleware\n{\n    private readonly RequestDelegate _next;\n\n    public TestGlobalExceptionMiddleware(RequestDelegate next, string arg1, DIType diType)\n    {\n        _next = next;\n        Console.WriteLine($\"{arg1}\");\n    }\n\n    public async Task InvokeAsync(RpcContext context, DIType diType)\n    {\n        try\n        {\n            await _next(context);\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine($\"[log by Middleware] {e.GetType().Name}\");\n            throw;\n        }\n    }\n}\n```\nClient side also support Middleware.\n```c#\npublic class ClientOpenTracingMiddleware\n{\n    private readonly ClientRequestDelegate _next;\n\n    public ClientOpenTracingMiddleware(ClientRequestDelegate next)\n    {\n        _next = next;\n    }\n\n    public async Task InvokeAsync(ClientContext context, ITracer tracer)\n    {\n...\n```\n## Retry\n```c#\n[ClientRetry(1000, 2000, 3000)]  // retry in 1000ms, 2000ms, 3000ms\npublic interface IServiceAsync\n{\n    //[ClientRetry(1000, 2000, 3000)]   or set here.\n    Task CallAsync(string s);\n}\n...\n```\n## Load Balance\nOnly for RabbitMQ.  \nWhen run multiple service instances, ther service will auto apply the load balance, this function is base on the RabbitMQ.  \n**MQOptions.PrefetchCount**: The service will acquire more messages, up to the PrefetchCount limit, defalut value is 1.\n## FaultException\\\u003cT\u003e\n**FaultException** is\n\n| Property | Type | Description |\n| :-----   | :--- | :---------- |\n| Detail   | Exception | Threw exception, will save the orginal **StackTrace** when Exception via remote transfer. |\n| Action   | string | Invoked action name and args, if many actions split by '\\|'.\n\n```c#\n//service side\ninternal class ServiceAsync : IServiceAsync\n{\n    public Task CallBySystemExceptionAsync()\n    {\n        throw new NotImplementedException();\n    }\n}\n```\n```c#\n//client side\ntry\n{\n    await proxy.CallBySystemExceptionAsync();\n}\ncatch (FaultException\u003cNotImplementedException\u003e e)\n{\n    //e.Detail.StackTrace will get the orginal info. (http channel do not support)\n}\ncatch (FaultException e2)\n{\n\n}\ncatch (OperationCanceledException e2)\n{\n\n}\ncatch (TaskCanceledException e2)\n{\n\n}\n\n```\n## Cancel\n```c#\n/// \u003cexception cref=\"TaskCanceledException\"\u003e\u003c/exception\u003e\nTask CallByCancelAsync(CancellationToken token);\n```\n## Call back\n```c#\nTask CallByCallBackAsync(Func\u003cCustomCallbackObj, Task\u003e cb);\n```\n\nBuilt-in **CallbackThrottlingMiddleware** is useful when callback is progress, normally progress do not need callback every time to client, also for saving network resources.\n```c#\n//service side\nservices.AddNRpcMiddleware(i =\u003e i.UseCallbackThrottling(1000)); //limit to one call per second\n//or this:\nservices.AddNRpcCallbackThrottling(1000);\n...\npublic async Task Call(Func\u003cint, Task\u003e cb)\n{\n    for (int i = 0; i \u003c= 20; i++)\n    {\n        await Task.Delay(100);\n        await cb(i);\n        Console.WriteLine($\"Send callback: {i}\");\n    }\n}\n```\n```c#\n//client side\nawait proxy.Call(i =\u003e Console.WriteLine($\"receive callback: {i}\"));\n```\n```c\n//service sent 20 callbacks by interval 100ms.\nSend callback: 0\nSend callback: 1\nSend callback: 2\nSend callback: 3\nSend callback: 4\nSend callback: 5\nSend callback: 6\nSend callback: 7\nSend callback: 8\nSend callback: 9\nSend callback: 10\nSend callback: 11\nSend callback: 12\nSend callback: 13\nSend callback: 14\nSend callback: 15\nSend callback: 16\nSend callback: 17\nSend callback: 18\nSend callback: 19\nSend callback: 20      //at the end will force send last callback\n\n//----------------------------------------------------\n\n//client only received 4 callbacks by interval 1000ms\nreceive callback: 0\nreceive callback: 8\nreceive callback: 17\nreceive callback: 20   //will receive last callback.\n```\n## Stream\n```c#\nTask\u003cStream\u003e GetStreamAsync();\n\nTask SetStreamAsync(Stream data);\n\nTask\u003cStream\u003e EchoStreamAsync(Stream data);\n\nTask\u003cComplexStream\u003e GetComplexStreamAsync();\n[Serializable]\npublic class ComplexStream\n{\n    [field: NonSerialized]\n    public Stream Stream { get; set; }\n\n    public string OtherInfo { get; set; }\n}\n```\n## MQPostAttribute\nOnly for RabbitMQ channel, means post way to call, after sent message to rabbitMQ then return immediately, consumer will consum messages in queue asynchronous.  \nPost method define has some limits, no callback Action, no cancelToken, no return value.\n```c#\n[MQPost]\nTask PostAsync(string s1, Stream stream);\n```\n## IgnoreAttribute\n* GrpcIgnoreAttribute\n* RabbitMQIgnoreAttribute\n* HttpIgnoreAttribute\n\n```c#\n[GrpcIgnore]\nTask CallAsync(call);\n```\n## Client Event\n**ClientProxy** has events:  \n* **ExceptionInvoked** it usefull when you want to log the exception when call.  \n* **Heartbeat** see topic below.  \n* **Connected** invoked when conntect the service.  \n* **DisConnected** invoked when disconntect the service. if Heartbeat faild will invoke too.\n```c#\nclientProxy.Connected += (s, e) =\u003e Console.WriteLine(\"[event] Connected\");\nclientProxy.DisConnected += (s, e) =\u003e Console.WriteLine(\"[event] DisConnected\");\nclientProxy.ExceptionInvoked += (s, e) =\u003e Console.WriteLine(\"[event] ExceptionInvoked\");\nclientProxy.Heartbeat += async s =\u003e s.Proxy.Hearbeat();\n```\n## Hearbeat\n**ClientProxy** has a **Heartbeat** function after you call **StartHeartbeat()**, the interval is 10 seconds by default.  \nClient should register the **Heartbeat** event and implementation of heartbeat.  \nAccording to **Heartbeat** is successfull or faild, **Connected** or **DisConnected** will invoke correspondingly.\n```c#\n//client set the heartbeat interval to 10*1000\nclientProxy.Heartbeat += async s =\u003e s.Proxy.Hearbeat();\nclientProxy.StartHeartbeat(true);\n```\n## Options support realtime update\nNormally four options below support realtime update, if options has changed, will reset underlying service use the new options, do not need restart process to take options effect.\n* GrpcServiceOptions\n* GrpcClientOptions\n* RabbitMQServiceOptions\n* RabbitMQClientOptions\n\nNote: only for DI mode:\n```c#\nservices.AddNGrpcService(i =\u003e i.AddPort(\"0.0.0.0\", 50001));\n```\n\n## Gateway\n\nGateway has many advantages:\n* Convert Channel.\n* Provide exception handle.\n* Provide access authority.\n* ...\n\n![](images/all2.png)\n\nThe code blow show how to Receive message from RabbitMQ channel client and send to Grpc channel service.\n```c#\n //set single target by DI.\nservices.AddNRabbitMQService(i =\u003e i.Value = TestHelper.Helper.GetMQOptions());\nservices.AddNRpcGateway\u003cIService\u003e(o =\u003e o.Channel = new Channel(\"localhost\", 50001, ChannelCredentials.Insecure));\nservices.AddNRpcGateway\u003cIService2\u003e();\n```\nAlso privode middleware in the gateway service, can add access authority if needed.\n\n## OpenTracing\nSet the tags relate to method, it contains:\n* Name\n* Params\n* Result\n* Exception\n\nFor more details pls go to [samples/OpenTracing](samples/OpenTracing)\n![](images/tracer.png)\n\n## Get HttpContext\nUse IHttpContextAccessor by services.AddHttpContextAccessor();\n\n# [Http] NetRpc.Http\nNetRpc.Http provide:\n* **Webapi** for call api.\n* **Swagger** for display and test api.\n* **SignalR** for callback and cancel during method invoking.\n\nNote:\n* **Swagger** is not necessary.\n* **Mvc** is not necessary.\n\n![](images/nrpc_http.png)\n\n## [Http] Create Host\nUse DI to create NHttp service, also could create NHttp service base on exist MVC servcie.\n```c#\n//regist services\nservices.AddSignalR();         // add SignalR service\nservices.AddNRpcSwagger();   // add Swgger service\nservices.AddNHttp(i =\u003e    // add RpcHttp service\n{\n    i.ApiRootPath = \"/api\";\n    i.IgnoreWhenNotMatched = false;\n});\nservices.AddNRpcMiddleware(i =\u003e i.UseMiddleware\u003cMyNRpcMiddleware\u003e());  // define NetRpc Middleware\nservices.AddNRpcServiceContract(instanceTypes); // add Contracts\n```\n```c#\n//use components\napp.UseSignalR(routes =\u003e { routes.MapHub\u003cCallbackHub\u003e(hubPath); });   // define CallbackHub\napp.UseNRpcSwagger();   // use NRpcSwagger middleware\napp.UseNHttp();      // use NHttp middleware\n```\n## [Http] Swagger\nUse [Swashbuckle.AspNetCore.Swagger](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) to implement swagger feature.  \nNote: swagger need add Http channel, swagger api path is **[HttpServiceOptions.ApiRootPath]/swagger**, if apiRootPath is \"api\", should like http://localhost:5000/api/swagger, if apiRootPath is null, should like http://localhost:5000/swagger.  \nAdd codes below to enabled swagger function.\n```c#\nservices.AddNRpcSwagger();   // add Swgger service\n...\napp.UseNRpcSwagger();        // use NRpcSwagger middleware\n```\nThe demo show how to call a method with callback and cancel:\n![](images/swagger.png)\n\nIf define Callback Func\\\u003cT, Task\u003e and CancelToken supported, need set **\\_connId** and **_callId** when request.\nOperationCanceledException will receive respones with statuscode 600.  \n\n![](images/callback.png)\n\nAlso support summary on model or method.\n## HttpServiceOptions\n```c#\n/// \u003csummary\u003e\n/// Api root path, like '/api', default value is null.\n/// \u003c/summary\u003e\npublic string? ApiRootPath { get; set; }\n\n/// \u003csummary\u003e\n/// Set true will pass to next middleware when not match the method, default value is false.\n/// \u003c/summary\u003e\npublic bool IgnoreWhenNotMatched { get; set; }\n\n/// \u003csummary\u003e\n/// ShortConn redis connection string.\n/// \u003c/summary\u003e\npublic string? ShortConnRedisConnStr { get; set; }\n\n/// \u003csummary\u003e\n/// ShortConn task stream file temp dir.\n/// \u003c/summary\u003e\npublic string? ShortConnTempDir { get; set; }\n```\n## [Http] Callback and Cancel\nContract define the **Func\\\u003cT, Task\u003e** and **CancellationToken** to enable this feature.\n```c#\nTask CallAsync(Func\u003cint, Task\u003e cb, CancellationToken token);\n```\nClient code belows show how to get connectionId, how to receive callback, how to cancel a method.\n```javascript\n//client js side\nvar connection = new signalR.HubConnectionBuilder().withUrl(\"{hubUrl}\").build();\n\n//GetConnId function\nconnection.start().then(function () {\n    addText(\"signalR connected!\");\n    connection.invoke(\"GetConnId\").then((cid) =\u003e {\n        addText(\"GetConnId, _connId:\" + cid);\n    });\n}).catch(function (err) {\n    return console.error(err.toString());\n});\n\n//Callback\nconnection.on(\"Callback\", function (callId, data) {\n    addText(\"callback, callId:\" + callId + \", data:\" + data);\n});\n\n//Cancel\ndocument.getElementById(\"cancelBtn\").addEventListener(\"click\", function (event) {\n    connection.invoke(\"Cancel\").catch(function (err) {\n        return console.error(err.toString());\n    });\n\n    event.preventDefault();\n});\n```\n## [Http] Short Connection\nConvert long running call to short call with **start** **prog** **cancel** **replace** interface.  \n**ref:** [samples/ShortConn](samples/ShortConn)\n![](images/shortconn.png)\n\n## [Http] FaultExceptionAttribute\nIf contract has **Exception** defined, should use **FaultExceptionAttribute** to define **statuscode**, \nuse **response code** to define summary(will display in Swagger), \notherwise NetRpc will use statuscode **400** to define all Exception by default.\n```c#\n[FaultException(typeof(CustomException), 400, 1, \"errorCode1 error description\")]\n[FaultException(typeof(CustomException2), 400, 2, \"errorCode2 error description\")]\nTask CallByCustomExceptionAsync();\n```\n```c#\n//Also can put here, will effect to all methods in IServiceAsync.\n[FaultException(typeof(CustomException2), 400, 2, \"errorCode2 error description\")]\npublic interface IServiceAsync\n```\n## [Http] Method Attribute\nSupport some Attributes, pls see demo.\n```c#\n[HttpTrimAsync]\n[HttpRoute(\"IRout1\")]\n[Tag(\"RoutTag1\")]\npublic interface IService2Async\n{\n    [Tag(\"CallTag1\")]\n    [HttpPost]\n    [HttpRoute(\"Call1/{p1}\")]\n    [HttpGet(\"/Root/Call/{p1}\")]\n    [HttpTrimAsync]\n    Task\u003cstring\u003e Call1Async(string p1, int p2);\n\n    [HttpGet]\n    [HttpDelete]\n    [HttpHead]\n    [HttpPut]\n    [HttpPatch]\n    [HttpOptions]\n    [HttpGet(\"Call2/{P1}/{P2}/Get\")]\n    [HttpPost(\"Call2/{P1}/Post\")]\n    Task\u003cstring\u003e Call2Async(CallObj obj);\n\n    [HttpGet(\"Call3/{P1}?vp2={P2}\")]\n    Task\u003cstring\u003e Call3Async(CallObj obj);\n}\n\n[Serializable]\npublic class CallObj\n{\n    public string P1 { get; set; }\n        \n    public int P2 { get; set; }\n}\n```\n\n## [Http] JsonParamName Attribute\n```c#\npublic interface IService4Async\n{\n    [HttpPost(\"Call\")]\n    Task\u003cObj4\u003e Call([JsonParamName(\"i-d\")] Obj4 id, [JsonParamName(\"test-red\")] string testRed);\n}\n```\n\n## [Http] QureyRequired Attribute\n```c#\npublic interface IService4Async\n{\n    [HttpGet(\"Call\")]\n    Task\u003cObj4\u003e Call([QureyRequired] string s);\n}\n```\n\n```c#\npublic interface IService4Async\n{\n    [HttpGet(\"Call\")]\n    Task\u003cObj4\u003e Call(Obj o);\n}\n\npublic class Obj\n{\n    [QureyRequired]\n    public string S1 {get;set;}\n}\n\n```\n## [Http] Obsolete Attribute\n```c#\n[Obsolete]\n[HttpRoute(\"IRout1\", obsolete: true)]\n[HttpRoute(\"IRout2\")]\npublic interface IService4Async\n{\n    //[Obsolete]\n    [HttpGet(obsolete: true)]\n    [HttpGet(\"Call/{id}\")]\n    Task Call(string id);\n}\n```\n## [Http] ValueFilter Attribute\n```c#\npublic class V1FilterAttribute : ValueFilterAttribute\n{\n    public override Task InvokeAsync(ValueContext context, IServiceProvider serviceProvider)\n    {\n        Console.WriteLine($\"value:{context.Value}\");\n        return Task.CompletedTask;\n    }\n}\n\npublic class Obj5\n{\n    [Trim] // TrimAttribute will trim string.\n    [V1]\n    public string TaskId { get; set; }\n\n    public Obj51 Obj51 { get;set; }\n}\n\n[Validate]  //declare validate\npublic class Obj51\n{\n    [V1]\n    public string TaskId { get; set; }\n}\n\npublic interface IService\n{\n    public Task T1([V1]string s1)\n\n    public Task T2(Obj5 obj5)\n}\n\n// add ValidateMiddleware\nservices.AddNMiddleware(i =\u003e i.UseMiddleware\u003cValueFilterMiddleware\u003e());\n\n\n\n```\n## [Http] Auth\n```c#\nservices.AddAuthentication(options =\u003e\n    {\n        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;\n        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;\n    })\n    .AddJwtBearer(opt =\u003e\n    {\n        opt.Audience = xxx;\n        opt.Authority = xxx;\n        opt.RequireHttpsMetadata = false;\n        opt.TokenValidationParameters = new TokenValidationParameters\n        {\n            ValidateIssuer = true,\n            ValidateAudience = true,\n        };\n\n        opt.IncludeErrorDetails = true;\n    });\n```\n\n```c#\n[SecurityApiKeyDefine(\"auth\", \"Authorization\", \"auth2.0 token, e.g: Bearer xxxx...\")]\npublic interface IService4Async\n{\n    [SecurityApiKey(\"auth\")]\n    Task\u003cstring\u003e Call(string id);\n}\n\npublic class Service4Async : IService4Async\n{\n    [AuthToken]\n    public async Task\u003cstring\u003e Call(string id)\n    {\n        return \"call\";\n    }\n}\n\n```\n## [Http] Role Attribute\nAdd a param **key** 'k' to url:  \nhttp://localhost:5000/swagger/index.html?k=k1  \nwill filter the interface in Swagger UI, how to config is blow:\n```c#\n//servcie side\nservices.AddNSwagger(i =\u003e\n                    {\n                        i.Items.Add(new KeyRole\n                        {\n                            Key = \"k1\",\n                            Role = \"R1\"\n                        });\n                        i.Items.Add(new KeyRole\n                        {\n                            Key = \"k2\",\n                            Role = \"R2,R3\"\n                        });\n                        i.Items.Add(new KeyRole\n                        {\n                            Key = \"kall\",\n                            Role = \"RAll\"\n                        });\n                    });\n\n[Role(\"RAll\")]\npublic interface IService3Async\n{\n    [SwaggerRole(\"R1,!RAll\")]    //!RALL mean exclude RALL\n    Task CallAsync();\n\n    [SwaggerRole(\"R1\")]\n    [SwaggerRole(\"R3\")]\n    Task Call2Async();\n\n    [SwaggerRole(\"R2,R3\")]\n    Task Call3Async();\n\n    Task Call4Async();\n}\n```\n\nDefalut role don't have to set the key:  \nhttp://localhost:5000/swagger/index.html would use the default role.\n\n```c#\n//[SwaggerRole(\"default\")] this line can be omitted.\npublic interface IService4Async\n{\n    [SwaggerRole(\"!default\")]    //hide in swagger, but still avaliable to call\n    Task Call(string id);\n} \n```\n\n## [Http] FaultExceptionDefineGroup InheritedFaultExceptionDefine HideFaultExceptionDescription Attribute\nAdd a header field to swagger.\n```c#\npublic sealed class FaultExceptionDefineGroupAttribute : Attribute, IFaultExceptionGroup\n{\n    public List\u003cFaultExceptionDefineAttribute\u003e FaultExceptionDefineAttributes =\u003e\n        new()\n        {\n            new(typeof(CustomException), 400, 1, \"errorCode1 error description\"),\n            new(typeof(CustomException2), 400, 2, \"errorCode2 error description\", true)\n        };\n}\n\n[FaultExceptionDefineGroup]     //declear a group\n[InheritedFaultExceptionDefine] //pass fault define to methods \n[HideFaultExceptionDescription] //is Hide fault decription in swagger?\npublic interface IService4Async\n{\n    //[HideFaultExceptionDecription]\n    [HttpGet(\"Call\")]\n    Task\u003cstring\u003e Call(string id);\n\n    [HttpPost(\"Call\")]\n    Task\u003cstring\u003e Call2(string id);\n}\n```\n\n## [Http] HttpHeaderAttribute\nAdd a header field to swagger.\n```c#\npublic interface IServiceAsync\n{\n    [HttpHeader(\"h2\", \"h2 descrption.\")]\n    Task CallAsync();  \n```\n## [Http] ResponseTextException\nResponseTextException define pain text response with statucode.\n```c#\npublic async Task CallByResponseTextExceptionAsync()\n{\n    throw new ResponseTextException(\"this is customs text. statucode is 701.\", 701);\n}\n````\nAlso should use **response code** define summary, it will display in swagger.\n```c#\n/// \u003cresponse code=\"701\"\u003ereturn the pain text.\u003c/response\u003e\n[ResponseText(701)]\nTask CallByResponseTextExceptionAsync();\n```\n## [Http] StreamName\nNormally stream of return value will map to filestream, if you define **StreamName** property, will set to file name to client.\n```c#\n//stream of return value\nTask\u003cComplexStream\u003e GetComplexStreamAsync();\n...\npublic class ComplexStream\n{\n    public Stream Stream { get; set; }\n    public string StreamName { get; set; }  //the property will map to file name.\n}\n```\n**StreamName** also apply to input params.\n```c#\n//Content-Disposition: form-data; name=\"stream\"; filename=\"t1.docx\"\n//mapping filename to streamname\nTask UploadAsync(Stream stream, string streamName);\n\n```\n## [Http] HttpImages\n```c#\npublic interface IServiceAsync\n{\n    //default Content-Type \"image/jepg\"\n    [HttpImages()]\n    Task\u003cStream\u003e CallAsync();\n```\nMeans return type is images to show in browser, not a download file.\n\n```c#\n\npublic class ComplexStream\n{\n    public Stream Stream { get; set; }\n\n    //affect Content-Type, 1.png -\u003e \"image/png\", 1.jpg -\u003e \"image/jepg\"\n    public string StreamName { get; set; }  \n}\n\npublic interface IServiceAsync\n{\n    [HttpImages()]\n    Task\u003cComplexStream\u003e CallAsync();\n```\n\n## [Http] Example\nSet Example to contract, will effect to swagger.\n```c#\n[Example(\"s1\", \"s1value\")]\n[Example(\"s2\", \"s2value\")]\nTask\u003cCustomObj\u003e Call2Async(CObj obj, string s1, string s2);\n\n[Example(null)]\npublic int? I1 { get; set; }\n```\n## Others\n* An contract args can only contains one **Func\u003cT, Task\u003e**, one **Stream**, same as return value.\n```c#\nComplexStream Call(Stream data, Func\u003cCustomCallbackObj, Task\u003e cb);\n```\n* **TimeoutInterval** of call is a mechanism of NetRpc owns, it do not use the Grpc or RabbitMQ timeout mechanism.\n```c#\nCreateClientProxy\u003cTService\u003e(Channel channel, int timeoutInterval = 1200000)\n```\n## Samples\n* [samples/HelloWorld](samples/HelloWorld) Quick start.\n* [samples/Api](samples/Api) Contains most of features.\n* [samples/Http](samples/Http) Http webapi and swagger api.\n* [samples/LoadBalance](samples/LoadBalance) RabbitMQ load balance and post way to call.\n* [samples/InitializeByDI](samples/InitializeByDI) Use DI to create a client or servcie and how to DI a http channel to exist MVC service.\n* [samples/InitializeByDIFactory](samples/InitializeByDIFactory) Use ClientProxyFactory to get multiple ClientProxies.\n* [samples/CallbackThrottling](samples/CallbackThrottling) It useful when callback is progress.\n* [samples/Gateway](samples/Gateway) Gateway for NetRpc.\n* [samples/OpenTracing](samples/OpenTracing) OpenTracing for NetRpc.\n* [samples/Retry](samples/Retry) Retry for NetRpc.\n* [samples/ShortConn](samples/ShortConn) ShortConn for NetRpc.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnewshadowk%2Fnetrpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnewshadowk%2Fnetrpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnewshadowk%2Fnetrpc/lists"}