NetRpc 8.0.8

dotnet add package NetRpc --version 8.0.8                
NuGet\Install-Package NetRpc -Version 8.0.8                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NetRpc" Version="8.0.8" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add NetRpc --version 8.0.8                
#r "nuget: NetRpc, 8.0.8"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install NetRpc as a Cake Addin
#addin nuget:?package=NetRpc&version=8.0.8

// Install NetRpc as a Cake Tool
#tool nuget:?package=NetRpc&version=8.0.8                

NetRpc

NuGet Badge GitHub license

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.

NuGet

NetRpc for Grpc channel can be installed in your project with the following command.

PM> Install-Package NetRpc.Grpc

NetRpc also supports Http, RabbitMQ channel following packages are available to install:

PM> Install-Package NetRpc.Http
PM> Install-Package NetRpc.RabbitMQ

Others install:

//Jeager tracing function.
PM> Install-Package NetRpc.Jeager

//Http channel for client only.
PM> Install-Package NetRpc.Http.Client
Name
NetRpc.Grpc NetRpc.Grpc NetRpc.Grpc
NetRpc.Http NetRpc.Http NetRpc.Http
NetRpc.Http.Client NetRpc.Http.Client NetRpc.Http.Client
NetRpc.RabbitMQ NetRpc.RabbitMQ NetRpc.RabbitMQ
NetRpc.Jaeger NetRpc.Jaeger NetRpc.Jaeger

Hello world

NetRpc for Grpc channel can be installed in your project with the following command.

PM> Install-Package NetRpc.Grpc
//service side
class Program
{
    static async Task Main(string[] args)
    {
        var host = Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.ConfigureKestrel((_, options) =>
                    {
                        options.ListenAnyIP(50001, listenOptions => listenOptions.Protocols = HttpProtocols.Http2);
                    })
                    .ConfigureServices((_, services) =>
                    {
                        services.AddNGrpcService();
                        services.AddNServiceContract<IServiceAsync, ServiceAsync>();
                    }).Configure(app => { app.UseNGrpc(); });
            }).Build();

        await host.RunAsync();
    }
}

public class ServiceAsync : IServiceAsync
{
    public async Task<string> CallAsync(string s)
    {
        Console.WriteLine($"Receive: {s}");
        return "call ret";
    }
}
//client side
class Program
{
    static async Task Main(string[] args)
    {
        //register
        var services = new ServiceCollection();
        services.AddNGrpcClient(options => options.Url = "http://localhost:50001");
        services.AddNClientContract<IServiceAsync>();
        services.AddLogging();
        var buildServiceProvider = services.BuildServiceProvider();

        //get service
        var service = buildServiceProvider.GetService<IServiceAsync>();
        Console.WriteLine("call: hello world.");
        var ret = await service.CallAsync("hello world.");
        Console.WriteLine($"ret: {ret}");
    }
}
//datacontract is referenced by client and service
public interface IServiceAsync
{
    Task<string> CallAsync(string s);
}

Code here: samples/HelloWorld.

Quick start: QuickRef

Overall

NetRpc provide RabbitMQ/Grpc/Http Channels to connect, each one has different advantages.

  • RabbitMQ provide load balance, queue feature.
  • Grpc use http2, provide all http2 advantages.
  • Http use webapi, also provide swagger api.

All channels use uniform contract, so easily to switch channel without modify service implementation.

alternate text is missing from this package README image

RabbitMQ/Grpc

There is message channel for RabbitMQ and Grpc, Http pls see topic blow. alternate text is missing from this package README image

Initialize by DI

There has two ways to initialize service and client, See DI sample below:

//service side
var host = Host.CreateDefaultBuilder()
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.ConfigureKestrel((context, options) =>
            {
                options.ListenAnyIP(50001, listenOptions => listenOptions.Protocols = HttpProtocols.Http2);
            })
            .ConfigureServices((context, services) =>
            {
                services.AddNGrpcService();
                services.AddNServiceContract<IServiceAsync, ServiceAsync>();
            }).Configure(app =>
            {
                app.UseNGrpc();
            });
    }).Build();
//client side
//register
ServiceCollection services = new ServiceCollection();
services.AddNGrpcClient(options => options.Url = "http://localhost:50001");
services.AddNClientContract<IServiceAsync>();
services.AddLogging();
var buildServiceProvider = services.BuildServiceProvider();

//get service
var service = buildServiceProvider.GetService<IServiceAsync>();
var clientProxy = buildServiceProvider.GetService<IClientProxy<IServiceAsync>>();

//call remote
await service.CallAsync("hello world.");
await clientProxy.Proxy.CallAsync("hello world.");

If want to inject multiple ClientProxies, should use IClientProxyFactory.

