├── src ├── Sandwych.JsonRpc │ ├── IJsonRpcServiceInfo.cs │ ├── JsonRpcProtocolDefinition.cs │ ├── IJsonRpcMethodInfo.cs │ ├── JsonRpcVersion.cs │ ├── JsonRpcException.cs │ ├── IRpcClient.cs │ ├── JsonRpcMethodAttribute.cs │ ├── JsonRpcServiceAttribute.cs │ ├── Settings.cs │ ├── JsonRpcError.cs │ ├── JsonRpcResponse.cs │ ├── JsonRpcRequest.cs │ ├── Sandwych.JsonRpc.csproj │ ├── RpcProxyFactory.cs │ ├── RpcMethodInterceptor.cs │ ├── JsonRpcClient.cs │ └── PlainJsonConvert.cs └── Sandwych.JsonRpc.Test │ ├── ITestJsonRpcService.cs │ ├── Sandwych.JsonRpc.Test.csproj │ ├── JsonRpcClientTests.cs │ └── RpcProxyFactoryTests.cs ├── .gitignore ├── .gitattributes ├── SandwychJsonRpc.sln └── README.md /src/Sandwych.JsonRpc/IJsonRpcServiceInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.JsonRpc 6 | { 7 | internal interface IJsonRpcServiceInfo 8 | { 9 | Uri Endpoint { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/JsonRpcProtocolDefinition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Sandwych.JsonRpc 6 | { 7 | public static class JsonRpcProtocolDefinition 8 | { 9 | public const string JsonMediaType = "application/json"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/IJsonRpcMethodInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.JsonRpc 6 | { 7 | internal interface IJsonRpcMethodInfo 8 | { 9 | string Name { get; } 10 | Type ErrorDataType { get; } 11 | bool IsNotification { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Bb]in/ 2 | [Oo]bj/ 3 | [Ww]orking*/ 4 | [Bb]uild/Temp/ 5 | [Dd]oc/doc.shfbproj_* 6 | TestResults/ 7 | AppPackages/ 8 | src/packages/ 9 | *.suo 10 | *.user 11 | *.userprefs 12 | _ReSharper.* 13 | *.ReSharper.user 14 | *.resharper.user 15 | .vs/ 16 | *.lock.json 17 | *.nuget.props 18 | *.nuget.targets 19 | *.orig 20 | .DS_Store 21 | /build/ 22 | 23 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/JsonRpcVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | using Newtonsoft.Json.Converters; 5 | using Newtonsoft.Json.Serialization; 6 | using Newtonsoft.Json; 7 | 8 | namespace Sandwych.JsonRpc 9 | { 10 | [JsonConverter(typeof(StringEnumConverter))] 11 | public enum JsonRpcVersion 12 | { 13 | [EnumMember(Value = "1.0")] 14 | Version1 = 1, 15 | 16 | [EnumMember(Value = "2.0")] 17 | Version2 = 2 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc.Test/ITestJsonRpcService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Sandwych.JsonRpc.Test 7 | { 8 | [JsonRpcService("/projects/jayrock/demo.ashx")] 9 | public interface ITestJsonRpcService 10 | { 11 | 12 | [JsonRpcMethod("echo")] 13 | Task EchoAsync(string text); 14 | 15 | [JsonRpcMethod("total")] 16 | Task TotalAsync(IEnumerable values); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/JsonRpcException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sandwych.JsonRpc 4 | { 5 | 6 | [Serializable] 7 | public sealed class JsonRpcException : Exception 8 | { 9 | public JsonRpcException(string msg, JsonRpcError error) 10 | : base(msg) 11 | { 12 | this.Error = error; 13 | } 14 | 15 | public JsonRpcError Error { get; private set; } 16 | 17 | public override string ToString() 18 | { 19 | return this.Error.ToString(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/IRpcClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Sandwych.JsonRpc 7 | { 8 | public interface IRpcClient 9 | { 10 | Task InvokeFuncWithErrorDataAsync(Uri path, string method, object args); 11 | Task InvokeFuncAsync(Uri path, string method, object args); 12 | Task InvokeActionAsync(Uri path, string method, object args); 13 | Task InvokeActionWithErrorDataAsync(Uri path, string method, object args); 14 | Task InvokeNotificationAsync(Uri endpoint, string method, object args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/JsonRpcMethodAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.JsonRpc 6 | { 7 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 8 | public sealed class JsonRpcMethodAttribute : Attribute, IJsonRpcMethodInfo 9 | { 10 | public JsonRpcMethodAttribute(string name, bool isNotification = false) 11 | { 12 | this.Name = name; 13 | this.IsNotification = isNotification; 14 | } 15 | 16 | public string Name { get; set; } 17 | 18 | public Type ErrorDataType { get; set; } 19 | 20 | public bool IsNotification { get; set; } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc.Test/Sandwych.JsonRpc.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp1.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/JsonRpcServiceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sandwych.JsonRpc 6 | { 7 | [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)] 8 | public sealed class JsonRpcServiceAttribute : Attribute, IJsonRpcServiceInfo 9 | { 10 | public JsonRpcServiceAttribute(string endpoint) 11 | { 12 | if (string.IsNullOrEmpty(endpoint)) 13 | { 14 | throw new ArgumentNullException(nameof(endpoint)); 15 | } 16 | 17 | this.Endpoint = new Uri(endpoint, UriKind.Relative); 18 | } 19 | 20 | public JsonRpcServiceAttribute(Uri endpoint) 21 | { 22 | this.Endpoint = endpoint; 23 | } 24 | 25 | public Uri Endpoint { get; private set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc.Test/JsonRpcClientTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using System.Net.Http; 6 | using Xunit; 7 | 8 | namespace Sandwych.JsonRpc.Test 9 | { 10 | 11 | 12 | public class JsonRpcClientTests 13 | { 14 | private readonly static HttpClient _client = new HttpClient 15 | { 16 | BaseAddress = new Uri("http://www.raboof.com") 17 | }; 18 | 19 | 20 | [Fact] 21 | public async Task InvokeEchoShouldBeOkAsync() 22 | { 23 | var settings = new Settings(); 24 | 25 | var client = new JsonRpcClient(_client, settings); 26 | 27 | var echoResult = await client.InvokeFuncAsync(new Uri("/projects/jayrock/demo.ashx", UriKind.Relative), "echo", new[] { "hello" }); 28 | Assert.Equal("hello", echoResult); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Newtonsoft.Json; 5 | 6 | namespace Sandwych.JsonRpc 7 | { 8 | public sealed class Settings 9 | { 10 | public Settings() 11 | { 12 | this.RequestEncoding = Encoding.UTF8; 13 | 14 | this.JsonSerializerSettings = new JsonSerializerSettings 15 | { 16 | Formatting = Formatting.None, 17 | }; 18 | 19 | this.JsonMediaType = JsonRpcProtocolDefinition.JsonMediaType; 20 | 21 | this.GenerateRequestIdMethod = HandleGenerateRequestId; 22 | } 23 | 24 | public Encoding RequestEncoding { get; set; } 25 | 26 | public JsonSerializerSettings JsonSerializerSettings { get; private set; } 27 | 28 | public string JsonMediaType { get; set; } 29 | 30 | public Func GenerateRequestIdMethod { get; set; } 31 | 32 | private static string HandleGenerateRequestId() 33 | { 34 | return Guid.NewGuid().ToString("N"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc.Test/RpcProxyFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Xunit; 4 | using System.Threading.Tasks; 5 | using System.Net.Http; 6 | 7 | namespace Sandwych.JsonRpc.Test 8 | { 9 | 10 | public class RpcProxyFactoryTests 11 | { 12 | private readonly static HttpClient _client = new HttpClient 13 | { 14 | BaseAddress = new Uri("http://www.raboof.com") 15 | }; 16 | 17 | [Fact] 18 | public async Task EchoAsyncShouldBeOkAsync() 19 | { 20 | var settings = new Settings(); 21 | var service = RpcProxyFactory.CreateProxyInstance(_client, settings); 22 | 23 | var echoResult = await service.EchoAsync("hello"); 24 | 25 | Assert.Equal("hello", echoResult); 26 | } 27 | 28 | 29 | [Fact] 30 | public async Task ArrayArgumentShouldBeOkAsync() 31 | { 32 | var settings = new Settings(); 33 | var service = RpcProxyFactory.CreateProxyInstance(_client, settings); 34 | 35 | var totalResult = await service.TotalAsync(new[] { 1, 2, 3, 4, 5 }); 36 | 37 | Assert.Equal(15, totalResult); 38 | } 39 | 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/JsonRpcError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | using Newtonsoft.Json; 7 | 8 | namespace Sandwych.JsonRpc 9 | { 10 | 11 | [JsonObject("error"), Serializable] 12 | public sealed class JsonRpcError 13 | { 14 | public JsonRpcError() 15 | { 16 | } 17 | 18 | public JsonRpcError(string code, string message, T data) 19 | { 20 | if (string.IsNullOrEmpty(code)) 21 | { 22 | throw new ArgumentNullException("code"); 23 | } 24 | 25 | if (string.IsNullOrEmpty(message)) 26 | { 27 | throw new ArgumentNullException("message"); 28 | } 29 | 30 | this.Code = code; 31 | this.Message = message; 32 | this.Data = data; 33 | } 34 | 35 | [JsonProperty("data", Required = Required.Default)] 36 | public T Data { get; private set; } 37 | 38 | [JsonProperty("code", Required = Required.Always)] 39 | public string Code { get; private set; } 40 | 41 | [JsonProperty("message", Required = Required.Always)] 42 | public string Message { get; private set; } 43 | 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/JsonRpcResponse.cs: -------------------------------------------------------------------------------- 1 | /* JSON-RPC Portable Library For .NET 4.5+ 2 | * Copyright (C) 2016 By Wei Li 3 | * Changelog: 4 | * 1.0: A init impl. 5 | * 6 | * */ 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.IO; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | 15 | using Newtonsoft.Json; 16 | 17 | namespace Sandwych.JsonRpc 18 | { 19 | [JsonObject, Serializable] 20 | public sealed class JsonRpcResponse 21 | { 22 | private JsonRpcResponse() 23 | { 24 | } 25 | 26 | [JsonProperty("jsonrpc", Required = Required.Default)] 27 | public JsonRpcVersion? Version { get; set; } 28 | 29 | [JsonProperty("result", Required = Required.Default)] 30 | public T Result { get; set; } 31 | 32 | [JsonProperty("error", Required = Required.Default)] 33 | public JsonRpcError Error { get; set; } 34 | 35 | [JsonProperty("id", Required = Required.Always)] 36 | public string Id { get; set; } 37 | 38 | public static JsonRpcResponse FromJson(string json) 39 | { 40 | return FromJson(json, JsonConvert.DefaultSettings()); 41 | } 42 | 43 | public static JsonRpcResponse FromJson(string json, JsonSerializerSettings settings) 44 | { 45 | return JsonConvert.DeserializeObject>(json, settings); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/JsonRpcRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.IO; 4 | using System.Text; 5 | using System.Diagnostics; 6 | using System.Collections.Generic; 7 | using System.Threading.Tasks; 8 | 9 | using Newtonsoft.Json; 10 | 11 | using Sandwych.JsonRpc; 12 | 13 | namespace Sandwych.JsonRpc 14 | { 15 | 16 | [JsonObject, Serializable] 17 | public sealed class JsonRpcRequest 18 | { 19 | private readonly Settings _settings; 20 | 21 | public JsonRpcRequest(Settings settings, string method, object args) 22 | { 23 | if (string.IsNullOrEmpty(method)) 24 | { 25 | throw new ArgumentNullException(nameof(method)); 26 | } 27 | 28 | _settings = settings ?? throw new ArgumentNullException(nameof(settings)); 29 | 30 | this.Method = method; 31 | this.Params = args; 32 | this.Id = _settings.GenerateRequestIdMethod(); 33 | } 34 | 35 | [JsonProperty("method")] 36 | public string Method { get; private set; } 37 | 38 | [JsonProperty("params")] 39 | public object Params { get; private set; } 40 | 41 | [JsonProperty("id")] 42 | public string Id { get; private set; } 43 | 44 | [JsonProperty("jsonrpc")] 45 | public JsonRpcVersion? Version => JsonRpcVersion.Version2; 46 | 47 | public string ToJson() 48 | { 49 | return JsonConvert.SerializeObject(this, _settings.JsonSerializerSettings); 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/Sandwych.JsonRpc.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | net45;netstandard1.3 7 | True 8 | True 9 | A client side library for JSON-RPC 2.0 protocol. 10 | Wei Li @ BinaryStars Technologies 11 | BinaryStars Technologies Yunnan LLC. 12 | Copyright (C) 2017-TODAY BinaryStars Technologies Yunnan LLC. 13 | https://opensource.org/licenses/BSD-3-Clause 14 | https://github.com/oldrev/jsonrpc 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /SandwychJsonRpc.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandwych.JsonRpc", "src\Sandwych.JsonRpc\Sandwych.JsonRpc.csproj", "{4BB5841E-2292-4C70-9015-D9914240D51A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandwych.JsonRpc.Test", "src\Sandwych.JsonRpc.Test\Sandwych.JsonRpc.Test.csproj", "{10169F7D-6E1F-4031-9345-0DD0091D0A7B}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {4BB5841E-2292-4C70-9015-D9914240D51A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {4BB5841E-2292-4C70-9015-D9914240D51A}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {4BB5841E-2292-4C70-9015-D9914240D51A}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {4BB5841E-2292-4C70-9015-D9914240D51A}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {10169F7D-6E1F-4031-9345-0DD0091D0A7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {10169F7D-6E1F-4031-9345-0DD0091D0A7B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {10169F7D-6E1F-4031-9345-0DD0091D0A7B}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {10169F7D-6E1F-4031-9345-0DD0091D0A7B}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/RpcProxyFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Net.Http; 5 | using System.Reflection; 6 | using Castle.DynamicProxy; 7 | 8 | namespace Sandwych.JsonRpc 9 | { 10 | public static class RpcProxyFactory 11 | { 12 | public static T CreateProxyInstance(HttpClient client, Settings settings) where T : class 13 | { 14 | return CreateProxyInstance(client, typeof(T), settings) as T; 15 | } 16 | 17 | public static object CreateProxyInstance(HttpClient client, Type rpcServiceInterfaceType, Settings settings) 18 | { 19 | if (rpcServiceInterfaceType == null) 20 | { 21 | throw new ArgumentNullException(nameof(rpcServiceInterfaceType)); 22 | } 23 | 24 | var ti = rpcServiceInterfaceType.GetTypeInfo(); 25 | 26 | if (!ti.IsInterface) 27 | { 28 | throw new NotSupportedException(); 29 | } 30 | 31 | var serviceInfo = ti.GetCustomAttribute() as IJsonRpcServiceInfo; 32 | if (serviceInfo == null) 33 | { 34 | throw new InvalidOperationException(); 35 | } 36 | 37 | var generator = new ProxyGenerator(); 38 | var rpcClient = new JsonRpcClient(client, settings); 39 | var interceptor = new RpcMethodInterceptor(rpcClient, rpcServiceInterfaceType, serviceInfo); 40 | return generator.CreateInterfaceProxyWithoutTarget(rpcServiceInterfaceType, interceptor); 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sandwych.JsonRpc: A JSON-RPC Client For .NET 2 | 3 | [![NuGet Stats](https://img.shields.io/nuget/v/Sandwych.JsonRpc.svg)](https://www.nuget.org/packages/Sandwych.JsonRpc) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/smxfipheeacvlvyb/branch/master?svg=true)](https://ci.appveyor.com/project/oldrev/jsonrpc/branch/master) 5 | 6 | Sandwych.JsonRpc is a JSON-RPC protocol client for .NET. 7 | 8 | WARNING: This library should be considered *PRE-BETA* software - the API can and will change frequently, do not use it for production. 9 | 10 | ## Getting Started 11 | 12 | ### Prerequisites 13 | 14 | * Visual Studio 2017: This project is written in C# 7.0 using Microsoft Visual Studio 2017 Community Edition. 15 | 16 | ### Supported Platform 17 | 18 | * .NET Framework 4.5+ 19 | * .NET Standard 1.3+ (including .NET Core, Xamarin and others) 20 | 21 | 22 | ### Installation 23 | 24 | Sandwych.JsonRpc can be installed from [NuGet](https://www.nuget.org/packages/Sandwych.JsonRpc) 25 | 26 | or type following commands in the NuGet Console: 27 | 28 | ``` 29 | PM> Install-Package Sandwych.JsonRpc 30 | ``` 31 | 32 | ## Demo & Usage: 33 | 34 | ### Step 1: Define your JSON-RPC interface: 35 | 36 | ```csharp 37 | [JsonRpcService("/projects/jayrock/demo.ashx")] 38 | public interface ITestJsonRpcService 39 | { 40 | [JsonRpcMethod("echo")] 41 | Task EchoAsync(string text); 42 | 43 | [JsonRpcMethod("total")] 44 | Task TotalAsync(IEnumerable values); 45 | } 46 | ``` 47 | 48 | ### Step 2: Call your JSON-RPC using a dynamic proxy approach: 49 | 50 | ```csharp 51 | var settings = new Settings(); //Use default settings 52 | var client = new HttpClient { BaseAddress = new Uri("http://www.raboof.com") }; 53 | var service = RpcProxyFactory.CreateProxyInstance(client, settings); //Create a dynamic proxy for the interface ITestJsonRpcService 54 | var echoResult = await service.EchoAsync("hello"); 55 | Console.WriteLine(echoResult); //Should print "hello" 56 | ``` 57 | 58 | And done! 59 | 60 | 61 | # License 62 | 63 | Author: Wei Li 64 | 65 | This software is licensed in 3-clause BSD License. 66 | 67 | Copyright (C) 2017-TODAY BinaryStars Technologies Yunnan LLC. 68 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/RpcMethodInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using Castle.DynamicProxy; 7 | using System.Reflection; 8 | 9 | namespace Sandwych.JsonRpc 10 | { 11 | 12 | /// 13 | /// The interceptor for user's service interface 14 | /// 15 | internal class RpcMethodInterceptor : IInterceptor 16 | { 17 | private static readonly MethodInfo _handleAsyncMethodInfo = 18 | typeof(RpcMethodInterceptor) 19 | .GetMethod(nameof(RpcMethodInterceptor.HandleAsyncWithResult), BindingFlags.Static | BindingFlags.NonPublic); 20 | 21 | private static readonly MethodInfo _invokeAsyncGenericMethodInfo = 22 | typeof(IRpcClient) 23 | .GetMethods(BindingFlags.Instance | BindingFlags.Public) 24 | .Single(mi => mi.Name == nameof(IRpcClient.InvokeFuncAsync) && mi.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)); 25 | 26 | private readonly IRpcClient _rpcClient; 27 | private readonly IJsonRpcServiceInfo _serviceInfo; 28 | 29 | public RpcMethodInterceptor(IRpcClient rpcClient, Type interfaceType, IJsonRpcServiceInfo serviceInfo) 30 | { 31 | _rpcClient = rpcClient; 32 | _serviceInfo = serviceInfo; 33 | } 34 | 35 | public void Intercept(IInvocation invocation) 36 | { 37 | var jsonRpcMethodInfo = invocation.Method.GetCustomAttribute() as IJsonRpcMethodInfo; 38 | if (jsonRpcMethodInfo == null) 39 | { 40 | throw new InvalidOperationException(); 41 | } 42 | 43 | var delegateType = this.GetDelegateType(invocation); 44 | switch (delegateType) 45 | { 46 | case MethodAsyncType.AsyncFunction: 47 | if (jsonRpcMethodInfo.IsNotification) 48 | { 49 | throw new NotSupportedException(); 50 | } 51 | var resultType = invocation.Method.ReturnType.GetGenericArguments()[0]; 52 | var invokeGenericAsync = _invokeAsyncGenericMethodInfo.MakeGenericMethod(resultType); 53 | var genericTask = invokeGenericAsync.Invoke(_rpcClient, new object[] { _serviceInfo.Endpoint, jsonRpcMethodInfo.Name, invocation.Arguments }); 54 | var asyncMi = _handleAsyncMethodInfo.MakeGenericMethod(resultType); 55 | invocation.ReturnValue = asyncMi.Invoke(this, new[] { genericTask }); 56 | break; 57 | 58 | case MethodAsyncType.AsyncAction: 59 | if (jsonRpcMethodInfo.IsNotification) 60 | { 61 | var task = _rpcClient.InvokeNotificationAsync(_serviceInfo.Endpoint, jsonRpcMethodInfo.Name, invocation.Arguments); 62 | invocation.ReturnValue = HandleAsync(task); 63 | } 64 | else 65 | { 66 | 67 | var task = _rpcClient.InvokeActionAsync(_serviceInfo.Endpoint, jsonRpcMethodInfo.Name, invocation.Arguments); 68 | invocation.ReturnValue = HandleAsync(task); 69 | } 70 | break; 71 | 72 | default: 73 | throw new NotSupportedException(); 74 | } 75 | 76 | } 77 | 78 | private static async Task HandleAsync(Task task) 79 | { 80 | await task; 81 | } 82 | 83 | private static async Task HandleAsyncWithResult(Task task) 84 | { 85 | return await task; 86 | } 87 | 88 | private MethodAsyncType GetDelegateType(IInvocation invocation) 89 | { 90 | var returnType = invocation.Method.ReturnType; 91 | if (returnType == typeof(Task)) 92 | { 93 | return MethodAsyncType.AsyncAction; 94 | } 95 | 96 | if (returnType.IsConstructedGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)) 97 | { 98 | return MethodAsyncType.AsyncFunction; 99 | } 100 | 101 | return MethodAsyncType.Synchronous; 102 | } 103 | 104 | private enum MethodAsyncType 105 | { 106 | Synchronous, 107 | AsyncAction, 108 | AsyncFunction 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/JsonRpcClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Dynamic; 5 | using System.Text; 6 | using System.IO; 7 | using System.Threading.Tasks; 8 | using System.Net.Http; 9 | using System.Collections.Concurrent; 10 | 11 | using Newtonsoft.Json; 12 | using Newtonsoft.Json.Serialization; 13 | 14 | namespace Sandwych.JsonRpc 15 | { 16 | public class JsonRpcClient : IRpcClient 17 | { 18 | private readonly HttpClient _client; 19 | private readonly Settings _settings; 20 | 21 | public JsonRpcClient(HttpClient client, Settings settings) 22 | { 23 | _client = client ?? throw new ArgumentNullException(nameof(client)); 24 | _settings = settings ?? throw new ArgumentNullException(nameof(settings)); 25 | } 26 | 27 | public async Task InvokeFuncWithErrorDataAsync(Uri endpoint, string method, object args) 28 | { 29 | var jreq = new JsonRpcRequest(_settings, method, args); 30 | var response = await this.PostJsonRequestForCallingAsync(jreq, endpoint); 31 | return response.Result; 32 | } 33 | 34 | public async Task InvokeActionAsync(Uri endpoint, string method, object args) 35 | { 36 | var jreq = new JsonRpcRequest(_settings, method, args); 37 | var response = await this.PostJsonRequestForCallingAsync(jreq, endpoint); 38 | } 39 | 40 | public async Task InvokeActionWithErrorDataAsync(Uri endpoint, string method, object args) 41 | { 42 | var jreq = new JsonRpcRequest(_settings, method, args); 43 | var response = await this.PostJsonRequestForCallingAsync(jreq, endpoint); 44 | } 45 | 46 | public async Task InvokeFuncAsync(Uri endpoint, string method, object args) 47 | { 48 | return await this.InvokeFuncWithErrorDataAsync(endpoint, method, args); 49 | } 50 | 51 | public async Task InvokeNotificationAsync(Uri endpoint, string method, object args) 52 | { 53 | var jreq = new JsonRpcRequest(_settings, method, args); 54 | await this.PostJsonRequestForNotifyAsync(jreq, endpoint); 55 | } 56 | 57 | private async Task> PostJsonRequestForCallingAsync(JsonRpcRequest request, Uri endpoint) 58 | { 59 | if (endpoint == null) 60 | { 61 | throw new ArgumentNullException(nameof(endpoint)); 62 | } 63 | 64 | if (request == null) 65 | { 66 | throw new ArgumentNullException(nameof(request)); 67 | } 68 | 69 | if (string.IsNullOrEmpty(request.Id)) 70 | { 71 | throw new ArgumentOutOfRangeException(nameof(request)); 72 | } 73 | 74 | var requestJson = request.ToJson(); 75 | using (var requestContent = new StringContent(requestJson, _settings.RequestEncoding, _settings.JsonMediaType)) 76 | using (var httpResponse = await _client.PostAsync(endpoint, requestContent)) 77 | { 78 | httpResponse.EnsureSuccessStatusCode(); 79 | string responseJson = await httpResponse.Content.ReadAsStringAsync(); 80 | var rpcResponse = JsonRpcResponse.FromJson(responseJson, _settings.JsonSerializerSettings); 81 | 82 | if (string.IsNullOrEmpty(rpcResponse.Id) || request.Id != rpcResponse.Id) 83 | { 84 | throw new InvalidOperationException("Different request-response IDs"); 85 | } 86 | 87 | if (rpcResponse.Error != null) 88 | { 89 | throw new JsonRpcException("Server side error", rpcResponse.Error); 90 | } 91 | return rpcResponse; 92 | } 93 | } 94 | 95 | private async Task PostJsonRequestForNotifyAsync(JsonRpcRequest request, Uri endpoint) 96 | { 97 | if (endpoint == null) 98 | { 99 | throw new ArgumentNullException(nameof(endpoint)); 100 | } 101 | 102 | if (request == null) 103 | { 104 | throw new ArgumentNullException(nameof(request)); 105 | } 106 | 107 | var requestJson = request.ToJson(); 108 | using (var requestContent = new StringContent(requestJson, _settings.RequestEncoding, _settings.JsonMediaType)) 109 | using (var httpResponse = await _client.PostAsync(endpoint, requestContent)) 110 | { 111 | httpResponse.EnsureSuccessStatusCode(); 112 | } 113 | } 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Sandwych.JsonRpc/PlainJsonConvert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.IO; 6 | using System.Diagnostics; 7 | 8 | using Newtonsoft.Json; 9 | 10 | namespace Sandwych.JsonRpc 11 | { 12 | /// 13 | /// 一个很重要的类 14 | /// JSON 中的对象如果不指定 CLR 类型的话,Json.NET 类库会把它解析成 JObject。 15 | /// 而此类会解析成 Dictionary 16 | /// 17 | public static class PlainJsonConvert 18 | { 19 | public static string Generate(object value) 20 | { 21 | var str = Newtonsoft.Json.JsonConvert.SerializeObject(value, Formatting.None); 22 | return str; 23 | } 24 | 25 | public static object Parse(string json) 26 | { 27 | if (string.IsNullOrEmpty(json)) 28 | { 29 | throw new ArgumentNullException("json"); 30 | } 31 | 32 | using (var ss = new StringReader(json)) 33 | { 34 | return Parse(ss); 35 | } 36 | } 37 | 38 | public static object Parse(TextReader reader) 39 | { 40 | if (reader == null) 41 | { 42 | throw new ArgumentNullException("reader"); 43 | } 44 | 45 | using (var jreader = new JsonTextReader(reader)) 46 | { 47 | return ParseInternal(jreader); 48 | } 49 | } 50 | 51 | public static object Parse(Stream ins) 52 | { 53 | if (ins == null) 54 | { 55 | throw new ArgumentNullException("ins"); 56 | } 57 | 58 | using (var tr = new StreamReader(ins, Encoding.UTF8)) 59 | { 60 | return Parse(tr); 61 | } 62 | } 63 | 64 | public static object Parse(byte[] utf8Buffer) 65 | { 66 | if (utf8Buffer == null) 67 | { 68 | throw new ArgumentNullException("utf8Buffer"); 69 | } 70 | 71 | using (var ms = new MemoryStream(utf8Buffer, false)) 72 | { 73 | return Parse(ms); 74 | } 75 | } 76 | 77 | private static object ParseInternal(JsonReader reader) 78 | { 79 | Debug.Assert(reader != null); 80 | 81 | reader.Read(); 82 | return ReadToken(reader); 83 | } 84 | 85 | private static object ReadToken(JsonReader reader) 86 | { 87 | Debug.Assert(reader != null); 88 | 89 | object result = null; 90 | 91 | switch (reader.TokenType) 92 | { 93 | //跳过注释 94 | case JsonToken.Comment: 95 | SkipComment(reader); 96 | break; 97 | 98 | case JsonToken.StartObject: 99 | result = ReadObject(reader); 100 | break; 101 | 102 | case JsonToken.StartArray: 103 | result = ReadArray(reader); 104 | break; 105 | 106 | //标量 107 | case JsonToken.Boolean: 108 | case JsonToken.Bytes: 109 | case JsonToken.Date: 110 | case JsonToken.Float: 111 | case JsonToken.Integer: 112 | case JsonToken.String: 113 | result = reader.Value; 114 | break; 115 | 116 | case JsonToken.Null: 117 | result = null; 118 | break; 119 | 120 | case JsonToken.Undefined: 121 | case JsonToken.None: 122 | default: 123 | throw new NotSupportedException( 124 | "Unsupported JSON token type: " + reader.TokenType.ToString()); 125 | } 126 | 127 | return result; 128 | } 129 | 130 | private static void SkipComment(JsonReader reader) 131 | { 132 | Debug.Assert(reader != null); 133 | 134 | while (reader.Read() && reader.TokenType != JsonToken.Comment) 135 | { 136 | //do nothing 137 | } 138 | } 139 | 140 | 141 | private static Dictionary ReadObject(JsonReader reader) 142 | { 143 | Debug.Assert(reader != null); 144 | 145 | Dictionary propBag = new Dictionary(); 146 | 147 | while (reader.Read() && reader.TokenType != JsonToken.EndObject) 148 | { 149 | if (reader.TokenType == JsonToken.PropertyName) 150 | { 151 | var key = (string)reader.Value; 152 | reader.Read(); 153 | object e = ReadToken(reader); 154 | propBag[key] = e; 155 | continue; 156 | } 157 | } 158 | 159 | return propBag; 160 | } 161 | 162 | private static object[] ReadArray(JsonReader reader) 163 | { 164 | Debug.Assert(reader != null); 165 | 166 | var list = new List(); 167 | 168 | while (reader.Read() && reader.TokenType != JsonToken.EndArray) 169 | { 170 | object e = ReadToken(reader); 171 | list.Add(e); 172 | } 173 | 174 | return list.ToArray(); 175 | } 176 | 177 | } 178 | } 179 | --------------------------------------------------------------------------------