├── test
├── EdjCase.JsonRpc.Client.Sample
│ ├── runtimeconfig.template.json
│ ├── EdjCase.JsonRpc.Client.Sample.csproj
│ └── Program.cs
├── EdjCase.JsonRpc.Router.Sample
│ ├── runtimeconfig.template.json
│ ├── Properties
│ │ └── launchSettings.json
│ ├── web.config
│ ├── wwwroot
│ │ └── web.config
│ ├── Controllers
│ │ ├── ApiController.cs
│ │ └── ComplexExampleController.cs
│ ├── EdjCase.JsonRpc.Router.Sample.csproj
│ ├── RpcControllers.cs
│ └── Startup.cs
├── EdjCase.JsonRpc.Router.Tests
│ ├── Controllers
│ │ ├── MethodMatcherThreeController.cs
│ │ ├── MethodMatcherDuplicatesController.cs
│ │ └── MethodMatcherController.cs
│ ├── FakeLogger.cs
│ ├── EdjCase.JsonRpc.Router.Tests.csproj
│ ├── RpcUtilTests.cs
│ ├── SerializerTests.cs
│ ├── RpcParameterConverterTests.cs
│ └── RequestSignatureTests.cs
├── Benchmarks
│ ├── Benchmarks.csproj
│ ├── BasicTests.websurge
│ └── Program.cs
└── EdjCase.JsonRpc.Client.Tests
│ ├── EdjCase.JsonRpc.Client.Tests.csproj
│ └── HttpTransportClientTests.cs
├── .vscode
├── settings.json
├── tasks.json
└── launch.json
├── .travis.yml
├── src
├── EdjCase.JsonRpc.Client
│ ├── Publish.cmd
│ ├── Constants.cs
│ ├── DefaultHttpClientFactory.cs
│ ├── IHttpAuthHeaderFactory.cs
│ ├── RpcBulkResponse.cs
│ ├── EdjCase.JsonRpc.Client.csproj
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── RpcEvents.cs
│ ├── RpcBulkRequest.cs
│ ├── RpcResponse.cs
│ ├── RpcParameters.cs
│ ├── RpcClientExceptions.cs
│ ├── RpcRequest.cs
│ ├── HttpClientBuilder.cs
│ ├── RpcTransportClients.cs
│ └── DefaultRequestSerializer.cs
├── EdjCase.JsonRpc.Router
│ ├── Publish.cmd
│ ├── Utilities
│ │ ├── StreamUtil.cs
│ │ ├── ExtensionsUtil.cs
│ │ ├── RpcUtil.cs
│ │ └── JsonStringGeneratorUtil.cs
│ ├── Abstractions
│ │ ├── IRpcParameterConverter.cs
│ │ ├── IRpcAuthorizationHandler.cs
│ │ ├── IRpcRequestMatcher.cs
│ │ ├── IRpcContext.cs
│ │ ├── IRpcMethodResult.cs
│ │ ├── IRpcInvoker.cs
│ │ ├── IRpcParser.cs
│ │ ├── IRpcResponseSerializer.cs
│ │ ├── IRpcMethodProvider.cs
│ │ └── IRpcRequestHandler.cs
│ ├── .editorconfig
│ ├── StaticRpcMethodProvider.cs
│ ├── Defaults
│ │ ├── DefaultRpcContext.cs
│ │ ├── DefaultRpcMethodResults.cs
│ │ ├── DefaultAuthorizationHandler.cs
│ │ └── DefaultRpcResponseSerializer.cs
│ ├── RpcRouterExceptions.cs
│ ├── RpcResponse.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── EdjCase.JsonRpc.Router.csproj
│ ├── RpcRequest.cs
│ ├── RpcController.cs
│ ├── RpcNumber.cs
│ ├── ParsingResult.cs
│ ├── IRpcParameter.cs
│ ├── RpcEndpointBuilder.cs
│ ├── RpcMethodInfo.cs
│ ├── Configuration.cs
│ ├── RpcHttpRouter.cs
│ ├── RpcRequestHandler.cs
│ ├── RpcPath.cs
│ └── BuilderExtensions.cs
├── EdjCase.JsonRpc.Router.Swagger
│ ├── Publish.cmd
│ ├── Models
│ │ ├── SwaggerConfiguration.cs
│ │ └── RpcRouteMethodInfo.cs
│ ├── EdjCase.JsonRpc.Router.Swagger.csproj
│ ├── BuilderExtensions.cs
│ └── IXmlDocumentationService.cs
└── EdjCase.JsonRpc.Common
│ ├── Constants.cs
│ ├── EdjCase.JsonRpc.Common.projitems
│ ├── Utilities
│ └── ExtensionsUtil.cs
│ ├── EdjCase.JsonRpc.Common.shproj
│ ├── Tools
│ └── StreamCompressors.cs
│ ├── RpcExceptions.cs
│ ├── RpcError.cs
│ └── RpcId.cs
├── Nuget.config
├── .editorconfig
├── publish.sh
├── Publish.cmd
├── .github
└── workflows
│ ├── build_and_test.yml
│ └── release.yml
├── LICENSE
├── .gitignore
└── EdjCase.JsonRpc.sln
/test/EdjCase.JsonRpc.Client.Sample/runtimeconfig.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "gcServer": true
3 | }
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Sample/runtimeconfig.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "gcServer": true
3 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.insertSpaces": false,
3 | "dotnet-test-explorer.testProjectPath": "**/*Tests.@(csproj|vbproj|fsproj)"
4 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | solution: EdjCase.JsonRpc.sln
3 | mono: none
4 | dotnet: 3.1.1
5 | script:
6 | - chmod a+x ./publish.sh
7 | - ./publish.sh
8 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/Publish.cmd:
--------------------------------------------------------------------------------
1 | SET configuration=Release
2 | SET out=C:\Publish
3 |
4 | call dotnet pack -c %configuration% -o "%out%/EdjCase.JsonRpc.Client"
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Publish.cmd:
--------------------------------------------------------------------------------
1 | SET configuration=Release
2 | SET out=C:\Publish
3 |
4 | call dotnet pack -c %configuration% -o "%out%/EdjCase.JsonRpc.Router"
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router.Swagger/Publish.cmd:
--------------------------------------------------------------------------------
1 | SET configuration=Release
2 | SET out=C:\Publish
3 |
4 | call dotnet pack -c %configuration% -o "%out%/EdjCase.JsonRpc.Router.Swagger"
--------------------------------------------------------------------------------
/Nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 |
5 | [*.{cs,razor}]
6 | dotnet_style_qualification_for_property = true:error
7 | dotnet_style_qualification_for_field = true:error
8 | dotnet_style_qualification_for_event = true:error
9 | dotnet_style_qualification_for_method = true:error
10 | indent_style = tab
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Tests/Controllers/MethodMatcherThreeController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EdjCase.JsonRpc.Router.Tests.Controllers
4 | {
5 | public class MethodMatcherThreeController
6 | {
7 | public Guid GuidTypeMethod(Guid guid)
8 | {
9 | return guid;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Tests/Controllers/MethodMatcherDuplicatesController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EdjCase.JsonRpc.Router.Tests.Controllers
4 | {
5 | public class MethodMatcherDuplicatesController
6 | {
7 | public Guid GuidTypeMethod(Guid guid)
8 | {
9 | return guid;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router.Swagger/Models/SwaggerConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 |
3 | namespace EdjCase.JsonRpc.Router.Swagger.Models
4 | {
5 | public class SwaggerConfiguration
6 | {
7 | public string[] Endpoints { get; set; } = new string[0];
8 | public JsonNamingPolicy NamingPolicy { get; set; } = JsonNamingPolicy.CamelCase;
9 | }
10 | }
--------------------------------------------------------------------------------
/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | dotnet test ./test/EdjCase.JsonRpc.Router.Tests -c Release
4 |
5 | dotnet pack ./src/EdjCase.JsonRpc.Router -c Release -o "./out/EdjCase.JsonRpc.Router"
6 |
7 | dotnet pack ./src/EdjCase.JsonRpc.Router.Swagger -c Release -o "./out/EdjCase.JsonRpc.Router.Swagger"
8 |
9 | dotnet pack ./src/EdjCase.JsonRpc.Client -c Release -o "out/EdjCase.JsonRpc.Client"
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Sample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Web": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "launchUrl": "http://localhost:5000/swagger/index.html",
7 | "environmentVariables": {
8 | "ASPNETCORE_URLS": "http://localhost:5000",
9 | "ASPNETCORE_ENVIRONMENT": "Development"
10 | }
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Sample/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/Constants.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace EdjCase.JsonRpc.Client
7 | {
8 | public static class Defaults
9 | {
10 | public const string ContentType = "application/json";
11 | public static readonly Encoding Encoding = Encoding.UTF8;
12 | public static List<(string, string)> GetHeaders() => new List<(string, string)> { ("Accept-Encoding", "gzip, deflate") };
13 | }
14 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router.Swagger/Models/RpcRouteMethodInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using EdjCase.JsonRpc.Router.Abstractions;
3 |
4 | namespace EdjCase.JsonRpc.Router.Swagger.Models
5 | {
6 | public class UniqueMethod
7 | {
8 | public string UniqueUrl { get; }
9 | public IRpcMethodInfo Info { get; }
10 |
11 | public UniqueMethod(string uniqueUrl, IRpcMethodInfo info)
12 | {
13 | this.UniqueUrl = uniqueUrl;
14 | this.Info = info;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Utilities/StreamUtil.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.IO;
3 | using System.Text;
4 |
5 | namespace EdjCase.JsonRpc.Router.Utilities
6 | {
7 | internal class StreamUtil
8 | {
9 | internal static MemoryStream GetStreamFromUtf8String(string utf8Text)
10 | {
11 | byte[] bytes = ArrayPool.Shared.Rent(Encoding.UTF8.GetByteCount(utf8Text));
12 | int byteCount = Encoding.UTF8.GetBytes(utf8Text, 0, utf8Text.Length, bytes, 0);
13 | return new MemoryStream(bytes, 0, byteCount);
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/Publish.cmd:
--------------------------------------------------------------------------------
1 | SET configuration=Release
2 | SET out=C:/Publish
3 |
4 | call dotnet test ./test/EdjCase.JsonRpc.Router.Tests -c %configuration%
5 |
6 | call dotnet pack ./src/EdjCase.JsonRpc.Router -c %configuration% -o "%out%/EdjCase.JsonRpc.Router"
7 |
8 | call dotnet pack ./src/EdjCase.JsonRpc.Router.Swagger -c %configuration% -o "%out%/EdjCase.JsonRpc.Router.Swagger"
9 |
10 | call dotnet test ./test/EdjCase.JsonRpc.Client.Tests -c %configuration%
11 |
12 | call dotnet pack ./src/EdjCase.JsonRpc.Client -c %configuration% -o "%out%/EdjCase.JsonRpc.Client"
13 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Abstractions/IRpcParameterConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace EdjCase.JsonRpc.Router.Abstractions
6 | {
7 | public interface IRpcParameterConverter
8 | {
9 | bool AreTypesCompatible(RpcParameterType sourceType, RpcParameterType destinationType);
10 | bool TryConvertValue(RpcParameter sourceValue, RpcParameterType destinationType, Type destinationRawType, out object? destinationValue, out Exception? exception);
11 | RpcParameterType GetRpcParameterType(Type type);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/Benchmarks/Benchmarks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net6.0
5 | enable
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Abstractions/IRpcAuthorizationHandler.cs:
--------------------------------------------------------------------------------
1 | using EdjCase.JsonRpc.Router.Utilities;
2 | using Microsoft.AspNetCore.Authorization;
3 | using Microsoft.Extensions.Logging;
4 | using System;
5 | using System.Collections.Concurrent;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Reflection;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace EdjCase.JsonRpc.Router.Abstractions
13 | {
14 | internal interface IRpcAuthorizationHandler
15 | {
16 | Task IsAuthorizedAsync(IRpcMethodInfo methodInfo);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # CS8625: Cannot convert null literal to non-nullable reference type.
4 | dotnet_diagnostic.CS8625.severity = error
5 |
6 | # CS8618: Non-nullable field is uninitialized. Consider declaring as nullable.
7 | dotnet_diagnostic.CS8618.severity = error
8 |
9 | # CS8604: Possible null reference argument.
10 | dotnet_diagnostic.CS8604.severity = error
11 |
12 | # CS8600: Converting null literal or possible null value to non-nullable type.
13 | dotnet_diagnostic.CS8600.severity = error
14 |
15 | # CS8603: Possible null reference return.
16 | dotnet_diagnostic.CS8603.severity = error
17 |
--------------------------------------------------------------------------------
/.github/workflows/build_and_test.yml:
--------------------------------------------------------------------------------
1 | name: .NET
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | types:
8 | - opened
9 | - synchronized
10 |
11 | jobs:
12 | build:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Setup .NET
19 | uses: actions/setup-dotnet@v2
20 | with:
21 | dotnet-version: 6.0.x
22 | - name: Restore dependencies
23 | run: dotnet restore
24 | - name: Build
25 | run: dotnet build --no-restore
26 | - name: Test
27 | run: dotnet test --no-build --verbosity normal
28 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Abstractions/IRpcRequestMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 | using EdjCase.JsonRpc.Router;
5 | using EdjCase.JsonRpc.Common;
6 |
7 | namespace EdjCase.JsonRpc.Router.Abstractions
8 | {
9 | internal interface IRpcRequestMatcher
10 | {
11 | ///
12 | /// Finds the matching Rpc method for the current request
13 | ///
14 | /// Current Rpc request
15 | /// The matching Rpc method to the current request
16 | IRpcMethodInfo GetMatchingMethod(RpcRequestSignature requestSignature);
17 | }
18 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Abstractions/IRpcContext.cs:
--------------------------------------------------------------------------------
1 | using EdjCase.JsonRpc.Router;
2 | using EdjCase.JsonRpc.Router.Abstractions;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Reflection;
6 | using System.Security.Claims;
7 |
8 | namespace EdjCase.JsonRpc.Router.Abstractions
9 | {
10 | public class RpcContext
11 | {
12 | public IServiceProvider RequestServices { get; }
13 | public RpcPath? Path { get; }
14 |
15 | public RpcContext(IServiceProvider serviceProvider, RpcPath? path = null)
16 | {
17 | this.RequestServices = serviceProvider;
18 | this.Path = path;
19 | }
20 | }
21 |
22 | internal interface IRpcContextAccessor
23 | {
24 | RpcContext Get();
25 | void Set(RpcContext context);
26 | }
27 | }
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Tests/FakeLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using EdjCase.JsonRpc.Router.Defaults;
3 | using Microsoft.Extensions.Logging;
4 |
5 | namespace EdjCase.JsonRpc.Router.Tests
6 | {
7 | internal class FakeLogger : ILogger
8 | {
9 | public IDisposable? BeginScope(TState state) where TState : notnull
10 | {
11 | return new FakeDisposable();
12 | }
13 |
14 | public bool IsEnabled(LogLevel logLevel)
15 | {
16 | return false;
17 | }
18 |
19 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
20 | {
21 |
22 | }
23 | }
24 |
25 | internal class FakeDisposable : IDisposable
26 | {
27 | public void Dispose()
28 | {
29 |
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Abstractions/IRpcMethodResult.cs:
--------------------------------------------------------------------------------
1 | using EdjCase.JsonRpc.Common;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace EdjCase.JsonRpc.Router.Abstractions
8 | {
9 | ///
10 | /// Rpc method response object that allows the server to customize an rpc response
11 | ///
12 | public interface IRpcMethodResult
13 | {
14 | ///
15 | /// Turns result data into a rpc response
16 | ///
17 | /// Rpc request id
18 | /// Json serializer function to use for objects for the response
19 | /// Rpc response for request
20 | RpcResponse ToRpcResponse(RpcId id);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Sample/wwwroot/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Sample/Controllers/ApiController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace EdjCase.JsonRpc.Router.Sample.Controllers;
4 |
5 | [ApiController]
6 | [Route("[controller]")]
7 | public class ApiController : Microsoft.AspNetCore.Mvc.ControllerBase
8 | {
9 | [HttpGet("{id}")]
10 | public IActionResult GetById(int id)
11 | {
12 | var result = new
13 | {
14 | Id = id,
15 | Description = $"This is an Api call {id}."
16 | };
17 |
18 | return (IActionResult)this.Ok(result);
19 | }
20 | }
21 |
22 | [RpcRoute("NonApi")]
23 | public class NonApiController : RpcController
24 | {
25 |
26 | public object GetById(int id)
27 | {
28 | var result = new
29 | {
30 | Id = id,
31 | Description = $"This is bar {id}."
32 | };
33 |
34 | return result;
35 | }
36 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Abstractions/IRpcInvoker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 |
5 | namespace EdjCase.JsonRpc.Router.Abstractions
6 | {
7 | internal interface IRpcInvoker
8 | {
9 | ///
10 | /// Call the incoming Rpc request method and gives the appropriate response
11 | ///
12 | /// Rpc request
13 | /// An Rpc response for the request
14 | Task InvokeRequestAsync(RpcRequest request);
15 |
16 | ///
17 | /// Call the incoming Rpc requests methods and gives the appropriate respones
18 | ///
19 | /// List of Rpc requests to invoke
20 | /// List of Rpc responses for the requests
21 | Task> InvokeBatchRequestAsync(IList requests);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/StaticRpcMethodProvider.cs:
--------------------------------------------------------------------------------
1 | using EdjCase.JsonRpc.Router.Abstractions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Reflection;
6 |
7 | namespace EdjCase.JsonRpc.Router
8 | {
9 | internal class StaticRpcMethodProvider : IRpcMethodProvider
10 | {
11 | private StaticRpcMethodDataAccessor dataAccessor { get; }
12 |
13 | public StaticRpcMethodProvider(StaticRpcMethodDataAccessor dataAccessor)
14 | {
15 | this.dataAccessor = dataAccessor;
16 | }
17 |
18 | public RpcRouteMetaData Get()
19 | {
20 | if (this.dataAccessor.Value == null)
21 | {
22 | throw new RpcConfigurationException("Rpc routes are not configured.");
23 | }
24 | return this.dataAccessor.Value;
25 | }
26 | }
27 |
28 | internal class StaticRpcMethodDataAccessor
29 | {
30 | public RpcRouteMetaData? Value { get; set; }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Common/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace EdjCase.JsonRpc.Common
2 | {
3 | ///
4 | /// Error codes for different Rpc errors
5 | ///
6 | public enum RpcErrorCode
7 | {
8 | ParseError = -32700,
9 | InvalidRequest = -32600,
10 | MethodNotFound = -32601,
11 | InvalidParams = -32602,
12 | InternalError = -32603
13 | }
14 |
15 | public static class JsonRpcContants
16 | {
17 | public const string VersionPropertyName = "jsonrpc";
18 | public const string MethodPropertyName = "method";
19 | public const string ParamsPropertyName = "params";
20 | public const string IdPropertyName = "id";
21 | public const string ResultPropertyName = "result";
22 | public const string ErrorPropertyName = "error";
23 | public const string ErrorCodePropertyName = "code";
24 | public const string ErrorMessagePropertyName = "message";
25 | public const string ErrorDataPropertyName = "data";
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Client.Tests/EdjCase.JsonRpc.Client.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | false
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 | all
14 | runtime; build; native; contentfiles; analyzers
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Abstractions/IRpcParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Text;
6 | using EdjCase.JsonRpc.Router;
7 | using EdjCase.JsonRpc.Common;
8 | using EdjCase.JsonRpc.Router.Utilities;
9 |
10 | namespace EdjCase.JsonRpc.Router.Abstractions
11 | {
12 | internal interface IRpcParser
13 | {
14 | ///
15 | /// Parses all the requests from the json in the request
16 | ///
17 | /// Json stream to parse from
18 | /// Result of the parsing. Includes all the parsed requests and any errors
19 | ParsingResult ParseRequests(Stream jsonStream);
20 |
21 | public virtual ParsingResult ParseRequests(string json)
22 | {
23 | using (var stream = StreamUtil.GetStreamFromUtf8String(json ?? string.Empty))
24 | {
25 | return this.ParseRequests(stream);
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Defaults/DefaultRpcContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Security.Claims;
6 | using System.Threading.Tasks;
7 | using EdjCase.JsonRpc.Router.Abstractions;
8 | using Microsoft.AspNetCore.Http;
9 |
10 | namespace EdjCase.JsonRpc.Router.Defaults
11 | {
12 |
13 | internal class DefaultContextAccessor : IRpcContextAccessor
14 | {
15 | private RpcContext? value;
16 |
17 | public RpcContext Get()
18 | {
19 | if (this.value == null)
20 | {
21 | throw new InvalidOperationException("Cannot access rpc context outside of a rpc request scope");
22 | }
23 | return this.value;
24 | }
25 |
26 | public void Set(RpcContext context)
27 | {
28 | if (this.value != null)
29 | {
30 | throw new InvalidOperationException("Cannot set rpc context multiple times");
31 | }
32 | this.value = context;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/DefaultHttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Net.Http;
4 |
5 | namespace EdjCase.JsonRpc.Client
6 | {
7 |
8 | internal class DefaultHttpClientFactory : IHttpClientFactory, IDisposable
9 | {
10 | public ConcurrentDictionary Clients { get; } = new ConcurrentDictionary();
11 |
12 | public HttpMessageHandler? MessageHandler { get; }
13 | public DefaultHttpClientFactory(HttpMessageHandler? handler = null)
14 | {
15 | this.MessageHandler = handler;
16 | }
17 |
18 | public HttpClient CreateClient(string name)
19 | {
20 | return this.Clients.GetOrAdd(name, BuildClient);
21 |
22 | HttpClient BuildClient(string n)
23 | {
24 | return this.MessageHandler == null ? new HttpClient() : new HttpClient(this.MessageHandler);
25 | }
26 | }
27 |
28 | public void Dispose()
29 | {
30 | foreach (HttpClient client in this.Clients.Values)
31 | {
32 | client?.Dispose();
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Common/EdjCase.JsonRpc.Common.projitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | 1546452e-2db4-421c-90cd-b4b310ce9ad9
7 |
8 |
9 | EdjCase.JsonRpc.Common
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Sample/EdjCase.JsonRpc.Router.Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 1591
6 | net6.0
7 | EdjCase.JsonRpc.Router.Sample
8 | EdjCase.JsonRpc.Router.Sample
9 | bd997694-63b3-4cf8-8798-f7274f180573
10 |
11 |
12 |
13 | bin\Debug\EdjCase.JsonRpc.Router.Sample.xml
14 |
15 |
16 |
17 | bin\Release\EdjCase.JsonRpc.Router.Sample.xml
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Common/Utilities/ExtensionsUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace EdjCase.JsonRpc.Common.Utilities
6 | {
7 | public static class TypeExtensions
8 | {
9 | ///
10 | /// Determines if the type is a number
11 | ///
12 | /// Type of the object
13 | /// Includes a check for whole number types. Defaults to true
14 | /// True if the type is a number, otherwise False
15 | public static bool IsNumericType(this Type type, bool includeInteger = true)
16 | {
17 | if (includeInteger)
18 | {
19 | return type == typeof(long)
20 | || type == typeof(int)
21 | || type == typeof(short)
22 | || type == typeof(float)
23 | || type == typeof(double)
24 | || type == typeof(decimal);
25 | }
26 | else
27 | {
28 | return type == typeof(float)
29 | || type == typeof(double)
30 | || type == typeof(decimal);
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Client.Sample/EdjCase.JsonRpc.Client.Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A Client sample for Json Rpc v2 requests for .Net Core.
5 | JsonRpc .Net Core Client Sample
6 | Gekctek
7 | net6.0
8 | true
9 | EdjCase.JsonRpc.Client.Sample
10 | Exe
11 | EdjCase.JsonRpc.Client.Sample
12 | https://github.com/edjCase/JsonRpc
13 | https://raw.githubusercontent.com/edjCase/JsonRpc/master/LICENSE
14 | git
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Sample/RpcControllers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace EdjCase.JsonRpc.Router.Sample
7 | {
8 | public abstract class ControllerBase : RpcController
9 | {
10 |
11 | }
12 |
13 | [RpcRoute("Main")]
14 | public class CustomController : ControllerBase
15 | {
16 | [RpcRoute("Method")]
17 | public string Method1(string a)
18 | {
19 | return a;
20 | }
21 | }
22 | public class OtherController : ControllerBase
23 | {
24 | public string Method1(string a)
25 | {
26 | return a;
27 | }
28 | }
29 | public class Commands : ControllerBase
30 | {
31 | public string Method1(string a)
32 | {
33 | return a;
34 | }
35 | }
36 | public class Test : RpcController
37 | {
38 | public string Method1(string a)
39 | {
40 | return a;
41 | }
42 | [NonRpcMethod]
43 | public string Method2(string a)
44 | {
45 | return a;
46 | }
47 | }
48 |
49 | public class NonRpcController
50 | {
51 | public string Method1(string a)
52 | {
53 | return a;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Common/EdjCase.JsonRpc.Common.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1546452e-2db4-421c-90cd-b4b310ce9ad9
5 | 14.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2015 Gekctek
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/IHttpAuthHeaderFactory.cs:
--------------------------------------------------------------------------------
1 |
2 | using EdjCase.JsonRpc.Common;
3 | using EdjCase.JsonRpc.Common.Tools;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.IO.Compression;
8 | using System.Linq;
9 | using System.Net.Http;
10 | using System.Net.Http.Headers;
11 | using System.Text;
12 | using System.Threading;
13 | using System.Threading.Tasks;
14 |
15 | namespace EdjCase.JsonRpc.Client
16 | {
17 | public interface IHttpAuthHeaderFactory
18 | {
19 | Task CreateAuthHeader();
20 | }
21 |
22 | public class DefaultHttpAuthHeaderFactory : IHttpAuthHeaderFactory
23 | {
24 | private Func> authHeaderFunc { get; }
25 | public DefaultHttpAuthHeaderFactory(Func>? authHeaderFunc = null)
26 | {
27 | this.authHeaderFunc = authHeaderFunc ?? (() => Task.FromResult(null));
28 | }
29 | public DefaultHttpAuthHeaderFactory(AuthenticationHeaderValue? authHeader)
30 | {
31 | this.authHeaderFunc = () => Task.FromResult(authHeader);
32 | }
33 |
34 | public Task CreateAuthHeader()
35 | {
36 | return this.authHeaderFunc();
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Abstractions/IRpcResponseSerializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using EdjCase.JsonRpc.Common;
7 |
8 | namespace EdjCase.JsonRpc.Router.Abstractions
9 | {
10 | internal interface IRpcResponseSerializer
11 | {
12 | Task SerializeAsync(RpcResponse response, Stream stream);
13 | Task SerializeBulkAsync(IEnumerable responses, Stream stream);
14 |
15 | public virtual async Task SerializeAsync(RpcResponse response)
16 | {
17 | using var stream = new MemoryStream();
18 | await this.SerializeAsync(response, stream);
19 | return await IRpcResponseSerializer.GetStringAsync(stream);
20 | }
21 |
22 | public virtual async Task SerializeBulkAsync(IEnumerable responses)
23 | {
24 | using var stream = new MemoryStream();
25 | await this.SerializeBulkAsync(responses, stream);
26 | return await IRpcResponseSerializer.GetStringAsync(stream);
27 | }
28 |
29 | private static Task GetStringAsync(MemoryStream stream)
30 | {
31 | stream.Position = 0;
32 | using var streamReader = new StreamReader(stream);
33 | return streamReader.ReadToEndAsync();
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Utilities/ExtensionsUtil.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Routing;
2 | using Microsoft.Extensions.Logging;
3 | using Microsoft.Extensions.Options;
4 | using System;
5 | using System.Text.Json;
6 | using System.Threading.Tasks;
7 |
8 | namespace EdjCase.JsonRpc.Router.Utilities
9 | {
10 | internal static class TypeExtensions
11 | {
12 | ///
13 | /// Determines if the type is nullable
14 | ///
15 | /// Type of the object
16 | /// True if the type is nullable, otherwise False
17 | public static bool IsNullableType(this Type type)
18 | {
19 | return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
20 | }
21 |
22 | public static JsonWriterOptions ToWriterOptions(this JsonSerializerOptions? options)
23 | {
24 | JsonWriterOptions writerOptions = default;
25 | if (options != null)
26 | {
27 | writerOptions.Encoder = options.Encoder;
28 | writerOptions.Indented = options.WriteIndented;
29 | }
30 | return writerOptions;
31 | }
32 | }
33 |
34 | internal static class RouteContextExtensions
35 | {
36 | public static void MarkAsHandled(this RouteContext context)
37 | {
38 | context.Handler = c => Task.FromResult(0);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Publish Release
2 |
3 | on:
4 | release:
5 | types: [published]
6 | jobs:
7 | publish:
8 | name: Publish
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 |
13 | - name: Setup Dotnet
14 | uses: actions/setup-dotnet@v1
15 | with:
16 | dotnet-version: 6.0.x
17 |
18 | - name: Pack Router
19 | run: dotnet pack src/EdjCase.JsonRpc.Router/EdjCase.JsonRpc.Router.csproj --configuration Release /p:Version=${{ github.event.release.tag_name }} --output . --include-symbols --include-source
20 |
21 | - name: Push Router
22 | run: dotnet nuget push EdjCase.JsonRpc.Router.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
23 |
24 | - name: Pack Client
25 | run: dotnet pack src/EdjCase.JsonRpc.Client/EdjCase.JsonRpc.Client.csproj --configuration Release /p:Version=${{ github.event.release.tag_name }} --output . --include-symbols --include-source
26 |
27 | - name: Push Client
28 | run: dotnet nuget push EdjCase.JsonRpc.Client.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
29 |
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Tests/EdjCase.JsonRpc.Router.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | JsonRpc.Router.Tests Class Library
5 | Gekctek
6 | net6.0
7 | EdjCase.JsonRpc.Router.Tests
8 | EdjCase.JsonRpc.Router.Tests
9 | enable
10 | latest
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | all
22 | runtime; build; native; contentfiles; analyzers
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router.Swagger/EdjCase.JsonRpc.Router.Swagger.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | An extension library for EdjCase.JsonRpc.Router to add Swagger functionality.
5 | 2m0nd;Gekctek
6 | net6.0
7 | EdjCase.JsonRpc.Router.Swagger
8 | EdjCase.JsonRpc.Router.Swagger
9 | JsonRpc Rpc AspNet Json-Rpc .Net Core Swagger
10 | Fixes to DI
11 | https://github.com/edjCase/JsonRpc
12 | MIT
13 | git
14 | https://github.com/edjCase/JsonRpc
15 | enable
16 | latest
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/RpcRouterExceptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace EdjCase.JsonRpc.Router
7 | {
8 |
9 | ///
10 | /// Base exception for all rpc router exceptions
11 | ///
12 | public abstract class RpcRouterException : Exception
13 | {
14 | /// Error message
15 | protected RpcRouterException(string message, Exception? ex = null) : base(message, ex)
16 | {
17 | }
18 | }
19 |
20 | ///
21 | /// Exception for bad configuration of the Rpc server
22 | ///
23 | //Not a response exception so it is not an `RpcException`
24 | public class RpcConfigurationException : RpcRouterException
25 | {
26 | /// Error message
27 | public RpcConfigurationException(string message) : base(message)
28 | {
29 | }
30 | }
31 | ///
32 | /// Exception for a canceled rpc request
33 | ///
34 | //Not a response exception so it is not an `RpcException`
35 | public class RpcCanceledRequestException : RpcRouterException
36 | {
37 | /// Error message
38 | public RpcCanceledRequestException(string message, Exception? ex = null) : base(message, ex)
39 | {
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/RpcBulkResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using EdjCase.JsonRpc.Common;
7 |
8 | namespace EdjCase.JsonRpc.Client
9 | {
10 | public class RpcBulkResponse
11 | {
12 | private IDictionary responses { get; }
13 |
14 | public RpcBulkResponse(IDictionary responses)
15 | {
16 | this.responses = responses ?? throw new ArgumentNullException(nameof(responses));
17 | }
18 |
19 | public RpcResponse GetResponse(RpcId id)
20 | {
21 | return this.responses[id];
22 | }
23 |
24 | public RpcResponse GetResponse(RpcId id)
25 | {
26 | return RpcResponse.FromResponse(this.responses[id]);
27 | }
28 |
29 | public List GetResponses()
30 | {
31 | return this.responses
32 | .Select(r => r.Value)
33 | .ToList();
34 | }
35 |
36 | public List> GetResponses()
37 | {
38 | return this.responses
39 | .Select(r => RpcResponse.FromResponse(r.Value))
40 | .ToList();
41 | }
42 |
43 | internal static RpcBulkResponse FromResponses(List responses)
44 | {
45 | Dictionary responseMap = responses.ToDictionary(r => r.Id, r => r);
46 | return new RpcBulkResponse(responseMap);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/EdjCase.JsonRpc.Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | A .Net Core Client implementation for Json Rpc v2 requests.
4 | Gekctek
5 | net6.0
6 | EdjCase.JsonRpc.Client
7 | EdjCase.JsonRpc.Client
8 | JsonRpc Rpc Json-Rpc .Net Core
9 | Integrated core project into client
10 | https://github.com/edjCase/JsonRpc
11 | MIT
12 | git
13 | enable
14 | latest
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/RpcResponse.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using EdjCase.JsonRpc.Common;
3 |
4 | namespace EdjCase.JsonRpc.Router
5 | {
6 | public class RpcResponse
7 | {
8 | protected RpcResponse()
9 | {
10 | }
11 |
12 | /// Request id
13 | protected RpcResponse(RpcId id)
14 | {
15 | this.Id = id;
16 | }
17 |
18 | /// Request id
19 | /// Request error
20 | public RpcResponse(RpcId id, RpcError error) : this(id)
21 | {
22 | this.Error = error;
23 | }
24 |
25 | /// Request id
26 | /// Response result object
27 | public RpcResponse(RpcId id, object? result) : this(id)
28 | {
29 | this.Result = result;
30 | }
31 |
32 | ///
33 | /// Request id
34 | ///
35 | public RpcId Id { get; private set; }
36 |
37 | ///
38 | /// Reponse result object (Required)
39 | ///
40 | public object? Result { get; private set; }
41 |
42 | ///
43 | /// Error from processing Rpc request (Required)
44 | ///
45 | public RpcError? Error { get; private set; }
46 |
47 | public bool HasError => this.Error != null;
48 |
49 | public void ThrowErrorIfExists()
50 | {
51 | if (this.HasError)
52 | {
53 | throw this.Error!.CreateException();
54 | }
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Common/Tools/StreamCompressors.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Text;
6 |
7 | namespace EdjCase.JsonRpc.Common.Tools
8 | {
9 | internal interface IStreamCompressor
10 | {
11 | bool TryGetCompressionStream(Stream uncompressedStream, string encoding, CompressionMode mode, out Stream compressedStream);
12 |
13 | }
14 |
15 | internal class DefaultStreamCompressor : IStreamCompressor
16 | {
17 |
18 | ///
19 | /// Decompresses the input stream to the output stream.
20 | ///
21 | /// The uncompressed stream.
22 | /// Type of the compression.
23 | /// Specifies compression or decompression
24 | ///
25 | public bool TryGetCompressionStream(Stream uncompressedStream, string encoding, CompressionMode mode, out Stream compressedStream)
26 | {
27 | switch (encoding)
28 | {
29 | case "gzip":
30 | compressedStream = new GZipStream(uncompressedStream, mode, leaveOpen: false);
31 | return true;
32 | case "deflate":
33 | compressedStream = new DeflateStream(uncompressedStream, mode, leaveOpen: false);
34 | return true;
35 | default:
36 | compressedStream = uncompressedStream;
37 | return false;
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | // General Information about an assembly is controlled through the following
4 | // set of attributes. Change these attribute values to modify the information
5 | // associated with an assembly.
6 | // [assembly: AssemblyTitle("EdjCase.JsonRpc.Router.Properties")]
7 | // [assembly: AssemblyDescription("")]
8 | // [assembly: AssemblyConfiguration("")]
9 | // [assembly: AssemblyCompany("")]
10 | // [assembly: AssemblyProduct("EdjCase.JsonRpc.Router.Properties")]
11 | // [assembly: AssemblyCopyright("Copyright © 2020")]
12 | // [assembly: AssemblyTrademark("")]
13 | // [assembly: AssemblyCulture("")]
14 |
15 | // Setting ComVisible to false makes the types in this assembly not visible
16 | // to COM components. If you need to access a type in this assembly from
17 | // COM, set the ComVisible attribute to true on that type.
18 | // [assembly: ComVisible(false)]
19 |
20 | // The following GUID is for the ID of the typelib if this project is exposed to COM
21 | // [assembly: Guid("27dbfb84-2e0e-4d81-a172-fbc8c8ab44d4")]
22 |
23 | // Version information for an assembly consists of the following four values:
24 | //
25 | // Major Version
26 | // Minor Version
27 | // Build Number
28 | // Revision
29 | //
30 | // [assembly: AssemblyVersion("1.0.0.0")]
31 | // [assembly: AssemblyFileVersion("1.0.0.0")]
32 | [assembly: InternalsVisibleTo("EdjCase.JsonRpc.Client.Tests")]
33 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
34 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/RpcEvents.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using EdjCase.JsonRpc.Common;
8 | using Newtonsoft.Json;
9 | using Newtonsoft.Json.Linq;
10 | using System.Net.Http;
11 | using System.Net.Http.Headers;
12 | using System.Text;
13 | using EdjCase.JsonRpc.Common.Tools;
14 |
15 | namespace EdjCase.JsonRpc.Client
16 | {
17 | public class RpcEvents
18 | {
19 | public Func? OnRequestStartAsync { get; set; }
20 | public Func? OnRequestCompleteAsync { get; set; }
21 | }
22 |
23 | public class RequestEventContext
24 | {
25 | public string? Route { get; }
26 | public List Requests { get; }
27 | public string RequestJson { get; }
28 |
29 | public RequestEventContext(string? route, List requests, string requestJson)
30 | {
31 | this.Route = route;
32 | this.Requests = requests;
33 | this.RequestJson = requestJson;
34 | }
35 | }
36 |
37 | public class ResponseEventContext
38 | {
39 | public string ResponseJson { get; }
40 | public List? Responses { get; }
41 | public Exception? ClientError { get; }
42 |
43 | public ResponseEventContext(string responseJson, List? responses, Exception? clientError = null)
44 | {
45 | this.ResponseJson = responseJson;
46 | this.Responses = responses;
47 | this.ClientError = clientError;
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/test/Benchmarks/BasicTests.websurge:
--------------------------------------------------------------------------------
1 | POST http://localhost:5000/Test HTTP/1.1
2 | Accept-Encoding: gzip,deflate
3 | Websurge-Request-Name: RPC
4 |
5 | {"id": "6e5fd5f5-d8b2-486c-a6ef-3800dd56a857", "jsonrpc": "2.0", "method": "Ping", "params": []}
6 |
7 | ------------------------------------------------------------------
8 |
9 | POST http://localhost:5000/Test HTTP/1.1
10 | Websurge-Request-Name: RPC No Compression
11 |
12 | {"id": "6e5fd5f5-d8b2-486c-a6ef-3800dd56a857", "jsonrpc": "2.0", "method": "Ping", "params": []}
13 |
14 | ------------------------------------------------------------------
15 |
16 |
17 | ----- Start WebSurge Options -----
18 |
19 | {
20 | "NoProgressEvents": true,
21 | "DelayTimeMs": 0,
22 | "NoContentDecompression": false,
23 | "CaptureMinimalResponseData": false,
24 | "MaxResponseSize": 0,
25 | "ReplaceQueryStringValuePairs": null,
26 | "ReplaceDomain": null,
27 | "ReplaceCookieValue": null,
28 | "ReplaceAuthorization": null,
29 | "Username": null,
30 | "Password": null,
31 | "Users": [],
32 | "RandomizeRequests": false,
33 | "RequestTimeoutMs": 15000,
34 | "WarmupSeconds": 2,
35 | "ReloadTemplates": false,
36 | "FormattedPreviewTheme": "visualstudio",
37 | "LastThreads": 12,
38 | "IgnoreCertificateErrors": false,
39 | "TrackPerSessionCookies": true,
40 | "LastSecondsToRun": 10
41 | }
42 |
43 | // This file was generated by West Wind WebSurge
44 | // Get your free copy at http://websurge.west-wind.com
45 | // to easily test or load test the requests in this file.
46 |
47 | ----- End WebSurge Options -----
48 |
49 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | // General Information about an assembly is controlled through the following
4 | // set of attributes. Change these attribute values to modify the information
5 | // associated with an assembly.
6 | // [assembly: AssemblyTitle("EdjCase.JsonRpc.Router.Properties")]
7 | // [assembly: AssemblyDescription("")]
8 | // [assembly: AssemblyConfiguration("")]
9 | // [assembly: AssemblyCompany("")]
10 | // [assembly: AssemblyProduct("EdjCase.JsonRpc.Router.Properties")]
11 | // [assembly: AssemblyCopyright("Copyright © 2020")]
12 | // [assembly: AssemblyTrademark("")]
13 | // [assembly: AssemblyCulture("")]
14 |
15 | // Setting ComVisible to false makes the types in this assembly not visible
16 | // to COM components. If you need to access a type in this assembly from
17 | // COM, set the ComVisible attribute to true on that type.
18 | // [assembly: ComVisible(false)]
19 |
20 | // The following GUID is for the ID of the typelib if this project is exposed to COM
21 | // [assembly: Guid("27dbfb84-2e0e-4d81-a172-fbc8c8ab44d4")]
22 |
23 | // Version information for an assembly consists of the following four values:
24 | //
25 | // Major Version
26 | // Minor Version
27 | // Build Number
28 | // Revision
29 | //
30 | // [assembly: AssemblyVersion("1.0.0.0")]
31 | // [assembly: AssemblyFileVersion("1.0.0.0")]
32 | [assembly: InternalsVisibleTo("EdjCase.JsonRpc.Router.Tests")]
33 | [assembly: InternalsVisibleTo("Benchmarks")]
34 | //For Mocking an internal type
35 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
36 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Utilities/RpcUtil.cs:
--------------------------------------------------------------------------------
1 | using EdjCase.JsonRpc.Common.Utilities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Reflection;
6 |
7 | namespace EdjCase.JsonRpc.Router.Utilities
8 | {
9 | internal class RpcUtil
10 | {
11 | public static bool TypesMatch(object value, Type type)
12 | {
13 | Type? nullableType = Nullable.GetUnderlyingType(type);
14 | if (nullableType != null)
15 | {
16 | type = nullableType;
17 | }
18 | return value?.GetType() == type;
19 | }
20 |
21 | public static bool NamesMatch(ReadOnlySpan actual, ReadOnlySpan requested)
22 | {
23 | //Requested can be longer because it could have - or _ characters
24 | if (actual.Length > requested.Length)
25 | {
26 | return false;
27 | }
28 | int j = 0;
29 | int equalsChars = 0;
30 | for (int i = 0; i < actual.Length && j < requested.Length; i++)
31 | {
32 | char requestedChar = requested[j++];
33 | char actualChar = actual[i];
34 | if (char.ToLowerInvariant(actualChar) == char.ToLowerInvariant(requestedChar))
35 | {
36 | equalsChars++;
37 | continue;
38 | }
39 | if (requestedChar == '-' || requestedChar == '_')
40 | {
41 | //Skip this j
42 | i--;
43 | continue;
44 | }
45 | return false;
46 | }
47 | //Make sure that it matched ALL the actual characters
48 | //j - all iterations of comparing, need compare j with requested.Length
49 | return j == requested.Length
50 | && equalsChars == actual.Length;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/EdjCase.JsonRpc.Router.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A AspNet Core IRouter implementation for Json Rpc v2 requests for Microsoft.AspNetCore.Routing.
5 | Gekctek
6 | net6.0
7 | EdjCase.JsonRpc.Router
8 | EdjCase.JsonRpc.Router
9 | JsonRpc Rpc AspNet Json-Rpc .Net Core
10 | Fixing Optional parameter matching issue
11 | https://github.com/edjCase/JsonRpc
12 | MIT
13 | git
14 | https://github.com/edjCase/JsonRpc
15 | enable
16 | latest
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/RpcBulkRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using EdjCase.JsonRpc.Common;
7 |
8 | namespace EdjCase.JsonRpc.Client
9 | {
10 | public class RpcBulkRequest
11 | {
12 | private IList<(RpcRequest Request, Type ResponseType)> requests { get; }
13 | public RpcBulkRequest(IList<(RpcRequest, Type)> requests)
14 | {
15 | if(requests == null)
16 | {
17 | throw new ArgumentNullException(nameof(requests));
18 | }
19 | if (!requests.Any())
20 | {
21 | throw new ArgumentException("Need at least one request", nameof(requests));
22 | }
23 | this.requests = requests;
24 | }
25 |
26 | public static RpcBulkRequest FromRequests(List requests, IDictionary responseTypeMap)
27 | {
28 | List<(RpcRequest Request, Type ResponseType)> list = requests
29 | .Select(r => (r, responseTypeMap[r.Id]))
30 | .ToList();
31 | return new RpcBulkRequest(list);
32 | }
33 |
34 | public static RpcBulkRequest FromRequests(List requests)
35 | {
36 | Type responseType = typeof(TResponseType);
37 | List<(RpcRequest Request, Type ResponseType)> list = requests
38 | .Select(r => (r, responseType))
39 | .ToList();
40 | return new RpcBulkRequest(list);
41 | }
42 |
43 | internal IDictionary GetTypeMap()
44 | {
45 | return this.requests.ToDictionary(r => r.Request.Id, r => r.ResponseType);
46 | }
47 |
48 | internal List GetRequests()
49 | {
50 | return this.requests
51 | .Select(r => r.Request)
52 | .ToList();
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Tests/Controllers/MethodMatcherController.cs:
--------------------------------------------------------------------------------
1 | using EdjCase.JsonRpc.Router.Abstractions;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace EdjCase.JsonRpc.Router.Tests.Controllers
6 | {
7 | public class MethodMatcherController : RpcController
8 | {
9 | public Guid GuidTypeMethod(Guid guid)
10 | {
11 | return guid;
12 | }
13 |
14 | public (int, bool, string, object, int?) SimpleMulitParam(int a, bool b, string c, object d, int? e = null)
15 | {
16 | return (a, b, c, d, e);
17 | }
18 |
19 | public List List(List values)
20 | {
21 | return values;
22 | }
23 |
24 | public string SnakeCaseParams(string parameterOne)
25 | {
26 | return parameterOne;
27 | }
28 |
29 | public bool IsLunchTime()
30 | {
31 | return true;
32 | }
33 |
34 | public (string, string?) Optional(string required, string? optional = null)
35 | {
36 | return (required, optional);
37 | }
38 |
39 | // https://github.com/edjCase/JsonRpc/issues/99
40 | public IRpcMethodResult CreateInfoHelperItem(string name, string language, string value, string description, string component, string locationIndex, string fontFamily = "Arial", int fontSize = 12, bool bold = false, bool italic = false, bool strikeout = false, bool underline = false)
41 | {
42 | return this.Ok((name, language, value, description, component, locationIndex, fontFamily, fontSize, bold, italic, strikeout, underline)); ;
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "Build Client Sample",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/test/EdjCase.JsonRpc.Router.Sample/EdjCase.JsonRpc.Router.Sample.csproj",
11 | "/property:GenerateFullPaths=true"
12 | ],
13 | "problemMatcher": "$msCompile"
14 | },
15 | {
16 | "label": "Build Router Sample",
17 | "command": "dotnet",
18 | "type": "process",
19 | "args": [
20 | "build",
21 | "${workspaceFolder}/test/EdjCase.JsonRpc.Router.Sample/EdjCase.JsonRpc.Router.Sample.csproj",
22 | "/property:GenerateFullPaths=true"
23 | ],
24 | "problemMatcher": "$msCompile"
25 | },
26 | {
27 | "label": "Build Performance Tests",
28 | "command": "dotnet",
29 | "type": "process",
30 | "args": [
31 | "build",
32 | "${workspaceFolder}/test/PerformanceTests/PerformanceTests.csproj",
33 | "/property:GenerateFullPaths=true"
34 | ],
35 | "problemMatcher": "$msCompile"
36 | },
37 | {
38 | "label": "Build All",
39 | "command": "dotnet",
40 | "type": "process",
41 | "args": [
42 | "build",
43 | "${workspaceFolder}/EdjCase.JsonRpc.sln",
44 | "/property:GenerateFullPaths=true"
45 | ],
46 | "problemMatcher": "$msCompile",
47 | "group": {
48 | "kind": "build",
49 | "isDefault": true
50 | }
51 | },
52 | {
53 | "label": "Test All",
54 | "command": "dotnet",
55 | "type": "process",
56 | "args": [
57 | "test",
58 | "${workspaceFolder}/test/EdjCase.JsonRpc.Router.Tests"
59 | ],
60 | "problemMatcher": "$msCompile",
61 | "group": {
62 | "kind": "test",
63 | "isDefault": true
64 | }
65 | }
66 | ]
67 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Common/RpcExceptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EdjCase.JsonRpc.Common
4 | {
5 | ///
6 | /// Rpc server exception that contains Rpc specfic error info
7 | ///
8 | public class RpcException : Exception
9 | {
10 | ///
11 | /// Rpc error code that corresponds to the documented integer codes
12 | ///
13 | public int ErrorCode { get; }
14 | ///
15 | /// Custom data attached to the error if needed
16 | ///
17 | public object? RpcData { get; }
18 |
19 | /// Rpc error code
20 | /// Error message
21 | /// Custom data if needed for error response
22 | /// Inner exception (optional)
23 | public RpcException(int errorCode, string message, Exception? innerException = null, object? data = null)
24 | : base(message, innerException)
25 | {
26 | this.ErrorCode = errorCode;
27 | this.RpcData = data;
28 | }
29 | /// Rpc error code
30 | /// Error message
31 | /// Custom data if needed for error response
32 | /// Inner exception (optional)
33 | public RpcException(RpcErrorCode errorCode, string message, Exception? innerException = null, object? data = null)
34 | : this((int)errorCode, message, innerException, data)
35 | {
36 | }
37 |
38 | public RpcError ToRpcError(bool includeServerErrors)
39 | {
40 | string message = this.Message;
41 | if (includeServerErrors)
42 | {
43 | message += Environment.NewLine + "Exception: " + this.InnerException;
44 | }
45 | return new RpcError(this.ErrorCode, message, this.RpcData);
46 | }
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/Abstractions/IRpcMethodProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 | using System.Text;
5 | using Microsoft.AspNetCore.Authorization;
6 |
7 | namespace EdjCase.JsonRpc.Router.Abstractions
8 | {
9 | public interface IRpcMethodProvider
10 | {
11 | RpcRouteMetaData Get();
12 | }
13 |
14 |
15 | public class RpcRouteMetaData
16 | {
17 | public IReadOnlyList BaseRoute { get; }
18 | public IReadOnlyDictionary> PathRoutes { get; }
19 |
20 | public RpcRouteMetaData(IReadOnlyList baseMethods, IReadOnlyDictionary> methods)
21 | {
22 | this.BaseRoute = baseMethods;
23 | this.PathRoutes = methods;
24 | }
25 | }
26 |
27 | public interface IRpcMethodInfo
28 | {
29 | string Name { get; }
30 | IReadOnlyList Parameters { get; }
31 | bool AllowAnonymous { get; }
32 | IReadOnlyList AuthorizeDataList { get; }
33 | Type RawReturnType { get; }
34 |
35 | object? Invoke(object[] parameters, IServiceProvider serviceProvider);
36 | }
37 |
38 | public interface IRpcParameterInfo
39 | {
40 | string Name { get; }
41 | Type RawType { get; }
42 | bool IsOptional { get; }
43 | }
44 |
45 | public static class MethodProviderExtensions
46 | {
47 | public static IReadOnlyList? GetByPath(this IRpcMethodProvider methodProvider, RpcPath? path)
48 | {
49 | RpcRouteMetaData metaData = methodProvider.Get();
50 | if (path == null)
51 | {
52 | return metaData.BaseRoute;
53 | }
54 | if (metaData.PathRoutes.TryGetValue(path, out IReadOnlyList? methods))
55 | {
56 | return methods;
57 | }
58 | return null;
59 | }
60 | }
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Common/RpcError.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EdjCase.JsonRpc.Common
4 | {
5 | public class RpcError : RpcError
6 | {
7 | public RpcError(RpcErrorCode code, string message, T data)
8 | : base(code, message, data)
9 | {
10 | }
11 |
12 | public RpcError(int code, string message, T data)
13 | : base(code, message, data)
14 | {
15 | }
16 |
17 | public new T Data => (T)base.Data!;
18 | }
19 |
20 | ///
21 | /// Model to represent an Rpc response error
22 | ///
23 | public class RpcError
24 | {
25 |
26 | /// Rpc error code
27 | /// Error message
28 | /// Optional error data
29 | public RpcError(RpcErrorCode code, string message, object? data = null)
30 | : this((int)code, message, data)
31 | {
32 | }
33 |
34 | /// Rpc error code
35 | /// Error message
36 | /// Optional error data
37 | public RpcError(int code, string message, object? data = null)
38 | {
39 | if (string.IsNullOrWhiteSpace(message))
40 | {
41 | throw new ArgumentNullException(nameof(message));
42 | }
43 | this.Code = code;
44 | this.Message = message;
45 | this.Data = data;
46 | this.DataType = data?.GetType();
47 | }
48 |
49 | ///
50 | /// Rpc error code (Required)
51 | ///
52 | public int Code { get; }
53 |
54 | public string Message { get; }
55 |
56 | ///
57 | /// Error data (Optional)
58 | ///
59 | public object? Data { get; }
60 |
61 | ///
62 | /// Type of the data object
63 | ///
64 | public Type? DataType { get; }
65 |
66 | public RpcException CreateException()
67 | {
68 | return new RpcException(this.Code, this.Message, data: this.Data);
69 | }
70 |
71 | }
72 | }
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Tests/RpcUtilTests.cs:
--------------------------------------------------------------------------------
1 | using EdjCase.JsonRpc.Common;
2 | using EdjCase.JsonRpc.Router.Defaults;
3 | using Microsoft.Extensions.Options;
4 | using EdjCase.JsonRpc.Router.Abstractions;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Text;
9 | using Xunit;
10 | using System.Threading.Tasks;
11 | using EdjCase.JsonRpc.Router.Utilities;
12 |
13 | namespace EdjCase.JsonRpc.Router.Tests
14 | {
15 | public class RpcUtilTests
16 | {
17 | [Theory]
18 | [InlineData("TestCase", "testcase")]
19 | [InlineData("TestCase", "test_case")]
20 | [InlineData("TestCase", "TEST_CASE")]
21 | [InlineData("TestCase", "test-case")]
22 | [InlineData("TestCase", "TEST-CASE")]
23 | [InlineData("test", "Test")]
24 | public void MatchMethodNamesTest(string methodInfo, string requestMethodName)
25 | {
26 | Assert.True(RpcUtil.NamesMatch(methodInfo, requestMethodName));
27 | }
28 |
29 | [Theory]
30 | [InlineData("GetUsers", "get_user")]
31 | [InlineData("GetUsers", "get_user_super")]
32 | public void NotMatchMethodNamesTest(string methodInfo, string requestMethodName)
33 | {
34 | Assert.False(RpcUtil.NamesMatch(methodInfo, requestMethodName));
35 | }
36 |
37 | [Fact]
38 | public void MatchMethodNamesCulturallyInvariantTest()
39 | {
40 | var previousCulture = System.Globalization.CultureInfo.CurrentCulture;
41 | // Switch to a locale that would result in lowercasing 'I' to
42 | // U+0131, if not done with invariant culture.
43 | System.Globalization.CultureInfo.CurrentCulture = new System.Globalization.CultureInfo("az");
44 | var methodInfo = "IsLunchTime";
45 | var requestMethodName = "isLunchtIme";
46 | Assert.True(RpcUtil.NamesMatch(methodInfo, requestMethodName));
47 | System.Globalization.CultureInfo.CurrentCulture = previousCulture;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/test/EdjCase.JsonRpc.Router.Sample/Controllers/ComplexExampleController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace EdjCase.JsonRpc.Router.Sample.Controllers
8 | {
9 | ///
10 | /// This complex api
11 | ///
12 | [RpcRoute("api/v1/complex")]
13 | public class ComplexExampleController : ControllerBase
14 | {
15 | static ConcurrentDictionary memoryCache = new ConcurrentDictionary();
16 |
17 |
18 | ///
19 | /// Save or update model
20 | ///
21 | ///
22 | ///
23 | public async Task Save(ComplexInputModel model)
24 | {
25 | await Task.Run(() =>
26 | {
27 | memoryCache.AddOrUpdate(model.Id, model, (i, inputModel) =>
28 | {
29 | inputModel.Title = model.Title;
30 | inputModel.CreatedOn = model.CreatedOn;
31 | inputModel.IsEnabled = model.IsEnabled;
32 |
33 | return inputModel;
34 | });
35 | });
36 | }
37 |
38 |
39 | ///
40 | /// Get model by id
41 | ///
42 | ///
43 | ///
44 | public async Task Get(int id)
45 | {
46 | return await Task.Run(() =>
47 | {
48 | memoryCache.TryGetValue(id, out var model);
49 | return model;
50 | });
51 | }
52 |
53 |
54 | ///
55 | /// Get all models
56 | ///
57 | ///
58 | public async Task> Get()
59 | {
60 | return await Task.Run(() => memoryCache.Select(x => x.Value));
61 | }
62 | }
63 |
64 | public class ComplexInputModel
65 | {
66 | public int Id { get; set; }
67 | public string Title { get; set; }
68 | public bool IsEnabled { get; set; }
69 | public DateTime CreatedOn { get; set; }
70 | }
71 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/RpcRequest.cs:
--------------------------------------------------------------------------------
1 | using EdjCase.JsonRpc.Common;
2 | using EdjCase.JsonRpc.Router.Utilities;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.ComponentModel;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Text.Json;
9 |
10 | namespace EdjCase.JsonRpc.Router
11 | {
12 | public class RpcRequest
13 | {
14 | public RpcId Id { get; }
15 | public string Method { get; }
16 | public TopLevelRpcParameters? Parameters { get; }
17 | public RpcRequest(RpcId id, string method, TopLevelRpcParameters? parameters = null)
18 | {
19 | this.Id = id;
20 | this.Method = method;
21 | this.Parameters = parameters;
22 | }
23 | }
24 |
25 | public class TopLevelRpcParameters
26 | {
27 | public object Value { get; }
28 | public bool IsDictionary { get; }
29 |
30 | public TopLevelRpcParameters(Dictionary parameters)
31 | {
32 | this.Value = parameters ?? throw new ArgumentNullException(nameof(parameters));
33 | this.IsDictionary = true;
34 | }
35 |
36 | public TopLevelRpcParameters(params RpcParameter[] parameters)
37 | {
38 | this.Value = parameters ?? throw new ArgumentNullException(nameof(parameters));
39 | this.IsDictionary = false;
40 | }
41 |
42 | public Dictionary AsDictionary
43 | {
44 | get
45 | {
46 | this.CheckValue(isDictionary: true);
47 | return (Dictionary)this.Value;
48 | }
49 | }
50 |
51 | public RpcParameter[] AsArray
52 | {
53 | get
54 | {
55 | this.CheckValue(isDictionary: false);
56 | return (RpcParameter[])this.Value;
57 | }
58 | }
59 |
60 | private void CheckValue(bool isDictionary)
61 | {
62 | if (isDictionary != this.IsDictionary)
63 | {
64 | throw new InvalidOperationException();
65 | }
66 | }
67 |
68 | public bool Any()
69 | {
70 | if (this.IsDictionary)
71 | {
72 | return this.AsDictionary.Any();
73 | }
74 | return this.AsArray.Any();
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/RpcResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using EdjCase.JsonRpc.Common;
3 |
4 | // ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
5 | // ReSharper disable UnusedMember.Local
6 |
7 | namespace EdjCase.JsonRpc.Client
8 | {
9 | public class RpcResponse : RpcResponse
10 | {
11 | public RpcResponse(RpcId id, T result)
12 | : base(id, result)
13 | {
14 |
15 | }
16 |
17 | public RpcResponse(RpcId id, RpcError error)
18 | : base(id, error)
19 | {
20 |
21 | }
22 |
23 | public new T Result => (T)base.Result!;
24 |
25 |
26 | public static RpcResponse FromResponse(RpcResponse response)
27 | {
28 | if (response.HasError)
29 | {
30 | return new RpcResponse(response.Id, response.Error!);
31 | }
32 | return new RpcResponse(response.Id, (T)response.Result!);
33 | }
34 | }
35 |
36 | public class RpcResponse
37 | {
38 | protected RpcResponse()
39 | {
40 | this.Id = new RpcId();
41 | }
42 |
43 | /// Request id
44 | protected RpcResponse(RpcId id)
45 | {
46 | this.Id = id;
47 | }
48 |
49 | /// Request id
50 | /// Request error
51 | public RpcResponse(RpcId id, RpcError error) : this(id)
52 | {
53 | this.Error = error;
54 | }
55 |
56 | /// Request id
57 | /// Response result object
58 | public RpcResponse(RpcId id, object? result) : this(id)
59 | {
60 | this.Result = result;
61 | }
62 |
63 | ///
64 | /// Request id
65 | ///
66 | public RpcId Id { get; private set; }
67 |
68 | ///
69 | /// Reponse result object (Required)
70 | ///
71 | public object? Result { get; private set; }
72 |
73 | ///
74 | /// Error from processing Rpc request (Required)
75 | ///
76 | public RpcError? Error { get; private set; }
77 |
78 | public bool HasError => this.Error != null;
79 |
80 | public void ThrowErrorIfExists()
81 | {
82 | if (this.HasError)
83 | {
84 | throw this.Error!.CreateException();
85 | }
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/RpcController.cs:
--------------------------------------------------------------------------------
1 | using EdjCase.JsonRpc.Router.Abstractions;
2 | using EdjCase.JsonRpc.Router.Defaults;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 |
8 | namespace EdjCase.JsonRpc.Router
9 | {
10 | public abstract class RpcController
11 | {
12 | ///
13 | /// Helper for returning a successful rpc response
14 | ///
15 | /// Object to return in response
16 | /// Success result for rpc response
17 | [NonRpcMethod]
18 | protected virtual RpcMethodSuccessResult Ok(object? obj = null)
19 | {
20 | return new RpcMethodSuccessResult(obj);
21 | }
22 |
23 |
24 | ///
25 | /// Helper for returning an error rpc response
26 | ///
27 | /// JSON-RPC custom error code
28 | /// (Optional)Error message
29 | /// (Optional)Error data
30 | ///
31 | [NonRpcMethod]
32 | protected virtual RpcMethodErrorResult Error(int errorCode, string message, object? data = null)
33 | {
34 | return new RpcMethodErrorResult(errorCode, message, data);
35 | }
36 | }
37 |
38 | ///
39 | /// Attribute to decorate a derived class
40 | /// Allows setting a custom route name instead of using the controller name
41 | ///
42 | public class RpcRouteAttribute : Attribute
43 | {
44 | ///
45 | /// Name of the route to be used in the router. If unspecified, will use controller name.
46 | ///
47 | public string? RouteName { get; }
48 |
49 | ///
50 | ///
51 | ///
52 | /// (Optional) Name of the route to be used in the router. If unspecified, will use controller name.
53 | public RpcRouteAttribute(string? routeName)
54 | {
55 | this.RouteName = routeName;
56 | }
57 | }
58 |
59 | ///
60 | /// Attribute to decorate a method from a derived class
61 | /// Allows a method to not be included as a method
62 | ///
63 | public class NonRpcMethodAttribute : Attribute
64 | {
65 |
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Router/RpcNumber.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EdjCase.JsonRpc.Router
4 | {
5 | public class RpcNumber
6 | {
7 | private readonly string stringValue;
8 | public RpcNumber(string stringValue)
9 | {
10 | this.stringValue = stringValue ?? throw new ArgumentNullException(nameof(stringValue));
11 | }
12 |
13 | public override string ToString()
14 | {
15 | return this.stringValue;
16 | }
17 |
18 | public static implicit operator RpcNumber(short d) => new RpcNumber(d.ToString());
19 | public bool TryGetShort(out short v)
20 | {
21 | return short.TryParse(this.stringValue, out v);
22 | }
23 | public static implicit operator RpcNumber(ushort d) => new RpcNumber(d.ToString());
24 | public bool TryGetUnsignedShort(out ushort v)
25 | {
26 | return ushort.TryParse(this.stringValue, out v);
27 | }
28 | public static implicit operator RpcNumber(int d) => new RpcNumber(d.ToString());
29 | public bool TryGetInteger(out int v)
30 | {
31 | return int.TryParse(this.stringValue, out v);
32 | }
33 | public static implicit operator RpcNumber(uint d) => new RpcNumber(d.ToString());
34 | public bool TryGetUnsignedInteger(out uint v)
35 | {
36 | return uint.TryParse(this.stringValue, out v);
37 | }
38 | public static implicit operator RpcNumber(long d) => new RpcNumber(d.ToString());
39 | public bool TryGetLong(out long v)
40 | {
41 | return long.TryParse(this.stringValue, out v);
42 | }
43 | public static implicit operator RpcNumber(ulong d) => new RpcNumber(d.ToString());
44 | public bool TryGetUnsingedLong(out ulong v)
45 | {
46 | return ulong.TryParse(this.stringValue, out v);
47 | }
48 | public static implicit operator RpcNumber(float d) => new RpcNumber(d.ToString());
49 | public bool TryGetFloat(out float v)
50 | {
51 | return float.TryParse(this.stringValue, out v);
52 | }
53 | public static implicit operator RpcNumber(double d) => new RpcNumber(d.ToString());
54 | public bool TryGetDouble(out double v)
55 | {
56 | return double.TryParse(this.stringValue, out v);
57 | }
58 | public static implicit operator RpcNumber(decimal d) => new RpcNumber(d.ToString());
59 | public bool TryGetDecimal(out decimal v)
60 | {
61 | return decimal.TryParse(this.stringValue, out v);
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Router Tester",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "Build Router Sample",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/test/EdjCase.JsonRpc.Router.Sample/bin/Debug/netcoreapp3.0/EdjCase.JsonRpc.Router.Sample.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}/test/EdjCase.JsonRpc.Router.Sample",
16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
17 | "console": "internalConsole",
18 | "stopAtEntry": false,
19 | "internalConsoleOptions": "openOnSessionStart"
20 | },
21 | {
22 | "name": "Performance Tests",
23 | "type": "coreclr",
24 | "request": "launch",
25 | "preLaunchTask": "Build Performance Tests",
26 | // If you have changed target frameworks, make sure to update the program path.
27 | "program": "${workspaceFolder}/test/PerformanceTests/bin/Debug/netcoreapp3.0/PerformanceTests.dll",
28 | "args": [],
29 | "cwd": "${workspaceFolder}/test/PerformanceTests",
30 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
31 | "console": "internalConsole",
32 | "stopAtEntry": false,
33 | "internalConsoleOptions": "openOnSessionStart"
34 | },
35 | {
36 | "name": "Client Tester",
37 | "type": "coreclr",
38 | "request": "launch",
39 | "preLaunchTask": "Build Client Sample",
40 | "program": "${workspaceRoot}/test/Edjcase.JsonRpc.Client.Sample/bin/Debug/netcoreapp3.0/EdjCase.JsonRpc.Client.Sample.dll",
41 | "args": [],
42 | "cwd": "${workspaceRoot}",
43 | "stopAtEntry": false,
44 | "console": "internalConsole"
45 | },
46 | {
47 | "name": ".NET Core Attach",
48 | "type": "coreclr",
49 | "request": "attach",
50 | "processId": "${command:pickProcess}"
51 | }
52 | ]
53 | }
--------------------------------------------------------------------------------
/src/EdjCase.JsonRpc.Client/RpcParameters.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace EdjCase.JsonRpc.Client
7 | {
8 | public enum RpcParametersType
9 | {
10 | Array,
11 | Dictionary
12 | }
13 |
14 | public struct RpcParameters
15 | {
16 | public bool HasValue { get; }
17 |
18 | public RpcParametersType Type { get; }
19 |
20 | public object Value { get; }
21 |
22 | public Dictionary DictionaryValue
23 | {
24 | get
25 | {
26 | if (this.Type != RpcParametersType.Dictionary)
27 | {
28 | throw new InvalidOperationException("Cannot cast params to dictionary.");
29 | }
30 | return (Dictionary)this.Value;
31 | }
32 | }
33 |
34 | public object[] ArrayValue
35 | {
36 | get
37 | {
38 | if (this.Type != RpcParametersType.Array)
39 | {
40 | throw new InvalidOperationException("Cannot cast params to array.");
41 | }
42 | return (object[])this.Value;
43 | }
44 | }
45 |
46 | public RpcParameters(object[]? parameters)
47 | {
48 | this.HasValue = true;
49 | this.Type = RpcParametersType.Array;
50 | this.Value = parameters ?? new object[0];
51 | }
52 |
53 | public RpcParameters(Dictionary? parameters)
54 | {
55 | this.HasValue = true;
56 | this.Type = RpcParametersType.Dictionary;
57 | this.Value = parameters ?? new Dictionary();
58 | }
59 |
60 | public static RpcParameters Empty => new RpcParameters(new object[0]);
61 |
62 | public static RpcParameters FromList(IEnumerable