//service side
services.Configure<RabbitMQClientOptions>("mq1", context.Configuration.GetSection("Mq1"));
services.Configure<RabbitMQClientOptions>("mq2", context.Configuration.GetSection("Mq2"));
services.AddNRabbitMQClient();
services.AddNRpcClientContract<IService>();
//client side
public class MyHost : IHostedService
{
    private readonly IClientProxyFactory _factory;

    public MyHost(IClientProxyFactory factory)  //Get IClientProxyFactory here
    {
        _factory = factory;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        var clientProxy = _factory.CreateProxy<IService>("grpc1");
...

Serialization

RabbitMQ, Grpc channel base on BinaryFormatter, make sure all contract model mark as [Serializable].

[Serializable]
public class CustomObj
{
    //...
}

[Important] When returned Custom object contains a Stream:
RabbitMQ, Grpc channel Stream mask as [field: NonSerialized].
Http channel Stream mask as [JsonIgnore].

Task<ComplexStream> GetComplexStreamAsync();

[Serializable]
public class ComplexStream
{
    [field: NonSerialized]       //add for RabbitMQ, Grpc channel
    [JsonIgnore]                 //add for http channel
    public Stream Stream { get; set; }

    public string OtherInfo { get; set; }
}

Supported contract type

//Async
public interface IServiceAsync
{
    Task<CustomObj> SetAndGetObj(CustomObj obj);

    /// <exception cref="TaskCanceledException"></exception>
    Task CallByCancelAsync(CancellationToken token);

    Task CallByCallBackAsync(Func<CustomCallbackObj, Task> cb);

    /// <exception cref="NotImplementedException"></exception>
    Task CallBySystemExceptionAsync();

    /// <exception cref="CustomException"></exception>>
    Task CallByCustomExceptionAsync();

    Task<Stream> GetStreamAsync();

    Task SetStreamAsync(Stream data);

    Task<Stream> EchoStreamAsync(Stream data);

    Task<ComplexStream> GetComplexStreamAsync();

    /// <exception cref="TaskCanceledException"></exception>
    Task<ComplexStream> ComplexCallAsync(CustomObj obj, Stream data, Func<CustomCallbackObj, Task> cb, CancellationToken token);
}

GenericType

Make sure the genericType in contract is mark as [Serializable].

Task<T2> CallByGenericTypeAsync<T1, T2>(T1 obj);

Header is a type of Dictionary<string, object> object, mark sure your object mark as [Serializable].
Before call action, client set the Header which mark as AsyncLocal that guarantee muti-threads don`t influence each other.

//client side
_proxy.AdditionHeader.Add("k1", "header value");
_proxy.TestHeader();

Service can receive the header object which client sent.

//service side
public void TestHeader()
{
    var h = GlobalActionExecutingContext.Context.Header;
    //...
}
  • AdditionHeader
    On 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.
//client side
//set the AdditionHeader with SessionId
client.Context.AdditionHeader = new Dictionary<string, object> {{"SessionId", 1}};
//will tranfer the header of SessionId to service.
client.Proxy.Call();

Context

On service side, Midderware or Filter can access ActionExecutingContext, it is

Property Type Description
Header Dictionary<string object> Header sent from client.
Target object Service instance of invoked action.
ChannelType ChannelType Enum value: Undefined, Grpc, RabbitMQ, Http.
InstanceMethodInfo MethodInfo Current invoked method.
ContractMethodInfo MethodInfo Current invoked contract method.
ActionInfo ActionInfo Warpped info of current invoked method.
Args object[] Args of invoked action.
PureArgs object[] Args of invoked action without stream and action.
Callback Func<object, Task> Callback of invoked action.
Token CancellationToken CancellationToken of invoked action.
Stream Stream Stream of invoked action.
ServiceProvider IServiceProvider ServiceProvider of invoked action.
Contract Contract Contract.
MethodObj MethodObj MethodObj.
Result object Result of invoked action.
Properties Dictionary<object, object> A central location for sharing state between components during the invoking process.

On client side, Midderware can access ClientActionExecutingContext, it is

Property Type Description
ServiceProvider IServiceProvider ServiceProvider of invoked action.
Result object Result of invoked action.
Header Dictionary<string object> Header sent from client.
OptionsName string Config options name by DI.
MethodInfo MethodInfo Current invoked method.
Callback Func<object, Task> Callback of invoked action.
Token CancellationToken CancellationToken of invoked action.
ContractInfo ContractInfo ContractInfo.
MethodObj MethodObj MethodObj.
Stream Stream Stream of invoked action.
PureArgs object[] Args of invoked action without stream and action.
Properties Dictionary<object, object> A central location for sharing state between components during the invoking process.

Filter

Filter is common function like MVC.

//service side
public class TestFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.Write($"TestFilter.Execute(), context:{context}");
    }
}

internal class Service : IService
{
    [TestFilter]
    public void Test()
    {
        //...
    }
}

NetRpc Middleware

The way use NetRpc Middleware and use MVC Middleware is same, the only difference is use RpcContext instead of HttpContext.
Support DI Type and ctor args.

//servcie
services.AddNRpcMiddleware(i => i.UseMiddleware<TestGlobalExceptionMiddleware>("arg1value"));

public class TestGlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;

    public TestGlobalExceptionMiddleware(RequestDelegate next, string arg1, DIType diType)
    {
        _next = next;
        Console.WriteLine($"{arg1}");
    }

    public async Task InvokeAsync(RpcContext context, DIType diType)
    {
        try
        {
            await _next(context);
        }
        catch (Exception e)
        {
            Console.WriteLine($"[log by Middleware] {e.GetType().Name}");
            throw;
        }
    }
}

Client side also support Middleware.

public class ClientOpenTracingMiddleware
{
    private readonly ClientRequestDelegate _next;

    public ClientOpenTracingMiddleware(ClientRequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(ClientContext context, ITracer tracer)
    {
...

Retry

[ClientRetry(1000, 2000, 3000)]  // retry in 1000ms, 2000ms, 3000ms
public interface IServiceAsync
{
    //[ClientRetry(1000, 2000, 3000)]   or set here.
    Task CallAsync(string s);
}
...

Load Balance

Only for RabbitMQ.
When run multiple service instances, ther service will auto apply the load balance, this function is base on the RabbitMQ.
MQOptions.PrefetchCount: The service will acquire more messages, up to the PrefetchCount limit, defalut value is 1.

FaultException<T>

FaultException is

Property Type Description
Detail Exception Threw exception, will save the orginal StackTrace when Exception via remote transfer.
Action string Invoked action name and args, if many actions split by '|'.
//service side
internal class ServiceAsync : IServiceAsync
{
    public Task CallBySystemExceptionAsync()
    {
        throw new NotImplementedException();
    }
}
//client side
try
{
    await proxy.CallBySystemExceptionAsync();
}
catch (FaultException<NotImplementedException> e)
{
    //e.Detail.StackTrace will get the orginal info. (http channel do not support)
}
catch (FaultException e2)
{

}
catch (OperationCanceledException e2)
{

}
catch (TaskCanceledException e2)
{

}

Cancel

/// <exception cref="TaskCanceledException"></exception>
Task CallByCancelAsync(CancellationToken token);

Call back

Task CallByCallBackAsync(Func<CustomCallbackObj, Task> cb);

Built-in CallbackThrottlingMiddleware is useful when callback is progress, normally progress do not need callback every time to client, also for saving network resources.

//service side
services.AddNRpcMiddleware(i => i.UseCallbackThrottling(1000)); //limit to one call per second
//or this:
services.AddNRpcCallbackThrottling(1000);
...
public async Task Call(Func<int, Task> cb)
{
    for (int i = 0; i <= 20; i++)
    {
        await Task.Delay(100);
        await cb(i);
        Console.WriteLine($"Send callback: {i}");
    }
}
//client side
await proxy.Call(i => Console.WriteLine($"receive callback: {i}"));
//service sent 20 callbacks by interval 100ms.
Send callback: 0
Send callback: 1
Send callback: 2
Send callback: 3
Send callback: 4
Send callback: 5
Send callback: 6
Send callback: 7
Send callback: 8
Send callback: 9
Send callback: 10
Send callback: 11
Send callback: 12
Send callback: 13
Send callback: 14
Send callback: 15
Send callback: 16
Send callback: 17
Send callback: 18
Send callback: 19
Send callback: 20      //at the end will force send last callback

//----------------------------------------------------

//client only received 4 callbacks by interval 1000ms
receive callback: 0
receive callback: 8
receive callback: 17
receive callback: 20   //will receive last callback.

Stream

Task<Stream> GetStreamAsync();

Task SetStreamAsync(Stream data);

Task<Stream> EchoStreamAsync(Stream data);

Task<ComplexStream> GetComplexStreamAsync();
[Serializable]
public class ComplexStream
{
    [field: NonSerialized]
    public Stream Stream { get; set; }

    public string OtherInfo { get; set; }
}

MQPostAttribute

Only for RabbitMQ channel, means post way to call, after sent message to rabbitMQ then return immediately, consumer will consum messages in queue asynchronous.
Post method define has some limits, no callback Action, no cancelToken, no return value.

[MQPost]
Task PostAsync(string s1, Stream stream);

IgnoreAttribute

  • GrpcIgnoreAttribute
  • RabbitMQIgnoreAttribute
  • HttpIgnoreAttribute
[GrpcIgnore]
Task CallAsync(call);

Client Event

ClientProxy has events:

  • ExceptionInvoked it usefull when you want to log the exception when call.
  • Heartbeat see topic below.
  • Connected invoked when conntect the service.
  • DisConnected invoked when disconntect the service. if Heartbeat faild will invoke too.
clientProxy.Connected += (s, e) => Console.WriteLine("[event] Connected");
clientProxy.DisConnected += (s, e) => Console.WriteLine("[event] DisConnected");
clientProxy.ExceptionInvoked += (s, e) => Console.WriteLine("[event] ExceptionInvoked");
clientProxy.Heartbeat += async s => s.Proxy.Hearbeat();

Hearbeat

ClientProxy has a Heartbeat function after you call StartHeartbeat(), the interval is 10 seconds by default.
Client should register the Heartbeat event and implementation of heartbeat.
According to Heartbeat is successfull or faild, Connected or DisConnected will invoke correspondingly.

//client set the heartbeat interval to 10*1000
clientProxy.Heartbeat += async s => s.Proxy.Hearbeat();
clientProxy.StartHeartbeat(true);

Options support realtime update

Normally 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.

  • GrpcServiceOptions
  • GrpcClientOptions
  • RabbitMQServiceOptions
  • RabbitMQClientOptions

Note: only for DI mode:

services.AddNGrpcService(i => i.AddPort("0.0.0.0", 50001));

Gateway

Gateway has many advantages:

  • Convert Channel.
  • Provide exception handle.
  • Provide access authority.
  • ...

alternate text is missing from this package README image

The code blow show how to Receive message from RabbitMQ channel client and send to Grpc channel service.

 //set single target by DI.
services.AddNRabbitMQService(i => i.Value = TestHelper.Helper.GetMQOptions());
services.AddNRpcGateway<IService>(o => o.Channel = new Channel("localhost", 50001, ChannelCredentials.Insecure));
services.AddNRpcGateway<IService2>();

Also privode middleware in the gateway service, can add access authority if needed.

OpenTracing

Set the tags relate to method, it contains:

  • Name
  • Params
  • Result
  • Exception

For more details pls go to samples/OpenTracing alternate text is missing from this package README image

Get HttpContext

Use IHttpContextAccessor by services.AddHttpContextAccessor();

[Http] NetRpc.Http

NetRpc.Http provide:

  • Webapi for call api.
  • Swagger for display and test api.
  • SignalR for callback and cancel during method invoking.

Note:

  • Swagger is not necessary.
  • Mvc is not necessary.

alternate text is missing from this package README image

[Http] Create Host

Use DI to create NHttp service, also could create NHttp service base on exist MVC servcie.

//regist services
services.AddSignalR();         // add SignalR service
services.AddNRpcSwagger();   // add Swgger service
services.AddNHttp(i =>    // add RpcHttp service
{
    i.ApiRootPath = "/api";
    i.IgnoreWhenNotMatched = false;
});
services.AddNRpcMiddleware(i => i.UseMiddleware<MyNRpcMiddleware>());  // define NetRpc Middleware
services.AddNRpcServiceContract(instanceTypes); // add Contracts
//use components
app.UseSignalR(routes => { routes.MapHub<CallbackHub>(hubPath); });   // define CallbackHub
app.UseNRpcSwagger();   // use NRpcSwagger middleware
app.UseNHttp();      // use NHttp middleware

[Http] Swagger

Use Swashbuckle.AspNetCore.Swagger to implement swagger feature.
Note: 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.
Add codes below to enabled swagger function.

services.AddNRpcSwagger();   // add Swgger service
...
app.UseNRpcSwagger();        // use NRpcSwagger middleware

The demo show how to call a method with callback and cancel: alternate text is missing from this package README image

If define Callback Func<T, Task> and CancelToken supported, need set _connId and _callId when request. OperationCanceledException will receive respones with statuscode 600.

alternate text is missing from this package README image

Also support summary on model or method.

HttpServiceOptions

/// <summary>
/// Api root path, like '/api', default value is null.
/// </summary>
public string? ApiRootPath { get; set; }

/// <summary>
/// Set true will pass to next middleware when not match the method, default value is false.
/// </summary>
public bool IgnoreWhenNotMatched { get; set; }

/// <summary>
/// ShortConn redis connection string.
/// </summary>
public string? ShortConnRedisConnStr { get; set; }

/// <summary>
/// ShortConn task stream file temp dir.
/// </summary>
public string? ShortConnTempDir { get; set; }

[Http] Callback and Cancel

Contract define the Func<T, Task> and CancellationToken to enable this feature.

Task CallAsync(Func<int, Task> cb, CancellationToken token);

Client code belows show how to get connectionId, how to receive callback, how to cancel a method.

//client js side
var connection = new signalR.HubConnectionBuilder().withUrl("{hubUrl}").build();

//GetConnId function
connection.start().then(function () {
    addText("signalR connected!");
    connection.invoke("GetConnId").then((cid) => {
        addText("GetConnId, _connId:" + cid);
    });
}).catch(function (err) {
    return console.error(err.toString());
});

//Callback
connection.on("Callback", function (callId, data) {
    addText("callback, callId:" + callId + ", data:" + data);
});

//Cancel
document.getElementById("cancelBtn").addEventListener("click", function (event) {
    connection.invoke("Cancel").catch(function (err) {
        return console.error(err.toString());
    });

    event.preventDefault();
});

[Http] Short Connection

Convert long running call to short call with start prog cancel replace interface.
ref: samples/ShortConn alternate text is missing from this package README image

[Http] FaultExceptionAttribute

If contract has Exception defined, should use FaultExceptionAttribute to define statuscode, use response code to define summary(will display in Swagger), otherwise NetRpc will use statuscode 400 to define all Exception by default.

[FaultException(typeof(CustomException), 400, 1, "errorCode1 error description")]
[FaultException(typeof(CustomException2), 400, 2, "errorCode2 error description")]
Task CallByCustomExceptionAsync();
//Also can put here, will effect to all methods in IServiceAsync.
[FaultException(typeof(CustomException2), 400, 2, "errorCode2 error description")]
public interface IServiceAsync

[Http] Method Attribute

Support some Attributes, pls see demo.

[HttpTrimAsync]
[HttpRoute("IRout1")]
[Tag("RoutTag1")]
public interface IService2Async
{
    [Tag("CallTag1")]
    [HttpPost]
    [HttpRoute("Call1/{p1}")]
    [HttpGet("/Root/Call/{p1}")]
    [HttpTrimAsync]
    Task<string> Call1Async(string p1, int p2);

    [HttpGet]
    [HttpDelete]
    [HttpHead]
    [HttpPut]
    [HttpPatch]
    [HttpOptions]
    [HttpGet("Call2/{P1}/{P2}/Get")]
    [HttpPost("Call2/{P1}/Post")]
    Task<string> Call2Async(CallObj obj);

    [HttpGet("Call3/{P1}?vp2={P2}")]
    Task<string> Call3Async(CallObj obj);
}

[Serializable]
public class CallObj
{
    public string P1 { get; set; }
        
    public int P2 { get; set; }
}

[Http] JsonParamName Attribute

public interface IService4Async
{
    [HttpPost("Call")]
    Task<Obj4> Call([JsonParamName("i-d")] Obj4 id, [JsonParamName("test-red")] string testRed);
}

[Http] QureyRequired Attribute

public interface IService4Async
{
    [HttpGet("Call")]
    Task<Obj4> Call([QureyRequired] string s);
}
public interface IService4Async
{
    [HttpGet("Call")]
    Task<Obj4> Call(Obj o);
}

public class Obj
{
    [QureyRequired]
    public string S1 {get;set;}
}

[Http] Obsolete Attribute

[Obsolete]
[HttpRoute("IRout1", obsolete: true)]
[HttpRoute("IRout2")]
public interface IService4Async
{
    //[Obsolete]
    [HttpGet(obsolete: true)]
    [HttpGet("Call/{id}")]
    Task Call(string id);
}

[Http] ValueFilter Attribute

public class V1FilterAttribute : ValueFilterAttribute
{
    public override Task InvokeAsync(ValueContext context, IServiceProvider serviceProvider)
    {
        Console.WriteLine($"value:{context.Value}");
        return Task.CompletedTask;
    }
}

public class Obj5
{
    [Trim] // TrimAttribute will trim string.
    [V1]
    public string TaskId { get; set; }

    public Obj51 Obj51 { get;set; }
}

[Validate]  //declare validate
public class Obj51
{
    [V1]
    public string TaskId { get; set; }
}

public interface IService
{
    public Task T1([V1]string s1)

    public Task T2(Obj5 obj5)
}

// add ValidateMiddleware
services.AddNMiddleware(i => i.UseMiddleware<ValueFilterMiddleware>());



[Http] Auth

services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(opt =>
    {
        opt.Audience = xxx;
        opt.Authority = xxx;
        opt.RequireHttpsMetadata = false;
        opt.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
        };

        opt.IncludeErrorDetails = true;
    });
[SecurityApiKeyDefine("auth", "Authorization", "auth2.0 token, e.g: Bearer xxxx...")]
public interface IService4Async
{
    [SecurityApiKey("auth")]
    Task<string> Call(string id);
}

public class Service4Async : IService4Async
{
    [AuthToken]
    public async Task<string> Call(string id)
    {
        return "call";
    }
}

[Http] Role Attribute

Add a param key 'k' to url:
http://localhost:5000/swagger/index.html?k=k1
will filter the interface in Swagger UI, how to config is blow:

//servcie side
services.AddNSwagger(i =>
                    {
                        i.Items.Add(new KeyRole
                        {
                            Key = "k1",
                            Role = "R1"
                        });
                        i.Items.Add(new KeyRole
                        {
                            Key = "k2",
                            Role = "R2,R3"
                        });
                        i.Items.Add(new KeyRole
                        {
                            Key = "kall",
                            Role = "RAll"
                        });
                    });

[Role("RAll")]
public interface IService3Async
{
    [SwaggerRole("R1,!RAll")]    //!RALL mean exclude RALL
    Task CallAsync();

    [SwaggerRole("R1")]
    [SwaggerRole("R3")]
    Task Call2Async();

    [SwaggerRole("R2,R3")]
    Task Call3Async();

    Task Call4Async();
}

Defalut role don't have to set the key:
http://localhost:5000/swagger/index.html would use the default role.

//[SwaggerRole("default")] this line can be omitted.
public interface IService4Async
{
    [SwaggerRole("!default")]    //hide in swagger, but still avaliable to call
    Task Call(string id);
} 

[Http] FaultExceptionDefineGroup InheritedFaultExceptionDefine HideFaultExceptionDescription Attribute

Add a header field to swagger.

public sealed class FaultExceptionDefineGroupAttribute : Attribute, IFaultExceptionGroup
{
    public List<FaultExceptionDefineAttribute> FaultExceptionDefineAttributes =>
        new()
        {
            new(typeof(CustomException), 400, 1, "errorCode1 error description"),
            new(typeof(CustomException2), 400, 2, "errorCode2 error description", true)
        };
}

[FaultExceptionDefineGroup]     //declear a group
[InheritedFaultExceptionDefine] //pass fault define to methods 
[HideFaultExceptionDescription] //is Hide fault decription in swagger?
public interface IService4Async
{
    //[HideFaultExceptionDecription]
    [HttpGet("Call")]
    Task<string> Call(string id);

    [HttpPost("Call")]
    Task<string> Call2(string id);
}

[Http] HttpHeaderAttribute

Add a header field to swagger.

public interface IServiceAsync
{
    [HttpHeader("h2", "h2 descrption.")]
    Task CallAsync();  

[Http] ResponseTextException

ResponseTextException define pain text response with statucode.

public async Task CallByResponseTextExceptionAsync()
{
    throw new ResponseTextException("this is customs text. statucode is 701.", 701);
}

Also should use response code define summary, it will display in swagger.

/// <response code="701">return the pain text.</response>
[ResponseText(701)]
Task CallByResponseTextExceptionAsync();

[Http] StreamName

Normally stream of return value will map to filestream, if you define StreamName property, will set to file name to client.

//stream of return value
Task<ComplexStream> GetComplexStreamAsync();
...
public class ComplexStream
{
    public Stream Stream { get; set; }
    public string StreamName { get; set; }  //the property will map to file name.
}

StreamName also apply to input params.

//Content-Disposition: form-data; name="stream"; filename="t1.docx"
//mapping filename to streamname
Task UploadAsync(Stream stream, string streamName);

[Http] HttpImages

public interface IServiceAsync
{
    //default Content-Type "image/jepg"
    [HttpImages()]
    Task<Stream> CallAsync();

Means return type is images to show in browser, not a download file.


public class ComplexStream
{
    public Stream Stream { get; set; }

    //affect Content-Type, 1.png -> "image/png", 1.jpg -> "image/jepg"
    public string StreamName { get; set; }  
}

public interface IServiceAsync
{
    [HttpImages()]
    Task<ComplexStream> CallAsync();

[Http] Example

Set Example to contract, will effect to swagger.

[Example("s1", "s1value")]
[Example("s2", "s2value")]
Task<CustomObj> Call2Async(CObj obj, string s1, string s2);

[Example(null)]
public int? I1 { get; set; }

Others

  • An contract args can only contains one Func<T, Task>, one Stream, same as return value.
ComplexStream Call(Stream data, Func<CustomCallbackObj, Task> cb);
  • TimeoutInterval of call is a mechanism of NetRpc owns, it do not use the Grpc or RabbitMQ timeout mechanism.
CreateClientProxy<TService>(Channel channel, int timeoutInterval = 1200000)

Samples

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (5)

Showing the top 5 NuGet packages that depend on NetRpc:

Package Downloads
NetRpc.RabbitMQ

Provide rabbitMQ channel implementation for NetRpc.

NetRpc.Http.Client

Provide Http client channel implementation for NetRpc.

NetRpc.Grpc

Provide grpc channel implementation for NetRpc.

NetRpc.OpenTracing

Provide openTracing support for NetRpc.

EE.Core.RabbitMQ

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
8.0.8 32 11/29/2024
8.0.7 38 11/28/2024
8.0.6 108 11/18/2024
8.0.5 106 11/1/2024
8.0.4 106 11/1/2024
8.0.3 97 11/1/2024
8.0.2 106 11/1/2024
8.0.1 110 11/1/2024
8.0.0 267 10/11/2024
7.0.31 220 4/18/2024
7.0.30 1,035 12/27/2022
7.0.29 852 12/27/2022
7.0.28 842 12/27/2022
7.0.27 834 12/27/2022
7.0.26 831 12/13/2022
7.0.25 859 12/8/2022
7.0.24 839 12/8/2022
7.0.23 881 12/2/2022
7.0.22 910 12/2/2022
7.0.21 886 12/2/2022
7.0.20 966 12/1/2022
7.0.19 924 12/1/2022
7.0.18 933 12/1/2022
7.0.17 941 11/29/2022
7.0.16 922 11/29/2022
7.0.15 891 11/29/2022
7.0.14 928 11/28/2022
7.0.13 907 11/28/2022
7.0.12 933 11/28/2022
7.0.11 938 11/28/2022
7.0.10 956 11/28/2022
7.0.9 913 11/26/2022
7.0.8 925 11/26/2022
7.0.7 913 11/26/2022
7.0.6 910 11/25/2022
7.0.5 909 11/25/2022
7.0.4 946 11/25/2022
7.0.3 907 11/25/2022
7.0.2 963 11/22/2022
7.0.1 946 11/22/2022
7.0.0 1,003 11/11/2022
6.1.17 401 2/1/2024
6.1.15 3,914 6/21/2022
6.1.14 1,849 6/20/2022
6.1.13 1,921 6/6/2022
6.1.8 1,863 5/23/2022
6.1.7 1,890 5/23/2022
6.1.6 1,916 5/20/2022
6.1.5 1,884 5/18/2022
6.1.4 1,932 5/16/2022
6.1.3 1,907 5/12/2022
6.1.1 1,905 5/6/2022
6.1.0 1,920 5/5/2022
6.1.0-rc3 752 4/12/2022
6.0.24 1,933 3/22/2022
6.0.20 1,874 3/22/2022
6.0.19 1,889 3/16/2022
6.0.14 1,933 3/9/2022
6.0.12 1,936 3/9/2022
6.0.10 1,936 3/7/2022
6.0.9 1,903 3/4/2022
6.0.8 2,090 1/26/2022
6.0.7 1,923 1/24/2022
6.0.4 1,384 12/14/2021
6.0.1 1,356 12/9/2021
6.0.0 1,328 12/1/2021
6.0.0-beta2 802 10/28/2021
6.0.0-beta1 725 10/14/2021
5.0.48 1,711 11/1/2021
5.0.47 1,804 10/28/2021
5.0.46 1,749 9/30/2021
5.0.45 1,745 9/27/2021
5.0.44 1,870 7/7/2021
5.0.41 1,774 6/22/2021
5.0.40 1,770 6/21/2021
5.0.38 1,685 5/14/2021
5.0.32 3,717 3/17/2021
5.0.31 1,754 3/15/2021
5.0.29 1,716 2/23/2021
5.0.28 1,833 2/3/2021
5.0.27 1,787 2/2/2021
5.0.26 1,781 1/29/2021
5.0.25 1,750 1/26/2021
5.0.24 1,739 1/26/2021
5.0.23 1,845 1/21/2021
5.0.22 1,860 1/21/2021
5.0.21 1,838 1/20/2021
5.0.20 1,804 1/19/2021
5.0.19 1,787 1/15/2021
5.0.15 1,860 1/6/2021
5.0.0-rc3 1,116 11/19/2020
5.0.0-rc1 1,049 11/11/2020
3.1.13 2,857 9/29/2020
3.1.12 2,769 9/23/2020
3.1.10 2,502 9/22/2020
3.1.9 6,932 9/17/2020
3.1.8 2,409 9/16/2020
3.1.7 2,403 9/15/2020
3.1.6 2,404 9/10/2020
3.1.5 2,417 9/10/2020
3.1.4 2,384 9/9/2020
3.1.3 2,412 9/1/2020
3.1.2 2,424 8/24/2020
3.1.1 2,400 8/23/2020
3.1.0 2,398 8/23/2020
3.0.16 2,415 8/21/2020
3.0.15 2,558 8/20/2020
3.0.12 2,540 8/20/2020
3.0.11 2,469 8/20/2020
3.0.10 2,530 8/19/2020
3.0.9 2,383 8/12/2020
3.0.8 2,475 8/6/2020
3.0.7 2,481 8/5/2020
2.6.9 2,582 6/10/2020
2.6.8 2,452 6/5/2020
2.6.6 2,492 6/5/2020
2.6.3 2,615 6/4/2020
2.6.2 2,664 6/3/2020
2.5.21 2,475 5/11/2020
2.5.20 2,488 4/24/2020
2.5.19 2,491 4/23/2020
2.5.18 2,565 4/23/2020
2.5.17 2,540 4/22/2020
2.5.14 2,497 4/20/2020
2.5.12 2,571 4/14/2020
2.5.11 2,564 4/13/2020
2.5.5 2,490 4/3/2020
2.5.4 2,498 3/30/2020
2.5.1 2,656 3/23/2020
2.5.0 2,597 3/12/2020
2.4.9 801 1/3/2020
2.4.7 2,658 1/2/2020
2.4.3 2,628 12/27/2019
2.3.0 2,590 3/12/2020
2.2.37 3,132 11/11/2019
2.2.36 2,789 11/11/2019
2.2.35 2,836 11/8/2019
2.2.34 2,795 11/7/2019
2.2.33 2,876 11/7/2019
2.2.32 2,828 11/6/2019
2.2.31 2,712 11/6/2019
2.2.30 2,923 11/6/2019
2.2.29 2,699 11/6/2019
2.2.28 2,885 11/5/2019
2.2.27 2,919 11/1/2019
2.2.26 2,885 11/1/2019
2.2.25 2,751 11/1/2019
2.2.24 2,709 10/30/2019
2.2.23 2,688 10/30/2019
2.2.22 2,837 10/29/2019
2.2.21 2,826 10/29/2019
2.2.20 2,688 10/29/2019
2.2.19 2,803 10/28/2019
2.2.18 2,869 10/25/2019
2.2.17 2,867 10/25/2019
2.2.16 2,718 10/25/2019
2.2.15 2,829 10/18/2019
2.2.14 2,823 10/17/2019
2.2.13 2,809 10/17/2019
2.2.12 2,807 10/17/2019
2.2.11 2,886 10/16/2019
2.2.10 2,743 10/16/2019
2.2.9 2,749 10/15/2019
2.2.8 2,846 10/15/2019
2.2.7 2,813 10/14/2019
2.2.6 2,914 10/14/2019
2.2.5 2,759 10/14/2019
2.2.4 2,801 10/14/2019
2.2.3 2,795 10/12/2019
2.2.2 2,855 10/12/2019
2.2.1 2,710 10/11/2019
2.2.0 1,340 10/11/2019
2.1.21 2,503 9/24/2019
2.1.20 2,493 9/20/2019
2.1.19 2,505 9/18/2019
2.1.18 2,575 9/18/2019
2.1.16 2,450 9/18/2019
2.1.15 2,396 9/17/2019
2.1.14 2,421 9/17/2019
2.1.13 2,406 9/17/2019
2.1.12 2,456 9/12/2019
2.1.11 2,519 9/11/2019
2.1.10 2,368 9/11/2019
2.1.9 2,413 9/11/2019
2.1.8 2,438 9/10/2019
2.1.7 2,320 9/9/2019
2.1.6 2,435 9/9/2019
2.1.5 2,480 9/9/2019
2.1.4 2,413 9/9/2019
2.1.3 2,328 9/9/2019
2.1.2 2,456 9/6/2019
2.1.1 2,502 9/6/2019
2.1.0 2,619 9/6/2019
2.0.7 2,015 8/23/2019
2.0.6 1,962 8/23/2019
2.0.5 2,038 8/22/2019
2.0.4 2,081 8/22/2019
2.0.3 1,948 8/22/2019
2.0.2 1,953 8/22/2019
2.0.1 1,957 8/21/2019
2.0.0 1,751 8/21/2019
1.0.39 1,618 7/9/2019
1.0.38 1,541 7/1/2019
1.0.37 1,552 6/28/2019
1.0.36 1,569 6/18/2019
1.0.35 1,503 6/17/2019
1.0.34 1,592 6/13/2019
1.0.33 1,585 6/13/2019
1.0.32 1,600 6/12/2019
1.0.31 1,601 6/10/2019
1.0.30 1,616 6/10/2019
1.0.29 1,601 5/29/2019
1.0.28 1,640 5/22/2019
1.0.27 1,585 5/22/2019
1.0.26 1,588 5/14/2019
1.0.25 1,581 5/14/2019
1.0.23 1,609 5/14/2019
1.0.22 1,616 5/14/2019