Parcourir la source

添加项目文件。

CrazyIter il y a 5 ans
Parent
commit
d3e3330519
100 fichiers modifiés avec 5215 ajouts et 0 suppressions
  1. 25 0
      CallbackDemo/CalculateClass.cs
  2. 8 0
      CallbackDemo/CallbackDemo.csproj
  3. 21 0
      CallbackDemo/FunctionClass.cs
  4. 32 0
      CallbackDemo/Program.cs
  5. 8 0
      ClassLibrary1/Class1.cs
  6. 7 0
      ClassLibrary1/ClassLibrary1.csproj
  7. 11 0
      ClassLibrary1/CosmosDBAttribute.cs
  8. 8 0
      ConsoleApp1/ConsoleApp1.csproj
  9. 78 0
      ConsoleApp1/Program.cs
  10. 8 0
      ConsoleApp2/ConsoleApp2.csproj
  11. 43 0
      ConsoleApp2/Program.cs
  12. 8 0
      ConsoleApp3/ConsoleApp3.csproj
  13. 12 0
      ConsoleApp3/Program.cs
  14. 12 0
      ConsoleApp4/ConsoleApp4.csproj
  15. 12 0
      ConsoleApp4/Distributor.cs
  16. 16 0
      ConsoleApp4/Order.cs
  17. 24 0
      ConsoleApp4/Program.cs
  18. 13 0
      ConsoleApp4/StreetAddress.cs
  19. 12 0
      ConsoleApp4/packs/DataInfo.cs
  20. BIN
      CosmosDB3Test.zip
  21. 15 0
      CosmosDB3Test/CosmosDB3Test.csproj
  22. 57 0
      CosmosDB3Test/CustomSerializer.cs
  23. 11 0
      CosmosDB3Test/ModelJsonNet.cs
  24. 13 0
      CosmosDB3Test/ModelTextJson.cs
  25. 114 0
      CosmosDB3Test/Program.cs
  26. 54 0
      CosmosDB3Test/School.cs
  27. 45 0
      CosmosDB3Test/SystemTextJsonCosmosSerializer.cs
  28. 31 0
      CosmosDB3Test/TextJsonSerializer.cs
  29. 12 0
      CosmosDBTest/AzureCosmosDB/Configuration/AzureCosmosDBConfig.cs
  30. 20 0
      CosmosDBTest/AzureCosmosDB/Configuration/AzureCosmosDBOptions.cs
  31. 24 0
      CosmosDBTest/AzureCosmosDB/Configuration/AzureCosmosDBServiceBuilder.cs
  32. 45 0
      CosmosDBTest/AzureCosmosDB/Configuration/AzureCosmosDBServiceCollectionExtensions.cs
  33. 49 0
      CosmosDBTest/AzureCosmosDB/Configuration/CosmosDBClientSingleton.cs
  34. 386 0
      CosmosDBTest/AzureCosmosDB/Implements/AzureCosmosDBRepository.cs
  35. 24 0
      CosmosDBTest/AzureCosmosDB/Interfaces/IAzureCosmosDBRepository.cs
  36. 12 0
      CosmosDBTest/AzureCosmosDB/PartitionKeyAttribute.cs
  37. 48 0
      CosmosDBTest/Controllers/WeatherForecastController.cs
  38. 11 0
      CosmosDBTest/CosmosDBTest.csproj
  39. 26 0
      CosmosDBTest/Program.cs
  40. 30 0
      CosmosDBTest/Properties/launchSettings.json
  41. 53 0
      CosmosDBTest/Startup.cs
  42. 34 0
      CosmosDBTest/Volume.cs
  43. 15 0
      CosmosDBTest/WeatherForecast.cs
  44. 9 0
      CosmosDBTest/appsettings.Development.json
  45. 19 0
      CosmosDBTest/appsettings.json
  46. 8 0
      DelegateDemo/DelegateDemo.csproj
  47. 80 0
      DelegateDemo/Program.cs
  48. 8 0
      EventDemo/EventDemo.csproj
  49. 35 0
      EventDemo/Fax.cs
  50. 29 0
      EventDemo/MailManager.cs
  51. 23 0
      EventDemo/NewMailEventArgs.cs
  52. 93 0
      EventDemo/Program.cs
  53. 8 0
      EventDemo1/EventDemo1.csproj
  54. 70 0
      EventDemo1/Program.cs
  55. 19 0
      GrpcServiceServer/GrpcServiceServer.csproj
  56. 27 0
      GrpcServiceServer/Program.cs
  57. 12 0
      GrpcServiceServer/Properties/launchSettings.json
  58. 24 0
      GrpcServiceServer/Protos/demo.proto
  59. 21 0
      GrpcServiceServer/Protos/greet.proto
  60. 192 0
      GrpcServiceServer/Services/DemoerService.cs
  61. 26 0
      GrpcServiceServer/Services/GreeterService.cs
  62. 43 0
      GrpcServiceServer/Startup.cs
  63. 10 0
      GrpcServiceServer/appsettings.Development.json
  64. 14 0
      GrpcServiceServer/appsettings.json
  65. 13 0
      JsonRPCTest/Controllers/Item.cs
  66. 206 0
      JsonRPCTest/Controllers/JsonNetHelper.cs
  67. 66 0
      JsonRPCTest/Controllers/WeatherForecastController.cs
  68. 11 0
      JsonRPCTest/JsonRPCTest.csproj
  69. 26 0
      JsonRPCTest/Program.cs
  70. 30 0
      JsonRPCTest/Properties/launchSettings.json
  71. 34 0
      JsonRPCTest/RpcCore/Constants.cs
  72. 49 0
      JsonRPCTest/RpcCore/RpcExceptions.cs
  73. 162 0
      JsonRPCTest/RpcCore/RpcId.cs
  74. 93 0
      JsonRPCTest/RpcCore/RpcParameters.cs
  75. 75 0
      JsonRPCTest/RpcCore/RpcRequest.cs
  76. 157 0
      JsonRPCTest/RpcCore/RpcResponse.cs
  77. 88 0
      JsonRPCTest/RpcCore/Tools/StreamCompressors.cs
  78. 34 0
      JsonRPCTest/RpcCore/Utilities/ExtensionsUtil.cs
  79. 10 0
      JsonRPCTest/RpcRouter/Abstractions/IRouteContext.cs
  80. 29 0
      JsonRPCTest/RpcRouter/Abstractions/IRpcInvoker.cs
  81. 22 0
      JsonRPCTest/RpcRouter/Abstractions/IRpcMethodResult.cs
  82. 17 0
      JsonRPCTest/RpcRouter/Abstractions/IRpcParser.cs
  83. 13 0
      JsonRPCTest/RpcRouter/Abstractions/IRpcRequestHandler.cs
  84. 11 0
      JsonRPCTest/RpcRouter/Abstractions/IRpcRequestMatcher.cs
  85. 13 0
      JsonRPCTest/RpcRouter/Abstractions/IRpcResponseSerializer.cs
  86. 62 0
      JsonRPCTest/RpcRouter/Abstractions/IRpcRouteProvider.cs
  87. 230 0
      JsonRPCTest/RpcRouter/BuilderExtensions.cs
  88. 30 0
      JsonRPCTest/RpcRouter/Configuration.cs
  89. 332 0
      JsonRPCTest/RpcRouter/Defaults/DefaultRequestMatcher.cs
  90. 31 0
      JsonRPCTest/RpcRouter/Defaults/DefaultRouteContext.cs
  91. 420 0
      JsonRPCTest/RpcRouter/Defaults/DefaultRpcInvoker.cs
  92. 82 0
      JsonRPCTest/RpcRouter/Defaults/DefaultRpcMethodResults.cs
  93. 186 0
      JsonRPCTest/RpcRouter/Defaults/DefaultRpcParser.cs
  94. 96 0
      JsonRPCTest/RpcRouter/Defaults/DefaultRpcResponseSerializer.cs
  95. 61 0
      JsonRPCTest/RpcRouter/MethodProviders/ControllerPublicMethodProvider.cs
  96. 76 0
      JsonRPCTest/RpcRouter/ParsingResult.cs
  97. 99 0
      JsonRPCTest/RpcRouter/RouteProviders/RpcAutoRouteProvider.cs
  98. 52 0
      JsonRPCTest/RpcRouter/RouteProviders/RpcManualRouteProvider.cs
  99. 42 0
      JsonRPCTest/RpcRouter/RouteProviders/RpcSingleRouteProvider.cs
  100. 0 0
      JsonRPCTest/RpcRouter/RpcController.cs

+ 25 - 0
CallbackDemo/CalculateClass.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CallbackDemo
+{
+    #region 实际开发中,下面这个类会封装起来,只提供函数接口。相当于系统底层,核心开发人员定义接口
+    public class CalculateClass
+    {
+        public delegate int SomeCalculateWay(int num1, int num2);
+
+        //将传入参数在系统底层进行某种处理,具体计算方法由开发者开发,函数仅提供执行计算方法后的返回值
+        public int PrintAndCalculate(int num1, int num2, SomeCalculateWay cal)
+        {
+            //处理传入参数的校验 或者其他处理
+            Console.WriteLine("系统底层处理:" + num1);
+            Console.WriteLine("系统底层处理:" + num2);
+            //对返回值进行错误校验等
+            int cb= cal(num1, num2);//调用传入函数的一个引用   
+            return cb;
+        }
+        //可以封装更多的业务逻辑方法   
+    }
+    #endregion
+}

+ 8 - 0
CallbackDemo/CallbackDemo.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 21 - 0
CallbackDemo/FunctionClass.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CallbackDemo
+{
+    /// <summary>
+    ///   开发层处理,开发人员编写具体的计算方法 ,由普通开发人员处理相关计算
+    /// </summary>
+    public class FunctionClass
+    {
+        public int GetSum(int a, int b)
+        {
+            return (a + b);
+        }
+        public int GetMulti(int a, int b)
+        {
+            return (a * b);
+        }
+    }
+}

+ 32 - 0
CallbackDemo/Program.cs

@@ -0,0 +1,32 @@
+using System;
+
+namespace CallbackDemo
+{
+    /// <summary>
+    /// 回调函数测试
+    /// </summary>
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            Console.WriteLine("Hello World!");
+            ////用户层,执行输入等操作,出入相关指令 调用开发人员定义的方法处理数据
+            CalculateClass cc = new CalculateClass();
+            FunctionClass fc = new FunctionClass();
+            int result1 = cc.PrintAndCalculate(2, 3, fc.GetSum);
+            Console.WriteLine("调用了开发人员的加法函数,处理后返回结果:" + result1);
+            int result2 = cc.PrintAndCalculate(2, 3, fc.GetMulti);
+            Console.WriteLine("调用了开发人员的乘法函数,处理后返回结果:" + result2);
+
+            //另外一种调用方式  没有对参数 或者结果进行处理
+            CalculateClass.SomeCalculateWay cc1 = new CalculateClass.SomeCalculateWay(fc.GetSum);
+            int resultca= cc1(5, 6);
+            Console.WriteLine("调用了开发人员的加法函数,处理后返回结果:" + resultca);
+           
+            CalculateClass.SomeCalculateWay cc2 = new CalculateClass.SomeCalculateWay(fc.GetMulti);
+            int resultcb = cc2(5, 6);
+            Console.WriteLine("调用了开发人员的乘法函数,处理后返回结果:" + resultcb);
+            Console.ReadKey();
+        }
+    }
+}

+ 8 - 0
ClassLibrary1/Class1.cs

@@ -0,0 +1,8 @@
+using System;
+
+namespace ClassLibrary1
+{
+    public class Class1
+    {
+    }
+}

+ 7 - 0
ClassLibrary1/ClassLibrary1.csproj

@@ -0,0 +1,7 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 11 - 0
ClassLibrary1/CosmosDBAttribute.cs

@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ClassLibrary1
+{
+    [AttributeUsage(AttributeTargets.Class)]
+    public class CosmosDBAttribute : Attribute
+    {
+    }
+}

+ 8 - 0
ConsoleApp1/ConsoleApp1.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 78 - 0
ConsoleApp1/Program.cs

@@ -0,0 +1,78 @@
+using System;
+
+namespace ConsoleApp1
+{
+    class Program
+    {
+        // 定义委托,并引用一个方法,这个方法需要获取一个int型参数返回void
+        internal delegate void Feedback(int value);
+        static void Main(string[] args)
+        {
+
+            StaticDelegateDemo();
+            InstanceDelegateDemo();
+            Console.ReadKey();
+        }
+
+        /// <summary>
+        /// 静态调用
+        /// </summary>
+        private static void StaticDelegateDemo()
+        {
+            Console.WriteLine("---------委托调用静态方法------------");
+            Counter(1, 10, null);
+            Counter(1, 10, new Feedback(FeedbackToConsole));
+
+
+        }
+
+        /// <summary>
+        /// 实例调用
+        /// </summary>
+        private static void InstanceDelegateDemo()
+        {
+            Console.WriteLine("---------委托调用实例方法------------");
+            Program p = new Program();
+            Counter(1, 10, null);
+            Counter(1, 5, new Feedback(p.InstanceFeedbackToConsole));
+        }
+
+
+        /// <summary>
+        /// 静态回调方法
+        /// </summary>
+        /// <param name="value"></param>
+        private static void FeedbackToConsole(int value)
+        {
+            // 依次打印数字
+            Console.WriteLine("Item=" + value);
+        }
+        /// <summary>
+        /// 实例回调方法
+        /// </summary>
+        /// <param name="value"></param>
+        private void InstanceFeedbackToConsole(int value)
+        {
+            Console.WriteLine("Item=" + value);
+        }
+
+        /// <summary>
+        /// 使用此方法触发委托回调
+        /// </summary>
+        /// <param name="from">开始</param>
+        /// <param name="to">结束</param>
+        /// <param name="fb">委托引用</param>
+        private static void Counter(int from, int to, Feedback fb)
+        {
+            for (int val = from; val <= to; val++)
+            {
+                // fb不为空,则调用回调方法
+                if (fb != null)
+                {
+                    fb(val);
+                }
+                //fb?.Invoke(val); 简化版本调用
+            }
+        }
+    }
+}

+ 8 - 0
ConsoleApp2/ConsoleApp2.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 43 - 0
ConsoleApp2/Program.cs

@@ -0,0 +1,43 @@
+using System;
+
+namespace ConsoleApp2
+{
+    class Program
+    {
+        delegate double Function(double x);
+        static void Main(string[] args)
+        {
+            double[] a = { 0.0, 0.5, 1.0 };
+            //double[] squares = Apply(a, Square);
+            //double[] sines = Apply(a,  Math.Sin);
+            double[] squares = Apply(a, new Function(Square));
+            double[] sines = Apply(a, new Function(Math.Sin));
+            Multiplier m = new Multiplier(2.0);
+            double[] doubles = Apply(a, m.Multiply);
+        }
+        static double Square(double x)
+        {
+            return x * x;
+        }
+        static double[] Apply(double[] a, Function f)
+        {
+            double[] result = new double[a.Length];
+            for (int i = 0; i < a.Length; i++) {
+                result[i] = f(a[i]);
+            }
+            return result;
+        }
+    }
+    class Multiplier
+    {
+        double factor;
+        public Multiplier(double factor)
+        {
+            this.factor = factor;
+        }
+        public double Multiply(double x)
+        {
+            return x * factor;
+        }
+    }
+}

+ 8 - 0
ConsoleApp3/ConsoleApp3.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 12 - 0
ConsoleApp3/Program.cs

@@ -0,0 +1,12 @@
+using System;
+
+namespace ConsoleApp3
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            Console.WriteLine("Hello World!");
+        }
+    }
+}

+ 12 - 0
ConsoleApp4/ConsoleApp4.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
+  </ItemGroup>
+
+</Project>

+ 12 - 0
ConsoleApp4/Distributor.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ConsoleApp4
+{
+    public class Distributor
+    {
+        public int Id { get; set; }
+        public ICollection<StreetAddress> ShippingCenters { get; set; }
+    }
+}

+ 16 - 0
ConsoleApp4/Order.cs

@@ -0,0 +1,16 @@
+using ClassLibrary1;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ConsoleApp4
+{
+    [CosmosDBAttribute]
+    public class Order
+    {
+        public int Id { get; set; }
+        public int? TrackingNumber { get; set; }
+        public string PartitionKey { get; set; }
+        public StreetAddress ShippingAddress { get; set; }
+    }
+}

+ 24 - 0
ConsoleApp4/Program.cs

@@ -0,0 +1,24 @@
+using ClassLibrary1;
+using System;
+using System.Linq;
+using System.Reflection;
+
+namespace ConsoleApp4
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+
+            Assembly assembly = Assembly.GetCallingAssembly();
+            var typesWithMyAttribute =
+              from t in assembly.GetTypes()
+              let attributes = t.GetCustomAttributes(typeof(CosmosDBAttribute), false)
+              where attributes != null && attributes.Length > 0
+              select new { Type = t, Attributes = attributes.Cast<CosmosDBAttribute>() };
+            Console.WriteLine("Hello World!");
+        }
+
+
+    }
+}

+ 13 - 0
ConsoleApp4/StreetAddress.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ConsoleApp4
+{
+   
+    public class StreetAddress
+    {
+        public string Street { get; set; }
+        public string City { get; set; }
+    }
+}

+ 12 - 0
ConsoleApp4/packs/DataInfo.cs

@@ -0,0 +1,12 @@
+using ClassLibrary1;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ConsoleApp4.packs
+{
+    [CosmosDBAttribute]
+    public class DataInfo
+    {
+    }
+}

BIN
CosmosDB3Test.zip


+ 15 - 0
CosmosDB3Test/CosmosDB3Test.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="EdjCase.JsonRpc.Router" Version="4.0.0-beta.2" />
+    <PackageReference Include="Microsoft.Azure.Cosmos" Version="3.4.1" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Cosmos" Version="3.0.0" />
+  </ItemGroup>
+
+</Project>

+ 57 - 0
CosmosDB3Test/CustomSerializer.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using CosmosDB3Test;
+using Microsoft.Azure.Cosmos;
+using Newtonsoft.Json.Serialization;
+
+namespace CosmosDBTest.Controllers
+{
+
+
+    public class CosDocument <T> {
+        public string _rid { get; set; }
+        public T Documents{get;set;}
+    }
+    public class CustomSerializer : CosmosSerializer
+    {
+        private readonly JsonSerializerOptions _options;
+
+        internal CustomSerializer(JsonSerializerOptions options)
+        {
+            _options = options;
+        }
+
+        /// <inheritdoc />
+        public override T FromStream<T>(Stream stream)
+        {
+            // Have to dispose of the stream, otherwise the Cosmos SDK throws.
+            // https://github.com/Azure/azure-cosmos-dotnet-v3/blob/0843cae3c252dd49aa8e392623d7eaaed7eb712b/Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonSerializerWrapper.cs#L22
+            // https://github.com/Azure/azure-cosmos-dotnet-v3/blob/0843cae3c252dd49aa8e392623d7eaaed7eb712b/Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonDotNetSerializer.cs#L73
+            using (stream)
+            {
+                // TODO Would be more efficient if CosmosSerializer supported async
+                // See https://github.com/Azure/azure-cosmos-dotnet-v3/issues/715
+                using var memory = new MemoryStream((int)stream.Length);
+                stream.CopyTo(memory);
+
+                byte[] utf8Json = memory.ToArray();
+
+                return JsonSerializer.Deserialize<T>(utf8Json, _options);
+            }
+        }
+
+        /// <inheritdoc />
+        public override Stream ToStream<T>(T input)
+        {
+            byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(input, _options);
+            return new MemoryStream(utf8Json);
+        }
+    }
+
+}

+ 11 - 0
CosmosDB3Test/ModelJsonNet.cs

@@ -0,0 +1,11 @@
+namespace CosmosDB3Test
+{
+    public class ModelJsonNet
+    {
+        public string Id { get; set; }
+
+        public string DescriptiveTitle { get; set;}
+
+        public string ThisShouldNotBeSaved { get; set;}
+    }
+}

+ 13 - 0
CosmosDB3Test/ModelTextJson.cs

@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+
+namespace CosmosDB3Test
+{
+    public class ModelTextJson
+    {
+        [JsonPropertyName("id")]
+        public string TheIdentifier { get; set; }
+
+        [JsonPropertyName("title")]
+        public string DescriptiveTitle { get; set;}
+    }
+}

+ 114 - 0
CosmosDB3Test/Program.cs

@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Azure.Cosmos;
+using Microsoft.Azure.Cosmos.Fluent;
+using Microsoft.Extensions.Configuration;
+
+namespace CosmosDB3Test
+{
+    class Program
+    {
+        static async Task Main(string[] args)
+        {
+            //IConfiguration configuration = new ConfigurationBuilder()
+            //   .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
+            //   .Build();
+            string constr = "AccountEndpoint=https://192.168.8.128:8081/;AccountKey=ddwAeGSf8Lsf1kxPXmdqnyzzi3CkJ0KW2BTPZ7Zq1N7qbJic5j7AaQ+WbF86F3rnzuDgGM1yg8O7BUFo93iA8w==;";
+            await Program.InitializeContainers(constr);
+
+            //await Program.WithCustomSerializerAsync(constr);
+            //await Program.WithSerializerOptionsAsync(constr);
+            //CosmosClientOptions options = new CosmosClientOptions()
+            //{
+            //    AllowBulkExecution = true,
+            //    Serializer = new CustomSerializer( new JsonSerializerOptions()),
+            //};
+            //CosmosClient cosmosClient = new CosmosClient("https://192.168.8.128:8081", 
+            //    "ddwAeGSf8Lsf1kxPXmdqnyzzi3CkJ0KW2BTPZ7Zq1N7qbJic5j7AaQ+WbF86F3rnzuDgGM1yg8O7BUFo93iA8w==", options);
+            //var database = await cosmosClient.CreateDatabaseIfNotExistsAsync("TEAMModelOS");
+            //var container = await database.Database.CreateContainerIfNotExistsAsync("CoreSchool", "/areaCode");
+            //Console.WriteLine("Created Container: {0}\n", container.Container.Id);
+            //var sqlQueryText = "SELECT * FROM c WHERE 1=1 ";
+
+            //Console.WriteLine("Running query: {0}\n", sqlQueryText);
+
+            //QueryDefinition queryDefinition = new QueryDefinition(sqlQueryText);
+            //FeedIterator<School> queryResultSetIterator = container.Container.GetItemQueryIterator<School>(queryDefinition);
+            //List<School> list = new List<School>();
+            //while (queryResultSetIterator.HasMoreResults) {
+            //    FeedResponse<School> currentResultSet = await queryResultSetIterator.ReadNextAsync();
+            //    foreach (School family in currentResultSet)
+            //    {
+            //        list.Add(family);
+            //        Console.WriteLine("\tRead {0}\n", family);
+            //    }
+            //}
+            //Console.WriteLine("Hello World!");
+        }
+        static async Task WithCustomSerializerAsync(string configuration)
+        {
+            CosmosClient client = new CosmosClientBuilder(configuration)
+                    .WithApplicationName("OnDotNetRocks")
+                    .WithCustomSerializer(new TextJsonSerializer())
+                    .Build();
+
+            ModelTextJson model = new ModelTextJson()
+            {
+                TheIdentifier = Guid.NewGuid().ToString(),
+                DescriptiveTitle = "With custom System.Text.Json serializer!"
+            };
+
+            Container container = client.GetContainer("OnDotNet", "episode1serializer");
+            ItemResponse<ModelTextJson> createdItem = await container.CreateItemAsync(model);
+
+            Console.WriteLine($"Used custom serializer to create item {createdItem.Resource.TheIdentifier}");
+        }
+
+        static async Task WithSerializerOptionsAsync(string configuration)
+        {
+            CosmosClient client = new CosmosClientBuilder(configuration)
+                    .WithApplicationName("OnDotNetRocks")
+                    .WithSerializerOptions(new CosmosSerializationOptions()
+                    {
+                        IgnoreNullValues = true,
+                        PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
+                    })
+                    .Build();
+
+            ModelJsonNet model = new ModelJsonNet()
+            {
+                Id = Guid.NewGuid().ToString(),
+                DescriptiveTitle = "With customized JSON.Net!"
+            };
+
+            Container container = client.GetContainer("OnDotNet", "episode1serializer");
+            ItemResponse<ModelJsonNet> createdItem = await container.CreateItemAsync(model);
+
+            Console.WriteLine($"Used serializer options to create item {createdItem.Resource.Id}");
+        }
+
+        static async Task InitializeContainers(string configuration)
+        {
+            CosmosClient client = new CosmosClientBuilder(configuration)
+                     .WithApplicationName("OnDotNet")
+                     .WithCustomSerializer(new SystemTextJsonCosmosSerializer(new System.Text.Json.JsonSerializerOptions()))
+                     .Build();
+            Database database = await client.CreateDatabaseIfNotExistsAsync("OnDotNet");
+            await database.CreateContainerIfNotExistsAsync("episode1serializer", "/id");
+
+           var sqlQueryText = "SELECT * FROM c WHERE 1=1 ";
+
+            Console.WriteLine("Running query: {0}\n", sqlQueryText);
+
+           QueryDefinition queryDefinition = new QueryDefinition(sqlQueryText);
+            Container container = client.GetContainer("OnDotNet", "episode1serializer");
+            // Container container = await database.CreateContainerIfNotExistsAsync("episode1serializer", "/areaCode");
+         ItemResponse<ModelTextJson> queryResultSetIterator =await container.ReadItemAsync<ModelTextJson>("1317cebb-f821-4877-80b2-48e03e6c6e48", new  PartitionKey ("1317cebb-f821-4877-80b2-48e03e6c6e48"));
+             List <ModelTextJson> list = new List<ModelTextJson>();
+         var  a =   queryResultSetIterator.Resource;
+           // IEnumerable<ModelTextJson> schools = queryResultSetIterator.ReadNextAsync().Result.Resource;
+           //  Console.WriteLine(queryResultSetIterator.ReadNextAsync().Result);
+        }
+    }
+}

+ 54 - 0
CosmosDB3Test/School.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CosmosDB3Test
+{
+    public class School
+    {
+        public School()
+        {
+            period = new List<Period>();
+        }
+        public string id { get; set; }
+        public string schoolCode { get; set; }
+        public string areaCode { get; set; }
+        public string schoolName { get; set; }
+        public List<Period> period { get; set; }
+    }
+    public class Grade
+    {
+        public string gradeCode { get; set; }
+        public string gradeName { get; set; }
+    }
+    public class Subject
+    {
+        public string subjectCode { get; set; }
+        public string subjectName { get; set; }
+    }
+    public class Semester
+    {
+        public string semesterName { get; set; }
+        public string count { get; set; }
+        public string month { get; set; }
+        public string day { get; set; }
+        public string semesterCode { get; set; }
+    }
+    public class Period
+    {
+        public Period()
+        {
+            grades = new List<Grade>();
+            subjects = new List<Subject>();
+            semesters = new List<Semester>();
+        }
+        public List<Grade> grades { get; set; }
+        public List<Subject> subjects { get; set; }
+        public List<Semester> semesters { get; set; }
+        public string periodName { get; set; }
+        public string periodCode { get; set; }
+        public int gradeCount { get; set; }
+        public int semesterCount { get; set; }
+        public int subjectCount { get; set; }
+    }
+}

+ 45 - 0
CosmosDB3Test/SystemTextJsonCosmosSerializer.cs

@@ -0,0 +1,45 @@
+using Microsoft.Azure.Cosmos;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+
+namespace CosmosDB3Test
+{
+    internal sealed class SystemTextJsonCosmosSerializer : CosmosSerializer
+    {
+        private readonly JsonSerializerOptions _options;
+
+        internal SystemTextJsonCosmosSerializer(JsonSerializerOptions options)
+        {
+            _options = options;
+        }
+
+        /// <inheritdoc />
+        public override T FromStream<T>(Stream stream)
+        {
+            // Have to dispose of the stream, otherwise the Cosmos SDK throws.
+            // https://github.com/Azure/azure-cosmos-dotnet-v3/blob/0843cae3c252dd49aa8e392623d7eaaed7eb712b/Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonSerializerWrapper.cs#L22
+            // https://github.com/Azure/azure-cosmos-dotnet-v3/blob/0843cae3c252dd49aa8e392623d7eaaed7eb712b/Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonDotNetSerializer.cs#L73
+            using (stream)
+            {
+                // TODO Would be more efficient if CosmosSerializer supported async
+                // See https://github.com/Azure/azure-cosmos-dotnet-v3/issues/715
+                using var memory = new MemoryStream((int)stream.Length);
+                stream.CopyTo(memory);
+
+                byte[] utf8Json = memory.ToArray();
+                return JsonSerializer.Deserialize<T>(utf8Json, _options);
+            }
+        }
+
+        /// <inheritdoc />
+        public override Stream ToStream<T>(T input)
+        {
+            byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(input, _options);
+            return new MemoryStream(utf8Json);
+        }
+    }
+}

+ 31 - 0
CosmosDB3Test/TextJsonSerializer.cs

@@ -0,0 +1,31 @@
+using System;
+using System.IO;
+using System.Text.Json;
+using Microsoft.Azure.Cosmos;
+
+namespace CosmosDB3Test
+{
+    public class TextJsonSerializer : CosmosSerializer
+    {
+        private JsonSerializerOptions options = new JsonSerializerOptions()
+        {
+            IgnoreNullValues = true,
+            PropertyNameCaseInsensitive = true
+        };
+
+        public override T FromStream<T>(Stream stream)
+        {
+            using (stream)
+            {
+                return JsonSerializer.DeserializeAsync<T>(stream, this.options).GetAwaiter().GetResult();
+            }
+        }
+
+        public override Stream ToStream<T>(T input)
+        {
+            MemoryStream stream = new MemoryStream();
+            JsonSerializer.SerializeAsync(stream, input, this.options).GetAwaiter().GetResult();
+            return stream;
+        }
+    }
+}

+ 12 - 0
CosmosDBTest/AzureCosmosDB/Configuration/AzureCosmosDBConfig.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.SDK.Module.AzureCosmosDB.Configuration
+{
+    public class AzureCosmosDBConfig
+    {
+        public readonly static string AZURE_CHINA = "AzureChina";
+        public readonly static string AZURE_GLOBAL = "AzureGlobal";
+    }
+}

+ 20 - 0
CosmosDBTest/AzureCosmosDB/Configuration/AzureCosmosDBOptions.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.SDK.Module.AzureCosmosDB.Configuration
+{
+    public class AzureCosmosDBOptions
+    {
+        public string ConnectionString { get; set; } = null;
+        public string ConnectionKey { get; set; } = null;
+        public string Database { get; set; } = null;
+        public string AzureTableDialect { get; set; } = null;
+        public int CollectionThroughput { get; set; } = 400;
+        
+        public AzureCosmosDBOptions()
+        {
+            //Azure Table Init
+        }
+    }
+}

+ 24 - 0
CosmosDBTest/AzureCosmosDB/Configuration/AzureCosmosDBServiceBuilder.cs

@@ -0,0 +1,24 @@
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.SDK.Module.AzureCosmosDB.Configuration
+{
+    public class AzureCosmosDBServiceBuilder
+    {
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="services"></param>
+        public AzureCosmosDBServiceBuilder(IServiceCollection services)
+        {
+            Services = services ?? throw new ArgumentNullException(nameof(services));
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        public IServiceCollection Services { get; }
+    }
+}

+ 45 - 0
CosmosDBTest/AzureCosmosDB/Configuration/AzureCosmosDBServiceCollectionExtensions.cs

@@ -0,0 +1,45 @@
+using TEAMModelOS.SDK.Module.AzureCosmosDB.Implements;
+using TEAMModelOS.SDK.Module.AzureCosmosDB.Interfaces;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.SDK.Module.AzureCosmosDB.Configuration
+{
+    public static class AzureCosmosDBServiceCollectionExtensions
+    {
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="services"></param>
+        /// <returns></returns>
+        private static AzureCosmosDBServiceBuilder AddCosmosDBServerBuilder(this IServiceCollection services)
+        {
+            return new AzureCosmosDBServiceBuilder(services);
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="services"></param>
+        /// <returns></returns>
+        public static AzureCosmosDBServiceBuilder AddAzureCosmosDB(this IServiceCollection services)
+        {
+            var builder = services.AddCosmosDBServerBuilder();
+            services.AddSingleton<IAzureCosmosDBRepository, AzureCosmosDBRepository>();
+            return builder;
+        }
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="builder"></param>
+        /// <param name="_connectionString"></param>
+        /// <returns></returns>
+        public static AzureCosmosDBServiceBuilder AddCosmosDBConnection(this AzureCosmosDBServiceBuilder builder, AzureCosmosDBOptions databaseOptions)
+        {
+            builder.Services.AddSingleton(databaseOptions);
+            return builder;
+        }
+    }
+}

+ 49 - 0
CosmosDBTest/AzureCosmosDB/Configuration/CosmosDBClientSingleton.cs

@@ -0,0 +1,49 @@
+using Microsoft.Azure.Documents.Client;
+using System;
+
+namespace TEAMModelOS.SDK.Module.AzureCosmosDB.Configuration
+{
+    public sealed class CosmosDBClientSingleton
+    {
+        private static string _connectionUrl;
+        private static string _connectionKey;
+        private DocumentClient CosmosClient;
+
+        private CosmosDBClientSingleton() { }
+
+
+
+        public DocumentClient GetCosmosDBClient()
+        {
+            if (CosmosClient != null)
+            {
+                return CosmosClient;
+            }
+            else
+            {
+                getInstance(_connectionUrl, _connectionKey);
+                return CosmosClient;
+            }
+        }
+
+        public static CosmosDBClientSingleton getInstance(string connectionUrl, string connectionKey)
+        {
+            _connectionUrl = connectionUrl;
+            _connectionKey = connectionKey;
+            return SingletonInstance.instance;
+        }
+
+        private static class SingletonInstance
+        {
+            public static CosmosDBClientSingleton instance = new CosmosDBClientSingleton()
+            {
+                CosmosClient = new DocumentClient(new Uri(_connectionUrl), _connectionKey, ConnectionPolicy)
+            };
+            private static readonly ConnectionPolicy ConnectionPolicy = new ConnectionPolicy
+            {
+                ConnectionMode = ConnectionMode.Direct,
+                ConnectionProtocol = Protocol.Tcp
+            };
+        }
+    }
+}

+ 386 - 0
CosmosDBTest/AzureCosmosDB/Implements/AzureCosmosDBRepository.cs

@@ -0,0 +1,386 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Linq;
+using TEAMModelOS.SDK.Module.AzureCosmosDB.Configuration;
+using TEAMModelOS.SDK.Module.AzureCosmosDB.Interfaces;
+using Microsoft.Azure.Documents.Client;
+using Microsoft.Azure.Documents;
+using Microsoft.Azure.Documents.Linq;
+using System.Reflection;
+using Microsoft.Azure.CosmosDB.BulkExecutor;
+using Microsoft.Azure.CosmosDB.BulkExecutor.BulkImport;
+using System.Threading;
+using Microsoft.Azure.CosmosDB.BulkExecutor.BulkUpdate;
+using CosmosDBTest.AzureCosmosDB;
+using System.Text.Json;
+
+namespace TEAMModelOS.SDK.Module.AzureCosmosDB.Implements
+{ /// <summary>
+  /// sdk 文档https://github.com/Azure/azure-cosmos-dotnet-v2/tree/master/samples
+  /// https://github.com/Azure/azure-cosmos-dotnet-v2/blob/530c8d9cf7c99df7300246da05206c57ce654233/samples/code-samples/DatabaseManagement/Program.cs#L72-L121
+  /// </summary>
+    public class AzureCosmosDBRepository : IAzureCosmosDBRepository
+    {
+        /// <summary>
+        /// sdk 文档https://github.com/Azure/azure-cosmos-dotnet-v2/tree/master/samples
+        /// https://github.com/Azure/azure-cosmos-dotnet-v2/blob/530c8d9cf7c99df7300246da05206c57ce654233/samples/code-samples/DatabaseManagement/Program.cs#L72-L121
+        /// </summary>
+       
+        private DocumentClient CosmosClient { get; set; }
+        private DocumentCollection CosmosCollection { get; set; }
+
+        private string Database { get; set; }
+        private int CollectionThroughput { get; set; }
+        public AzureCosmosDBRepository(AzureCosmosDBOptions options)
+        {
+            try
+            {
+                if (!string.IsNullOrEmpty(options.ConnectionString))
+                {
+                    CosmosClient = CosmosDBClientSingleton.getInstance(options.ConnectionString, options.ConnectionKey).GetCosmosDBClient();
+                }
+                
+                else
+                {
+                    throw new Exception("请设置正确的AzureCosmosDB数据库配置信息!");
+                }
+                Database = options.Database;
+                CollectionThroughput = options.CollectionThroughput;
+                CosmosClient.CreateDatabaseIfNotExistsAsync(new Database { Id = Database });
+                // _connectionString = options.ConnectionString;
+            }
+            catch (DocumentClientException de)
+            {
+                Exception baseException = de.GetBaseException();
+                Console.WriteLine("{0} error occurred: {1}, Message: {2}", de.StatusCode, de.Message, baseException.Message);
+            }
+            catch (Exception e)
+            {
+                Exception baseException = e.GetBaseException();
+                Console.WriteLine("Error: {0}, Message: {1}", e.Message, baseException.Message);
+            }
+            finally
+            {
+                Console.WriteLine("End of demo, press any key to exit.");
+                //  Console.ReadKey();
+            }
+
+
+        }
+        private async Task<DocumentCollection> InitializeCollection<T>()
+        {
+            Type t = typeof(T);
+            if (CosmosCollection == null || !CosmosCollection.Id.Equals(t.Name))
+            {
+                DocumentCollection collectionDefinition = new DocumentCollection { Id = t.Name };
+                collectionDefinition.IndexingPolicy = new IndexingPolicy(new RangeIndex(DataType.String) { Precision = -1 });
+                string partitionKey = GetPartitionKey<T>();
+                // collectionDefinition.PartitionKey = new PartitionKeyDefinition {  Paths = new System.Collections.ObjectModel.Collection<string>() };
+                if (!string.IsNullOrEmpty(partitionKey))
+                {
+                    collectionDefinition.PartitionKey.Paths.Add("/" + partitionKey);
+
+                }
+                // CosmosCollection = await this.CosmosClient.CreateDocumentCollectionIfNotExistsAsync(UriFactory.CreateDatabaseUri(Database), collectionDefinition);
+                CosmosCollection = await this.CosmosClient.CreateDocumentCollectionIfNotExistsAsync(
+                    UriFactory.CreateDatabaseUri(Database), collectionDefinition, new RequestOptions { OfferThroughput = CollectionThroughput }
+                    );
+            }
+
+            return CosmosCollection;
+        }
+
+        private string GetPartitionKey<T>()
+        {
+            Type type = typeof(T);
+            PropertyInfo[] properties = type.GetProperties();
+            List<PropertyInfo> attrProperties = new List<PropertyInfo>();
+            foreach (PropertyInfo property in properties)
+            {
+                if (property.Name.Equals("PartitionKey"))
+                {
+                    attrProperties.Add(property);
+                    break;
+                }
+                object[] attributes = property.GetCustomAttributes(true);
+                foreach (object attribute in attributes) //2.通过映射,找到成员属性上关联的特性类实例,
+                {
+                    if (attribute is PartitionKeyAttribute)
+                    {
+                        attrProperties.Add(property);
+                    }
+                }
+            }
+
+            if (attrProperties.Count <= 0)
+            {
+                return null;
+            }
+            else
+            {
+                if (attrProperties.Count == 1)
+                {
+                    return attrProperties[0].Name;
+                }
+                else { throw new Exception("PartitionKey can only be single!"); }
+            }
+        }
+        public async Task<T> Save<T>(T entity)  //where T : object, new()
+        {
+            try {
+                Type t = typeof(T);
+                DocumentCollection documentCollection = await InitializeCollection<T>();
+                ResourceResponse<Document> doc =
+                    await CosmosClient.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(Database, t.Name), entity);
+                //Console.WriteLine(doc.ActivityId);
+                return entity;
+            } catch (Exception e ) {
+                throw  new Exception(e.Message);
+            }
+        }
+
+        public async Task<T> Update<T>(T entity)
+        {
+            Type t = typeof(T);
+            await InitializeCollection<T>();
+            ResourceResponse<Document> doc =
+                await CosmosClient.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(Database, t.Name), entity);
+            return entity;
+        }
+        public async Task<string> ReplaceObject<T>(T entity, string key)
+        {
+            Type t = typeof(T);
+            await InitializeCollection<T>();
+            try
+            {
+                ResourceResponse<Document> doc =
+                await CosmosClient.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(Database, t.Name, key), entity);
+                return key;
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("{0} Exception caught.", e);
+                //return false;
+            }
+            return null;
+
+        }
+
+        public async Task<string> ReplaceObject<T>(T entity, string key, string partitionKey)
+        {
+            Type t = typeof(T);
+            await InitializeCollection<T>();
+            try
+            {
+                ResourceResponse<Document> doc =
+                await CosmosClient.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(Database, t.Name, key),
+                entity,
+                new RequestOptions { PartitionKey = new PartitionKey(partitionKey) });
+                return key;
+            }
+            catch (Exception e)
+            {
+                throw new Exception(e.Message);
+                //Console.WriteLine("{0} Exception caught.", e);
+                //return false;
+            }
+        }
+
+        public async Task<List<T>> FindAll<T>()
+        {
+            Type t = typeof(T);
+            Boolean open = true;
+            List<T> objs = new List<T>();
+
+            //await InitializeCollection<T>();
+            //查询条数 -1是全部
+            FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = open };
+            var query = CosmosClient.CreateDocumentQuery<T>(UriFactory.CreateDocumentCollectionUri(Database, t.Name), queryOptions).AsDocumentQuery();
+            while (query.HasMoreResults)
+            {
+                foreach (T obj in await query.ExecuteNextAsync())
+                {
+                    objs.Add(obj);
+                }
+            }
+            return objs;
+            //return CosmosClient.CreateDocumentQuery<T>(UriFactory.CreateDocumentCollectionUri(Database, t.Name),sql);
+
+        }
+
+        public async Task<List<T>> FindLinq<T>(Func<IQueryable<object>, object> singleOrDefault)
+        {
+            Type t = typeof(T);
+
+            List<T> objs = new List<T>();
+            await InitializeCollection<T>();
+            //查询条数 -1是全部
+            FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 };
+            var query = CosmosClient.CreateDocumentQuery<T>(UriFactory.CreateDocumentCollectionUri(Database, t.Name), queryOptions);
+
+            //  query.Where();
+            return objs;
+            //return CosmosClient.CreateDocumentQuery<T>(UriFactory.CreateDocumentCollectionUri(Database, t.Name),sql);
+
+        }
+
+        public async Task<List<T>> FindSQL<T>(string sql)
+        {
+            Type t = typeof(T);
+            List<T> objs = new List<T>();
+            await InitializeCollection<T>();
+            var query = CosmosClient.CreateDocumentQuery<T>(UriFactory.CreateDocumentCollectionUri(Database, t.Name), sql);
+            foreach (var item in query)
+            {
+                objs.Add(item);
+            }
+            return objs;
+
+        }
+
+        public async Task<List<T>> FindSQL<T>(string sql, bool IsPk)
+        {
+            Type t = typeof(T);
+            List<T> objs = new List<T>();
+            Boolean open = IsPk;
+            await InitializeCollection<T>();
+            //查询条数 -1是全部
+            FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = open };
+            var query = CosmosClient.CreateDocumentQuery<T>(UriFactory.CreateDocumentCollectionUri(Database, t.Name), sql, queryOptions);
+            foreach (var item in query)
+            {
+                objs.Add(item);
+            }
+            return objs;
+
+        }
+
+
+
+        public async Task<string> DeleteAsync<T>(string id)
+        {
+            Type t = typeof(T);
+            await InitializeCollection<T>();
+            ResourceResponse<Document> doc =
+                await CosmosClient.DeleteDocumentAsync(UriFactory.CreateDocumentUri(Database, t.Name, id));
+            //Console.WriteLine(doc.ActivityId);
+            return id;
+        }
+        public async Task<T> DeleteAsync<T>(T entity)
+        {
+            await InitializeCollection<T>();
+            Type t = typeof(T);
+            string PartitionKey = GetPartitionKey<T>();
+            if (!string.IsNullOrEmpty(PartitionKey))
+            {
+                string pkValue = entity.GetType().GetProperty(PartitionKey).GetValue(entity).ToString();
+                string idValue = entity.GetType().GetProperty("id").GetValue(entity).ToString();
+                ResourceResponse<Document> doc =
+              await CosmosClient.DeleteDocumentAsync(UriFactory.CreateDocumentUri(Database, t.Name, idValue), new RequestOptions { PartitionKey = new PartitionKey(pkValue) });
+            }
+            else
+            {
+                string idValue = entity.GetType().GetProperty("id").GetValue(entity).ToString();
+                ResourceResponse<Document> doc =
+                await CosmosClient.DeleteDocumentAsync(UriFactory.CreateDocumentUri(Database, t.Name, idValue));
+            }
+
+
+            //Console.WriteLine(doc.ActivityId);
+            return entity;
+
+        }
+        public async Task<string> DeleteAsync<T>(string id, string partitionKey)
+        {
+            Type t = typeof(T);
+
+            await InitializeCollection<T>();
+            ResourceResponse<Document> doc =
+                await CosmosClient.DeleteDocumentAsync(UriFactory.CreateDocumentUri(Database, t.Name, id), new RequestOptions { PartitionKey = new PartitionKey(partitionKey) });
+            //Console.WriteLine(doc.ActivityId);
+            return id;
+
+        }
+
+        public async Task<List<T>> SaveAll<T>(List<T> enyites)
+        {
+            DocumentCollection dataCollection = await InitializeCollection<T>();
+            // Set retry options high for initialization (default values).
+            CosmosClient.ConnectionPolicy.RetryOptions.MaxRetryWaitTimeInSeconds = 30;
+            CosmosClient.ConnectionPolicy.RetryOptions.MaxRetryAttemptsOnThrottledRequests = 9;
+            IBulkExecutor bulkExecutor = new BulkExecutor(CosmosClient, dataCollection);
+            await bulkExecutor.InitializeAsync();
+            // Set retries to 0 to pass control to bulk executor.
+            CosmosClient.ConnectionPolicy.RetryOptions.MaxRetryWaitTimeInSeconds = 0;
+            CosmosClient.ConnectionPolicy.RetryOptions.MaxRetryAttemptsOnThrottledRequests = 0;
+            BulkImportResponse bulkImportResponse = null;
+            long totalNumberOfDocumentsInserted = 0;
+            double totalRequestUnitsConsumed = 0;
+            double totalTimeTakenSec = 0;
+            var tokenSource = new CancellationTokenSource();
+            var token = tokenSource.Token;
+            int pageSize = 100;
+            int pages = (int)Math.Ceiling((double)enyites.Count / pageSize);
+
+            for (int i = 0; i < pages; i++)
+            {
+                List<string> documentsToImportInBatch = new List<string>();
+                List<T> lists = enyites.Skip((i) * pageSize).Take(pageSize).ToList();
+                for (int j = 0; j < lists.Count; j++)
+                {
+                    documentsToImportInBatch.Add(JsonSerializer.Serialize(lists[j]));
+                }
+                var tasks = new List<Task>
+                { Task.Run(async () =>
+                    {
+                        do
+                        {
+                            //try
+                            //{
+                            bulkImportResponse = await bulkExecutor.BulkImportAsync(
+                                documents: documentsToImportInBatch,
+                                enableUpsert: true,
+                                disableAutomaticIdGeneration: true,
+                                maxConcurrencyPerPartitionKeyRange: null,
+                                maxInMemorySortingBatchSize: null,
+                                cancellationToken: token);
+                            //}
+                            //catch (DocumentClientException de)
+                            //{
+                            //    break;
+                            //}
+                            //catch (Exception e)
+                            //{
+                            //    break;
+                            //}
+                        } while (bulkImportResponse.NumberOfDocumentsImported < documentsToImportInBatch.Count);
+                        totalNumberOfDocumentsInserted += bulkImportResponse.NumberOfDocumentsImported;
+                        totalRequestUnitsConsumed += bulkImportResponse.TotalRequestUnitsConsumed;
+                        totalTimeTakenSec += bulkImportResponse.TotalTimeTaken.TotalSeconds;
+                    },
+                token)
+                };
+                await Task.WhenAll(tasks);
+            }
+            return enyites;
+        }
+         
+        private static UpdateOperation SwitchType(string key, object obj)
+        {
+            Type s = obj.GetType();
+            TypeCode typeCode = Type.GetTypeCode(s);
+            switch (typeCode)
+            {
+                case TypeCode.String: return new SetUpdateOperation<string>(key, obj.ToString());
+                case TypeCode.Int32: return new SetUpdateOperation<Int32>(key, (Int32)obj);
+                case TypeCode.Double: return new SetUpdateOperation<Double>(key, (Double)obj);
+                case TypeCode.Byte: return new SetUpdateOperation<Byte>(key, (Byte)obj);
+                case TypeCode.Boolean: return new SetUpdateOperation<Boolean>(key, (Boolean)obj);
+                case TypeCode.DateTime: return new SetUpdateOperation<DateTime>(key, (DateTime)obj);
+                case TypeCode.Int64: return new SetUpdateOperation<Int64>(key, (Int64)obj);
+                default: return null;
+            }
+        }
+    }
+}

+ 24 - 0
CosmosDBTest/AzureCosmosDB/Interfaces/IAzureCosmosDBRepository.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+
+namespace TEAMModelOS.SDK.Module.AzureCosmosDB.Interfaces
+{
+    public interface IAzureCosmosDBRepository
+    {
+        Task<T> Save<T>(T entity)  ;
+        Task<T> Update<T>(T entity);
+        Task<string> ReplaceObject<T>(T entity, string key);
+        Task<string> ReplaceObject<T>(T entity, string key, string partitionKey);
+        Task<List<T>> FindAll<T>();
+        Task<string> DeleteAsync<T>(string id);
+        Task<string> DeleteAsync<T>(string id, string partitionKey);
+        Task<T> DeleteAsync<T>(T entity);
+        Task<List<T>> FindSQL<T>(string sql);
+        Task<List<T>> FindSQL<T>(string sql, bool isPK);
+        Task<List<T>> FindLinq<T>(Func<IQueryable<object>, object> singleOrDefault);
+        Task<List<T>> SaveAll<T>(List<T> enyites);
+    }
+}

+ 12 - 0
CosmosDBTest/AzureCosmosDB/PartitionKeyAttribute.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace CosmosDBTest.AzureCosmosDB
+{
+    [AttributeUsage(AttributeTargets.Property)]
+    public class PartitionKeyAttribute : Attribute
+    {
+    }
+}

+ 48 - 0
CosmosDBTest/Controllers/WeatherForecastController.cs

@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.Cosmos;
+using Microsoft.Extensions.Logging;
+using TEAMModelOS.SDK.Module.AzureCosmosDB.Interfaces;
+
+namespace CosmosDBTest.Controllers
+{
+    [ApiController]
+    [Route("[controller]")]
+    public class WeatherForecastController : ControllerBase
+    {
+        private static readonly string[] Summaries = new[]
+        {
+            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
+        };
+
+        private readonly ILogger<WeatherForecastController> _logger;
+        private readonly IAzureCosmosDBRepository azureCosmosDBRepository;
+        public WeatherForecastController(ILogger<WeatherForecastController> logger, IAzureCosmosDBRepository _azureCosmosDBRepository)
+        {
+            _logger = logger;
+            azureCosmosDBRepository = _azureCosmosDBRepository;
+        }
+
+        [HttpGet]
+        public IEnumerable<WeatherForecast> Get()
+        {
+            var rng = new Random();
+            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
+            {
+                Date = DateTime.Now.AddDays(index),
+                TemperatureC = rng.Next(-20, 55),
+                Summary = Summaries[rng.Next(Summaries.Length)]
+            })
+            .ToArray();
+        }
+        [HttpGet("GetData")]
+        public async Task<IEnumerable<Volume>> GetData()
+        {
+
+          return await  azureCosmosDBRepository.FindSQL<Volume>("select * from c where c.id= '1'" ,true);
+        }
+    }
+}

+ 11 - 0
CosmosDBTest/CosmosDBTest.csproj

@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Azure.CosmosDB.BulkExecutor" Version="2.4.1-preview" />
+    <PackageReference Include="Microsoft.Azure.DocumentDB.Core" Version="2.9.0" />
+  </ItemGroup>
+</Project>

+ 26 - 0
CosmosDBTest/Program.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace CosmosDBTest
+{
+    public class Program
+    {
+        public static void Main(string[] args)
+        {
+            CreateHostBuilder(args).Build().Run();
+        }
+
+        public static IHostBuilder CreateHostBuilder(string[] args) =>
+            Host.CreateDefaultBuilder(args)
+                .ConfigureWebHostDefaults(webBuilder =>
+                {
+                    webBuilder.UseStartup<Startup>();
+                });
+    }
+}

+ 30 - 0
CosmosDBTest/Properties/launchSettings.json

@@ -0,0 +1,30 @@
+{
+  "$schema": "http://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:55872",
+      "sslPort": 44361
+    }
+  },
+  "profiles": {
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "launchUrl": "weatherforecast",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "CosmosDBTest": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "launchUrl": "weatherforecast",
+      "applicationUrl": "https://localhost:5001;http://localhost:5000",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 53 - 0
CosmosDBTest/Startup.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.HttpsPolicy;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using TEAMModelOS.SDK.Module.AzureCosmosDB.Configuration;
+
+namespace CosmosDBTest
+{
+    public class Startup
+    {
+        public Startup(IConfiguration configuration)
+        {
+            Configuration = configuration;
+        }
+
+        public IConfiguration Configuration { get; }
+
+        // This method gets called by the runtime. Use this method to add services to the container.
+        public void ConfigureServices(IServiceCollection services)
+        {
+            services.AddAzureCosmosDB().AddCosmosDBConnection(Configuration.GetSection("Azure:CosmosDB").Get<AzureCosmosDBOptions>());
+            services.AddControllers();
+        }
+
+        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+        {
+            if (env.IsDevelopment())
+            {
+                app.UseDeveloperExceptionPage();
+            }
+
+            app.UseHttpsRedirection();
+
+            app.UseRouting();
+
+            app.UseAuthorization();
+
+            app.UseEndpoints(endpoints =>
+            {
+                endpoints.MapControllers();
+            });
+        }
+    }
+}

+ 34 - 0
CosmosDBTest/Volume.cs

@@ -0,0 +1,34 @@
+using CosmosDBTest.AzureCosmosDB;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace CosmosDBTest
+{
+    public class Volume
+    {
+        /// <summary>
+        /// id生成规则
+        /// </summary>
+        public string id { get; set; }
+        /// <summary>
+        /// 0默认教学课纲的册别 1个人或单独的专题课纲册别
+        /// </summary>
+        public int type { get; set; }
+        /// <summary>
+        /// Type 如果为0 则是学校编码  如果为1 则是seminar 专题/研讨/培训
+        /// </summary>
+        [PartitionKey]
+        public string schoolCode { get; set; }
+        public string periodCode { get; set; }
+        public string subjectCode { get; set; }
+        public string gradeCode { get; set; }
+        public string semesterCode { get; set; }
+        public int status { get; set; } = 1;
+        public string volumeName { get; set; }
+        public string volumeCode { get; set; }
+        public string TEAMModelId { get; set; }
+        public string[] editors { get; set; }
+    }
+}

+ 15 - 0
CosmosDBTest/WeatherForecast.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace CosmosDBTest
+{
+    public class WeatherForecast
+    {
+        public DateTime Date { get; set; }
+
+        public int TemperatureC { get; set; }
+
+        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+
+        public string Summary { get; set; }
+    }
+}

+ 9 - 0
CosmosDBTest/appsettings.Development.json

@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Debug",
+      "System": "Information",
+      "Microsoft": "Information"
+    }
+  }
+}

+ 19 - 0
CosmosDBTest/appsettings.json

@@ -0,0 +1,19 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft": "Warning",
+      "Microsoft.Hosting.Lifetime": "Information"
+    }
+  },
+  "AllowedHosts": "*",
+  "Azure": {
+    "CosmosDB": {
+      "ConnectionString": "https://192.168.8.192:8081",
+      "ConnectionKey": "GCe8wmHSRl/3hKUNXW6TfLiNTs3Vw8aBC9LKuma1Q4aUfukYqWb7lO0UCnWAJTeWmbMqU8JsufmIZQ3lk0VIbA==",
+      "Database": "TEAMModelOS",
+      "AzureTableDialect": "",
+      "CollectionThroughput": 400
+    }
+  }
+}

+ 8 - 0
DelegateDemo/DelegateDemo.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 80 - 0
DelegateDemo/Program.cs

@@ -0,0 +1,80 @@
+using System;
+
+namespace DelegateDemo
+{
+    class Program
+    {
+        /// <summary>
+        /// 泛型委托
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <typeparam name="S"></typeparam>
+        /// <param name="a"></param>
+        /// <param name="b"></param>
+        /// <returns></returns>
+        public delegate int ProcessDelegate<T, S, V>(T a, S b, V v);
+        /// <summary>
+        /// 委托
+        /// </summary>
+        /// <param name="a"></param>
+        /// <param name="b"></param>
+        /// <returns></returns>
+        public delegate int ProcessDelegate(int a, int b);
+        static void Main(string[] args)
+        {
+            //静态调用
+            ProcessDelegate process = new ProcessDelegate(Add);
+            ProcessDelegate<int, int, int> processDelegate = new ProcessDelegate<int, int, int>(Add);
+            var a = processDelegate(5, 5, 5);
+            Console.WriteLine(process(2, 4));
+            Console.WriteLine(Calculate(4, 3, Add ));
+            Console.WriteLine(Calculate(4, 3,5, Add));
+           
+            Console.WriteLine(a);
+            //实例调用
+            Test test = new Test();
+            ProcessDelegate  processA = new ProcessDelegate(test.Add);
+            Console.WriteLine(processA(5, 8));
+            ProcessDelegate processB = (ProcessDelegate)Delegate.Combine(process , processA);
+            //调用了Delegate.Combine()方法,通过名称可以指定它用于将多个委托组合起来,调用test3时,会按照它的参数顺序执行所有方法。这种方式有时候非常有用,因为我们很可能在某个事件发生时,要执行多个操作。
+            Console.WriteLine(Calculate(50, 10, processB));
+            //Func调用从无参到16个参数共有17个重载,用于分装有输入值而且有返回值的方法。如:delegate TResule Func<T>(T obj);
+            Func<int, int, int> func = new Func<int, int, int>(Add);
+            //Action 从无参到16个参数共有17个重载,用于分装有输入值而没有返回值的方法。如:delegate void Action<T>(T obj);
+            Action<int, int> action = new Action<int, int>(test.Say);
+            Console.WriteLine(func(5, 9));
+        }
+
+        //执行委托
+        public static int Calculate(int a, int b, ProcessDelegate process) {
+           return process(a, b);
+        }
+        public static int Calculate(int a, int b, int c , ProcessDelegate<int , int ,int > process)
+        {
+            return process(a, b , c);
+        }
+        public static int Add(int a, int b)
+        {
+            return a + b;
+        }
+        public static int DS(int a, int b)
+        {
+            return a + b;
+        }
+        public static int Add(int a, int b, int c)
+        {
+            return a + b + c;
+        }
+    }
+    public class Test {
+
+        public int Add(int a, int b) {
+            return a + b;
+        }
+        public void Say(int a, int b)
+        {
+            Console.WriteLine(a + b);
+        }
+    }
+    
+}

+ 8 - 0
EventDemo/EventDemo.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 35 - 0
EventDemo/Fax.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace EventDemo
+{
+    /// <summary>
+    /// 定义事件接收者
+    /// </summary>
+    public class Fax
+    {
+        public Fax(MailManager mm)
+        {
+            // 构造委托实例,向事件登记回调方法
+            mm.NewMail += FaxMsg;
+        }
+        /// <summary>
+        /// 回调方法
+        /// </summary>
+        /// <param name="sender">表示MailManager对象,便于将信息传递给他</param>
+        /// <param name="e">表示MailManager对象想传给我们的附加信息</param>
+        private void FaxMsg(object sender, NewMailEventArgs e)
+        {
+            Console.WriteLine("msg:{0},{1},{2}", e.From, e.To, e.Content);
+        }
+        /// <summary>
+        /// 注销对事件的登记
+        /// </summary>
+        /// <param name="mm"></param>
+        public void Unregister(MailManager mm)
+        {
+            mm.NewMail -= FaxMsg;
+        }
+    }
+}

+ 29 - 0
EventDemo/MailManager.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+namespace EventDemo
+{
+   public class MailManager
+    {
+        // 定义事件成员
+        //  public delegate void EventHandler<[NullableAttribute(2)] TEventArgs>([NullableAttribute(2)] object? sender, TEventArgs e);
+        public event EventHandler<NewMailEventArgs> NewMail;
+        // 定义负责引发事件的方法来通知已登记的对象
+        protected virtual void OnNewMail(NewMailEventArgs e)
+        {
+            // 将字段复制到一个临时变量,避免多线程情况中这个成员被移除
+            EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);
+            if (temp != null) temp(this, e);
+        }
+
+        // 接受附加信息并调用引发事件的方法来通知所有登记的对象
+        public void SimulateNewMail(string from, string to, string content)
+        {
+            NewMailEventArgs e = new NewMailEventArgs(from, to, content);
+            OnNewMail(e);
+        }
+
+    }
+}

+ 23 - 0
EventDemo/NewMailEventArgs.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace EventDemo
+{
+    /// <summary>
+    /// 定义一个附加信息类,用来通知接收者发生了什么。 即 事件本身
+    /// </summary>
+    public class NewMailEventArgs :EventArgs
+    {
+        public string From { get; }
+        public string To { get; }
+        public string Content { get; }
+
+        public NewMailEventArgs(string from, string to, string content)
+        {
+            From = from;
+            To = to;
+            Content = content;
+        }
+    }
+}

+ 93 - 0
EventDemo/Program.cs

@@ -0,0 +1,93 @@
+using System;
+
+namespace EventDemo
+{
+    public delegate void ProcessDelegate(object sender, EventArgs e);
+    class Program
+    {
+      
+        public event ProcessDelegate ProcessEvent;
+
+        static void Main(string[] args)
+        {
+            //  方式1  委托方式创建事件
+            Swim swim = new Swim();//实例化发布者
+            MyClass myClass = new MyClass();//实例化订阅者
+            myClass.EventABS += new MyClass.DelegateABS<string>(swim.You);//事件的固定格式 事件名+= new 委托(发布者的方法)
+            myClass.OK("大家好!");
+
+            // 方式2 EventArgs 创建
+
+
+            /*  第一步执行  */
+            Test t = new Test();
+            /* 关联事件方法,相当于寻找到了委托人 */
+            t.ProcessEvent += new ProcessDelegate(t_ProcessEvent);
+            /* 进入Process方法 */
+            Console.WriteLine(t.Process());
+
+            //方式3
+            //实例化发布者
+            MailManager mm = new MailManager();
+            //向事件登记回调方法
+            Fax f = new Fax(mm);
+            mm.SimulateNewMail("a", "b", "Hello World!");
+            Console.ReadKey();
+        }
+        static void t_ProcessEvent(object sender, EventArgs e)
+        {
+            Test t = (Test)sender;
+            t.Text1 = "Hello";
+            t.Text2 = "World";
+        }
+    }
+    public class Test { 
+    
+        public string Text1 { get; set; }
+        public string Text2 { get; set; }
+        public event ProcessDelegate ProcessEvent;
+        void ProcessAction(object sender, EventArgs e)
+        {
+            //if (ProcessEvent == null)
+            //    ProcessEvent += new ProcessDelegate(t_ProcessEvent);
+            ProcessEvent(sender, e);
+        }
+        //如果没有自己指定关联方法,将会调用该方法抛出错误
+        //void t_ProcessEvent(object sender, EventArgs e)
+        //{
+        //    throw new Exception("The method or operation is not implemented.");
+        //}
+        void OnProcess()
+        {
+            ProcessAction(this, EventArgs.Empty);
+        }
+
+        public string Process()
+        {
+            OnProcess();
+            return Text1 + Text2;
+        }
+    }
+    class MyClass //定一个订阅者的类
+    {
+        //定义一个委托
+        public delegate void DelegateABS<T >(T a);
+
+
+
+        //定义一个事件
+        public event DelegateABS<string > EventABS;
+        //定义一个方法
+        public void OK(string a )
+        {
+            EventABS(a);//调用事件
+        }
+    }
+    class Swim//定义一个发布者
+    {
+        public void You(string a )
+        {
+            Console.WriteLine(a+"我喜欢游泳");
+        }
+    }
+}

+ 8 - 0
EventDemo1/EventDemo1.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 70 - 0
EventDemo1/Program.cs

@@ -0,0 +1,70 @@
+using System;
+
+namespace EventDemo1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            Console.WriteLine("Hello World!");
+            EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
+            subscribEvent v = new subscribEvent(); /* 实例化对象 */
+            e.ChangeNum += new EventTest.NumManipulationHandler(v.printf); /* 注册 */
+            e.SetValue(7);
+            e.SetValue(11);
+        }
+    }
+
+  /***********发布器类***********/
+  public class EventTest
+    {
+        private int value;
+
+        public delegate void NumManipulationHandler( int  a );
+
+
+        public event NumManipulationHandler ChangeNum;
+        protected virtual void OnNumChanged(int  a)
+        {
+            if (ChangeNum != null)
+            {
+                ChangeNum(a); /* 事件被触发 */
+            }
+            else
+            {
+                Console.WriteLine("event not fire");
+                Console.ReadKey(); /* 回车继续 */
+            }
+        }
+
+
+        public EventTest()
+        {
+            int n = 5;
+            SetValue(n);
+        }
+
+
+        public void SetValue(int n)
+        {
+            if (value != n)
+            {
+                value = n;
+                OnNumChanged(n);
+            }
+        }
+    }
+
+
+    /***********订阅器类***********/
+
+    public class subscribEvent
+    {
+        public void printf(int a )
+        {
+            Console.WriteLine(a+"event fire");
+            Console.ReadKey(); /* 回车继续 */
+        }
+    }
+
+}

+ 19 - 0
GrpcServiceServer/GrpcServiceServer.csproj

@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Protobuf Include="Protos\*.proto" GrpcServices="Server" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Remove="Protos\demo.proto" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Grpc.AspNetCore" Version="2.23.2" />
+  </ItemGroup>
+
+</Project>

+ 27 - 0
GrpcServiceServer/Program.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+
+namespace GrpcServiceServer
+{
+    public class Program
+    {
+        public static void Main(string[] args)
+        {
+            CreateHostBuilder(args).Build().Run();
+        }
+
+        // Additional configuration is required to successfully run gRPC on macOS.
+        // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
+        public static IHostBuilder CreateHostBuilder(string[] args) =>
+            Host.CreateDefaultBuilder(args)
+                .ConfigureWebHostDefaults(webBuilder =>
+                {
+                    webBuilder.UseStartup<Startup>();
+                });
+    }
+}

+ 12 - 0
GrpcServiceServer/Properties/launchSettings.json

@@ -0,0 +1,12 @@
+{
+  "profiles": {
+    "GrpcServiceServer": {
+      "commandName": "Project",
+      "launchBrowser": false,
+      "applicationUrl": "https://localhost:5001",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 24 - 0
GrpcServiceServer/Protos/demo.proto

@@ -0,0 +1,24 @@
+syntax ="proto3";
+option csharp_namespace="GrpcServer";
+package Demo;
+
+service Demoer{
+	rpc Chat (stream ChatMessage) returns (stream ChatMessage);
+	rpc SayHellos (DataMessage) returns (stream DataMessage);
+	rpc ClientStreamedData (stream DataMessage) returns (DataComplete);
+	rpc BufferAllData (stream DataMessage) returns (stream DataMessage);
+	rpc EchoAllData (stream DataMessage) returns (stream DataMessage);
+	rpc SayHello (DataMessage) returns (DataMessage);
+}
+message DataMessage {
+  bytes data = 1;
+  int32 serverDelayMilliseconds = 2;
+  string message=3;
+}
+message DataComplete {
+  int64 size = 1;
+}
+message ChatMessage {
+  string name = 1;
+  string message = 2;
+}

+ 21 - 0
GrpcServiceServer/Protos/greet.proto

@@ -0,0 +1,21 @@
+syntax = "proto3";
+
+option csharp_namespace = "GrpcServiceServer";
+
+package Greet;
+
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting
+  rpc SayHello (HelloRequest) returns (HelloReply);
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings.
+message HelloReply {
+  string message = 1;
+}

+ 192 - 0
GrpcServiceServer/Services/DemoerService.cs

@@ -0,0 +1,192 @@
+using Google.Protobuf;
+using Grpc.Core;
+using GrpcServer;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace GrpcServiceServer
+{
+    public class DemoerService :Demoer.DemoerBase
+    {
+        private readonly ILogger<DemoerService> _logger;
+        public DemoerService(ILogger<DemoerService> logger)
+        {
+            _logger = logger;
+        }
+
+        /// <summary>
+        /// 一元调用
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public override Task<DataMessage> SayHello(DataMessage request, ServerCallContext context)
+        {
+
+            Console.WriteLine(request.Message);
+            return Task.FromResult(new DataMessage
+            {
+                Message = "Hello " + request.Message
+            });
+        }
+
+
+        private static HashSet<IServerStreamWriter<ChatMessage>> _subscribers = new HashSet<IServerStreamWriter<ChatMessage>>();
+
+        /// <summary>
+        /// 双向流式处理调用
+        /// </summary>
+        /// <param name="requestStream"></param>
+        /// <param name="responseStream"></param>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public override Task Chat(IAsyncStreamReader<ChatMessage> requestStream, IServerStreamWriter<ChatMessage> responseStream, ServerCallContext context)
+        {
+            return ChatCore(requestStream, responseStream);
+        }
+
+        public static async Task ChatCore(IAsyncStreamReader<ChatMessage> requestStream, IServerStreamWriter<ChatMessage> responseStream)
+        {
+            if (!await requestStream.MoveNext())
+            {
+                // No messages so don't register and just exit.
+                return;
+            }
+
+            // Warning, the following is very racy
+            _subscribers.Add(responseStream);
+
+            do
+            {
+                await BroadcastMessageAsync(requestStream.Current);
+            } while (await requestStream.MoveNext());
+
+            _subscribers.Remove(responseStream);
+        }
+
+        private static async Task BroadcastMessageAsync(ChatMessage message)
+        {
+            message.Name= message.Name + " Say:";
+            foreach (var subscriber in _subscribers)
+            {
+                await subscriber.WriteAsync(message);
+            }
+        }
+
+        /// <summary>
+        /// 客户端流式处理调用
+        /// </summary>
+        /// <param name="requestStream"></param>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public override async Task<DataComplete> ClientStreamedData(
+            IAsyncStreamReader<DataMessage> requestStream,
+            ServerCallContext context)
+        {
+            var total = 0L;
+            await foreach (var message in requestStream.ReadAllAsync())
+            {
+                total += message.Data.Length;
+                Console.WriteLine(message.Message+" "+ message.Data.ToStringUtf8()+"    "+message.ServerDelayMilliseconds);
+                if (message.ServerDelayMilliseconds > 0)
+                {
+                    await Task.Delay(message.ServerDelayMilliseconds);
+                }
+            }
+            return new DataComplete
+            {
+                Size = total
+            };
+        }
+
+        public override async Task BufferAllData(
+            IAsyncStreamReader<DataMessage> requestStream,
+            IServerStreamWriter<DataMessage> responseStream,
+            ServerCallContext context)
+        {
+            // Read data into MemoryStream
+            var ms = new MemoryStream();
+            await foreach (var message in requestStream.ReadAllAsync())
+            {
+                ms.Write(message.Data.Span);
+                _logger.LogInformation($"Received {ms.Length} bytes");
+            }
+
+            // Write back to client in batches
+            var data = ms.ToArray();
+            var sent = 0;
+            while (sent < data.Length)
+            {
+                const int BatchSize = 1024 * 64; // 64 KB
+
+                var writeCount = Math.Min(data.Length - sent, BatchSize);
+                await responseStream.WriteAsync(new DataMessage
+                {
+                    Data = ByteString.CopyFrom(data, sent, writeCount)
+                });
+
+                sent += writeCount;
+                _logger.LogInformation($"Sent {sent} bytes");
+            }
+        }
+        /// <summary>
+        /// 服务器流式处理调用
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="responseStream"></param>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public override async Task SayHellos(DataMessage request, IServerStreamWriter<DataMessage> responseStream, ServerCallContext context)
+        {
+            // Explicitly send the response headers before any streamed content
+            Metadata responseHeaders = new Metadata
+            {
+                { "test-response-header", "value" }
+            };
+            await context.WriteResponseHeadersAsync(responseHeaders);
+
+            await SayHellosCore(request, responseStream);
+        }
+        public static async Task SayHellosCore(DataMessage request, IServerStreamWriter<DataMessage> responseStream)
+        {
+            for (var i = 0; i < 3; i++)
+            {
+                // Gotta look busy
+               // await Task.Delay(100);
+
+                var message = $"How are you {request.Message}? {i}";
+                await responseStream.WriteAsync(new DataMessage { Message = message });
+            }
+
+            // Gotta look busy
+          //  await Task.Delay(100);
+            await responseStream.WriteAsync(new DataMessage { Message = $"Goodbye {request.Message}!" });
+        }
+        public override async Task EchoAllData(
+            IAsyncStreamReader<DataMessage> requestStream,
+            IServerStreamWriter<DataMessage> responseStream,
+            ServerCallContext context)
+        {
+            var flushHeaders = context.RequestHeaders.Any(x => x.Key == "flush-headers");
+            if (flushHeaders)
+            {
+                await context.WriteResponseHeadersAsync(new Metadata());
+            }
+
+            await foreach (var message in requestStream.ReadAllAsync())
+            {
+
+                Console.WriteLine(message.Message);
+                await responseStream.WriteAsync(new DataMessage
+                {
+                    Message = message.Message + "aaaa"
+                }); ;
+            }
+        }
+
+    }
+}

+ 26 - 0
GrpcServiceServer/Services/GreeterService.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Microsoft.Extensions.Logging;
+
+namespace GrpcServiceServer
+{
+    public class GreeterService : Greeter.GreeterBase
+    {
+        private readonly ILogger<GreeterService> _logger;
+        public GreeterService(ILogger<GreeterService> logger)
+        {
+            _logger = logger;
+        }
+
+        public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
+        {
+            return Task.FromResult(new HelloReply
+            {
+                Message = "Hello " + request.Name
+            });
+        }
+    }
+}

+ 43 - 0
GrpcServiceServer/Startup.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace GrpcServiceServer
+{
+    public class Startup
+    {
+        // This method gets called by the runtime. Use this method to add services to the container.
+        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+        public void ConfigureServices(IServiceCollection services)
+        {
+            services.AddGrpc();
+        }
+
+        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+        {
+            if (env.IsDevelopment())
+            {
+                app.UseDeveloperExceptionPage();
+            }
+
+            app.UseRouting();
+
+            app.UseEndpoints(endpoints =>
+            {
+                endpoints.MapGrpcService<GreeterService>();
+                endpoints.MapGrpcService<DemoerService>();
+                endpoints.MapGet("/", async context =>
+                {
+                    await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
+                });
+            });
+        }
+    }
+}

+ 10 - 0
GrpcServiceServer/appsettings.Development.json

@@ -0,0 +1,10 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Debug",
+      "System": "Information",
+      "Grpc": "Information",
+      "Microsoft": "Information"
+    }
+  }
+}

+ 14 - 0
GrpcServiceServer/appsettings.json

@@ -0,0 +1,14 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Warning",
+      "Microsoft.Hosting.Lifetime": "Information"
+    }
+  },
+  "AllowedHosts": "*",
+  "Kestrel": {
+    "EndpointDefaults": {
+      "Protocols": "Http2"
+    }
+  }
+}

+ 13 - 0
JsonRPCTest/Controllers/Item.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace JsonRPCTest.Controllers
+{
+    public class Item
+    {
+        public string aaa { get; set; }
+        public string ccc { get; set; }
+    }
+}

+ 206 - 0
JsonRPCTest/Controllers/JsonNetHelper.cs

@@ -0,0 +1,206 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+namespace JsonRPCTest.Controllers
+{
+    public static class JsonNetHelper
+    {
+        static JsonSerializerSettings settings = new JsonSerializerSettings()
+        {
+            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+            PreserveReferencesHandling = PreserveReferencesHandling.None
+        };
+
+        /// <summary>
+        /// 使用json序列化为字符串
+        /// </summary>
+        /// <param name="dateTimeFormat">默认null,即使用json.net默认的序列化机制,如:"\/Date(1439335800000+0800)\/"</param>
+        /// <returns></returns>
+        public static string ToJson(this object input, string dateTimeFormat = "yyyy-MM-dd HH:mm:ss", bool ignoreNullValue = true, bool isIndented = false)
+        {
+            settings.NullValueHandling = ignoreNullValue ? Newtonsoft.Json.NullValueHandling.Ignore : NullValueHandling.Include;
+
+            if (!string.IsNullOrWhiteSpace(dateTimeFormat))
+            {
+                var jsonConverter = new List<JsonConverter>()
+                {
+                    new Newtonsoft.Json.Converters.IsoDateTimeConverter(){ DateTimeFormat = dateTimeFormat }//如: "yyyy-MM-dd HH:mm:ss"
+                };
+                settings.Converters = jsonConverter;
+            }
+
+            //no format
+            var format = isIndented ? Newtonsoft.Json.Formatting.Indented : Formatting.None;
+            var json = JsonConvert.SerializeObject(input, format, settings);
+            return json;
+        }
+        /// <summary>
+        /// 使用json序列化为字符串
+        /// </summary>
+        /// <param name="dateTimeFormat">默认null,即使用json.net默认的序列化机制,如:"\/Date(1439335800000+0800)\/"</param>
+        /// <returns></returns>
+        public static string ToJsonAbs(this object input, string dateTimeFormat = "yyyy-MM-dd HH:mm:ss", bool ignoreNullValue = true, bool isIndented = false)
+        {
+
+            settings = new JsonSerializerSettings
+            {
+                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+                TypeNameHandling = TypeNameHandling.All
+            };
+            settings.NullValueHandling = ignoreNullValue ? Newtonsoft.Json.NullValueHandling.Ignore : NullValueHandling.Include;
+
+            if (!string.IsNullOrWhiteSpace(dateTimeFormat))
+            {
+                var jsonConverter = new List<JsonConverter>()
+                {
+                    new Newtonsoft.Json.Converters.IsoDateTimeConverter(){ DateTimeFormat = dateTimeFormat }//如: "yyyy-MM-dd HH:mm:ss"
+                };
+                settings.Converters = jsonConverter;
+            }
+
+            //no format
+            var format = isIndented ? Newtonsoft.Json.Formatting.Indented : Formatting.None;
+            var json = JsonConvert.SerializeObject(input, format, settings);
+            return json;
+        }
+        /// <summary>
+        /// 从序列化字符串里反序列化
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="input"></param>
+        /// <param name="dateTimeFormat">默认null,即使用json.net默认的序列化机制</param>
+        /// <returns></returns>
+        public static T FromJsonAbs<T>(this string input, string dateTimeFormat = "yyyy-MM-dd HH:mm:ss", bool ignoreNullValue = true)
+        {
+            var settings = new JsonSerializerSettings()
+            {
+                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
+                TypeNameHandling = TypeNameHandling.All
+            };
+            settings.NullValueHandling = ignoreNullValue ? Newtonsoft.Json.NullValueHandling.Ignore : NullValueHandling.Include;
+
+            if (!string.IsNullOrWhiteSpace(dateTimeFormat))
+            {
+                var jsonConverter = new List<JsonConverter>()
+                {
+                    new Newtonsoft.Json.Converters.IsoDateTimeConverter(){ DateTimeFormat = dateTimeFormat }//如: "yyyy-MM-dd HH:mm:ss"
+                };
+                settings.Converters = jsonConverter;
+            }
+
+            return JsonConvert.DeserializeObject<T>(input, settings);
+        }
+        /// <summary>
+        /// 从序列化字符串里反序列化
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="input"></param>
+        /// <param name="dateTimeFormat">默认null,即使用json.net默认的序列化机制</param>
+        /// <returns></returns>
+        public static T TryFromJson<T>(this string input, string dateTimeFormat = "yyyy-MM-dd HH:mm:ss", bool ignoreNullValue = true)
+        {
+            try
+            {
+                return input.FromJson<T>(dateTimeFormat, ignoreNullValue);
+            }
+            catch
+            {
+                return default(T);
+            }
+        }
+
+
+        /// <summary>
+        /// 从字典获取对象
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="input"></param>
+        /// <param name="dateTimeFormat">默认null,即使用json.net默认的序列化机制</param>
+        /// <returns></returns>
+        public static T DictToObj<T>(this Dictionary<string, object> dict, string dateTimeFormat = "yyyy-MM-dd HH:mm:ss", bool ignoreNullValue = true)
+        {
+            try
+            {
+                string input = ToJson(dict);
+                return input.FromJson<T>(dateTimeFormat, ignoreNullValue);
+            }
+            catch
+            {
+                return default(T);
+            }
+        }
+        /// <summary>
+        /// 从对象获取字典
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <param name="dateTimeFormat"></param>
+        /// <param name="ignoreNullValue"></param>
+        /// <returns></returns>
+        public static Dictionary<string, object> ObjToDict(this object obj, string dateTimeFormat = "yyyy-MM-dd HH:mm:ss", bool ignoreNullValue = true)
+        {
+            string input = ToJson(obj);
+            return input.FromJson<Dictionary<string, object>>(dateTimeFormat, ignoreNullValue);
+        }
+
+        /// <summary>
+        /// 从序列化字符串里反序列化
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="input"></param>
+        /// <param name="dateTimeFormat">默认null,即使用json.net默认的序列化机制</param>
+        /// <returns></returns>
+        public static T FromJson<T>(this string input, string dateTimeFormat = "yyyy-MM-dd HH:mm:ss", bool ignoreNullValue = true)
+        {
+            var settings = new JsonSerializerSettings()
+            {
+                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
+            };
+            settings.NullValueHandling = ignoreNullValue ? Newtonsoft.Json.NullValueHandling.Ignore : NullValueHandling.Include;
+
+            if (!string.IsNullOrWhiteSpace(dateTimeFormat))
+            {
+                var jsonConverter = new List<JsonConverter>()
+                {
+                    new Newtonsoft.Json.Converters.IsoDateTimeConverter(){ DateTimeFormat = dateTimeFormat }//如: "yyyy-MM-dd HH:mm:ss"
+                };
+                settings.Converters = jsonConverter;
+            }
+
+            return JsonConvert.DeserializeObject<T>(input, settings);
+        }
+        /// <summary>
+        /// 从序列化字符串里反序列化
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="input"></param>
+        /// <param name="dateTimeFormat">默认null,即使用json.net默认的序列化机制</param>
+        /// <returns></returns>
+        public static object FromJson(this string input, Type type, string dateTimeFormat = "yyyy-MM-dd HH:mm:ss", bool ignoreNullValue = true)
+        {
+            var settings = new JsonSerializerSettings()
+            {
+                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
+            };
+            if (ignoreNullValue)
+            {
+                settings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
+            }
+
+            if (!string.IsNullOrWhiteSpace(dateTimeFormat))
+            {
+                var jsonConverter = new List<JsonConverter>()
+                {
+                    new Newtonsoft.Json.Converters.IsoDateTimeConverter(){ DateTimeFormat = dateTimeFormat }//如: "yyyy-MM-dd HH:mm:ss"
+                };
+                settings.Converters = jsonConverter;
+            }
+
+            return JsonConvert.DeserializeObject(input, type, settings);
+        }
+    }
+}

+ 66 - 0
JsonRPCTest/Controllers/WeatherForecastController.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EdjCase.JsonRpc.Router;
+using EdjCase.JsonRpc.Router.Abstractions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json.Linq;
+
+namespace JsonRPCTest.Controllers
+{
+    [RpcRoute]
+    public class WeatherForecastController : RpcController
+    {
+        private static readonly string[] Summaries = new[]
+        {
+            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
+        };
+
+        private readonly ILogger<WeatherForecastController> _logger;
+
+        public WeatherForecastController(ILogger<WeatherForecastController> logger)
+        {
+            _logger = logger;
+        }
+
+         
+        public Task<IRpcMethodResult> Get(Item item ,JObject bbb)
+        {
+            var rng = new Random();
+            var aps= Enumerable.Range(1, 5).Select(index => new WeatherForecast
+            {
+                Date = DateTime.Now.AddDays(index),
+                TemperatureC = rng.Next(-20, 55),
+                Summary = Summaries[rng.Next(Summaries.Length)]
+            })
+            .ToArray();
+            return Task.Run<IRpcMethodResult>(() => Ok(aps));
+        }
+        public Task<IRpcMethodResult> Get(Item item)
+        {
+            var rng = new Random();
+            var aps = Enumerable.Range(1, 5).Select(index => new WeatherForecast
+            {
+                Date = DateTime.Now.AddDays(index),
+                TemperatureC = rng.Next(-20, 55),
+                Summary = Summaries[rng.Next(Summaries.Length)]
+            })
+            .ToArray();
+            return Task.Run<IRpcMethodResult>(() => Ok(aps));
+        }
+        public Task<IRpcMethodResult> Get(string aaa ,string ccc)
+        {
+            var rng = new Random();
+            var aps = Enumerable.Range(1, 5).Select(index => new WeatherForecast
+            {
+                Date = DateTime.Now.AddDays(index),
+                TemperatureC = rng.Next(-20, 55),
+                Summary = Summaries[rng.Next(Summaries.Length)]
+            })
+            .ToArray();
+            return Task.Run<IRpcMethodResult>(() => Ok(aps));
+        }
+    }
+}

+ 11 - 0
JsonRPCTest/JsonRPCTest.csproj

@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
+  </ItemGroup>
+</Project>

+ 26 - 0
JsonRPCTest/Program.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace JsonRPCTest
+{
+    public class Program
+    {
+        public static void Main(string[] args)
+        {
+            CreateHostBuilder(args).Build().Run();
+        }
+
+        public static IHostBuilder CreateHostBuilder(string[] args) =>
+            Host.CreateDefaultBuilder(args)
+                .ConfigureWebHostDefaults(webBuilder =>
+                {
+                    webBuilder.UseStartup<Startup>();
+                });
+    }
+}

+ 30 - 0
JsonRPCTest/Properties/launchSettings.json

@@ -0,0 +1,30 @@
+{
+  "$schema": "http://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:51632",
+      "sslPort": 44364
+    }
+  },
+  "profiles": {
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": false,
+      "launchUrl": "weatherforecast",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "JsonRPCTest": {
+      "commandName": "Project",
+      "launchBrowser": false,
+      "launchUrl": "weatherforecast",
+      "applicationUrl": "https://localhost:5001;http://localhost:5000",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 34 - 0
JsonRPCTest/RpcCore/Constants.cs

@@ -0,0 +1,34 @@
+namespace EdjCase.JsonRpc.Core
+{
+	/// <summary>
+	/// Error codes for different Rpc errors
+	/// </summary>
+	public enum RpcErrorCode
+	{
+		ParseError = -32700,
+		InvalidRequest = -32600,
+		MethodNotFound = -32601,
+		InvalidParams = -32602,
+		InternalError = -32603
+	}
+
+	public static class JsonRpcContants
+	{		
+		public const string VersionPropertyName = "jsonrpc";
+		public const string MethodPropertyName = "method";
+		public const string ParamsPropertyName = "params";
+		public const string IdPropertyName = "id";
+		public const string ResultPropertyName = "result";
+		public const string ErrorPropertyName = "error";
+		public const string ErrorCodePropertyName = "code";
+		public const string ErrorMessagePropertyName = "message";
+		public const string ErrorDataPropertyName = "data";
+	}
+
+
+	public enum CompressionType
+	{
+		Gzip,
+		Deflate
+	}
+}

+ 49 - 0
JsonRPCTest/RpcCore/RpcExceptions.cs

@@ -0,0 +1,49 @@
+using System;
+
+namespace EdjCase.JsonRpc.Core
+{
+	/// <summary>
+	/// Rpc server exception that contains Rpc specfic error info
+	/// </summary>
+	public class RpcException : Exception
+	{
+		/// <summary>
+		/// Rpc error code that corresponds to the documented integer codes
+		/// </summary>
+		public int ErrorCode { get; }
+		/// <summary>
+		/// Custom data attached to the error if needed
+		/// </summary>
+		public object RpcData { get; }
+		
+		/// <param name="errorCode">Rpc error code</param>
+		/// <param name="message">Error message</param>
+		/// <param name="data">Custom data if needed for error response</param>
+		/// <param name="innerException">Inner exception (optional)</param>
+		public RpcException(int errorCode, string message, Exception innerException = null, object data = null)
+			: base(message, innerException)
+		{
+			this.ErrorCode = errorCode;
+			this.RpcData = data;
+		}
+		/// <param name="errorCode">Rpc error code</param>
+		/// <param name="message">Error message</param>
+		/// <param name="data">Custom data if needed for error response</param>
+		/// <param name="innerException">Inner exception (optional)</param>
+		public RpcException(RpcErrorCode errorCode, string message, Exception innerException = null, object data = null)
+			: this((int)errorCode, message, innerException, data)
+		{
+		}
+
+		public RpcError ToRpcError(bool includeServerErrors)
+		{
+			string message = this.Message;
+			if (includeServerErrors)
+			{
+				message += Environment.NewLine + "Exception: " + this.InnerException;
+			}
+			return new RpcError(this.ErrorCode, this.Message, this.RpcData);
+		}
+	}
+	
+}

+ 162 - 0
JsonRPCTest/RpcCore/RpcId.cs

@@ -0,0 +1,162 @@
+using EdjCase.JsonRpc.Core.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace EdjCase.JsonRpc.Core
+{
+	public enum RpcIdType
+	{
+		String,
+		Number
+	}
+
+	public struct RpcId : IEquatable<RpcId>
+	{
+		public bool HasValue { get; }
+
+		public RpcIdType Type { get; }
+
+		public object Value { get; }
+
+		public double NumberValue
+		{
+			get
+			{
+				if (this.Type != RpcIdType.Number)
+				{
+					throw new InvalidOperationException("Cannot cast id to number.");
+				}
+				return (double)this.Value;
+			}
+		}
+
+		public string StringValue
+		{
+			get
+			{
+				if (this.Type != RpcIdType.String)
+				{
+					throw new InvalidOperationException("Cannot cast id to string.");
+				}
+				return (string)this.Value;
+			}
+		}
+
+		public RpcId(string id)
+		{
+			this.HasValue = id != null;
+			this.Value = id;
+			this.Type = RpcIdType.String;
+		}
+
+		public RpcId(double id)
+		{
+			this.HasValue = true;
+			this.Value = id;
+			this.Type = RpcIdType.Number;
+		}
+
+		public static bool operator ==(RpcId x, RpcId y)
+		{
+			return x.Equals(y);
+		}
+
+		public static bool operator !=(RpcId x, RpcId y)
+		{
+			return !x.Equals(y);
+		}
+
+		public bool Equals(RpcId other)
+		{
+			if (this.HasValue && other.HasValue)
+			{
+				return true;
+			}
+			if (this.HasValue || other.HasValue)
+			{
+				return false;
+			}
+			if (this.Type != other.Type)
+			{
+				return false;
+			}
+			switch (this.Type)
+			{
+				case RpcIdType.Number:
+					return this.NumberValue == other.NumberValue;
+				case RpcIdType.String:
+					return this.StringValue == other.StringValue;
+				default:
+					throw new ArgumentOutOfRangeException(nameof(this.Type));
+			}
+
+		}
+
+		public override bool Equals(object obj)
+		{
+			if (obj is RpcId id)
+			{
+				return this.Equals(id);
+			}
+			return false;
+		}
+
+		public override int GetHashCode()
+		{
+			if (!this.HasValue)
+			{
+				return 0;
+			}
+			return this.Value.GetHashCode();
+		}
+
+		public override string ToString()
+		{
+			if (!this.HasValue)
+			{
+				return string.Empty;
+			}
+			switch (this.Type)
+			{
+				case RpcIdType.Number:
+					return this.Value.ToString();
+				case RpcIdType.String:
+					return "'" + (string)this.Value + "'";
+				default:
+					throw new ArgumentOutOfRangeException(nameof(this.Type));
+			}
+		}
+
+		public static implicit operator RpcId(double id)
+		{
+			return new RpcId(id);
+		}
+
+		public static implicit operator RpcId(string id)
+		{
+			return new RpcId(id);
+		}
+
+		public static RpcId FromObject(object value)
+		{
+			if (value == null)
+			{
+				return default;
+			}
+			if(value is RpcId rpcId)
+			{
+				return rpcId;
+			}
+			if(value is string stringValue)
+			{
+				return new RpcId(stringValue);
+			}
+			if (value.GetType().IsNumericType())
+			{
+				return new RpcId(Convert.ToDouble(value));
+			}
+			throw new RpcException(RpcErrorCode.InvalidRequest, "Id must be a string, a number or null.");
+		}
+	}
+}

+ 93 - 0
JsonRPCTest/RpcCore/RpcParameters.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace EdjCase.JsonRpc.Core
+{
+	public enum RpcParametersType
+	{
+		Array,
+		Dictionary
+	}
+
+	public struct RpcParameters
+	{
+		public bool HasValue { get; }
+
+		public RpcParametersType Type { get; }
+
+		public object Value { get; }
+
+		public Dictionary<string, object> DictionaryValue
+		{
+			get
+			{
+				if (this.Type != RpcParametersType.Dictionary)
+				{
+					throw new InvalidOperationException("Cannot cast params to dictionary.");
+				}
+				return (Dictionary<string, object>)this.Value;
+			}
+		}
+
+		public object[] ArrayValue
+		{
+			get
+			{
+				if (this.Type != RpcParametersType.Array)
+				{
+					throw new InvalidOperationException("Cannot cast params to array.");
+				}
+				return (object[])this.Value;
+			}
+		}
+
+		public RpcParameters(object[] parameters)
+		{
+			this.HasValue = true;
+			this.Type = RpcParametersType.Array;
+			this.Value = parameters ?? new object[0];
+		}
+
+		public RpcParameters(Dictionary<string, object> parameters)
+		{
+			this.HasValue = true;
+			this.Type = RpcParametersType.Dictionary;
+			this.Value = parameters ?? new Dictionary<string, object>();
+		}
+
+		public static RpcParameters Empty => new RpcParameters(new object[0]);
+
+		public static RpcParameters FromList(IEnumerable<object> parameters)
+		{
+			return new RpcParameters(parameters?.ToArray());
+		}
+
+		public static RpcParameters From(params object[] parameters)
+		{
+			return new RpcParameters(parameters);
+		}
+
+		public static RpcParameters FromDictionary(IDictionary<string, object> parameters)
+		{
+			return new RpcParameters(parameters?.ToDictionary(kv => kv.Key, kv => kv.Value));
+		}
+
+		public static implicit operator RpcParameters(List<object> parameters)
+		{
+			return new RpcParameters(parameters?.ToArray());
+		}
+
+
+		public static implicit operator RpcParameters(object[] parameters)
+		{
+			return new RpcParameters(parameters);
+		}
+
+		public static implicit operator RpcParameters(Dictionary<string, object> parameters)
+		{
+			return new RpcParameters(parameters);
+		}
+	}
+}

+ 75 - 0
JsonRPCTest/RpcCore/RpcRequest.cs

@@ -0,0 +1,75 @@
+using System.Collections.Generic;
+using System.Linq;
+using System;
+
+// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
+// ReSharper disable UnusedMember.Local
+
+namespace EdjCase.JsonRpc.Core
+{
+	/// <summary>
+	/// Model representing a Rpc request
+	/// </summary>
+	public class RpcRequest
+	{
+		/// <param name="id">Request id</param>
+		/// <param name="method">Target method name</param>
+		/// <param name="parameterList">Json parameters for the target method</param>
+		public RpcRequest(RpcId id, string method, RpcParameters parameters = default)
+		{
+			this.Id = id;
+			this.Method = method;
+			this.Parameters = parameters;
+		}
+		
+		/// <param name="method">Target method name</param>
+		/// <param name="parameterList">Json parameters for the target method</param>
+		public RpcRequest(string method, RpcParameters parameters = default)
+		{
+			this.Id = null;
+			this.Method = method;
+			this.Parameters = parameters;
+		}
+
+		/// <summary>
+		/// Request Id (Optional)
+		/// </summary>
+		public RpcId Id { get; private set; }
+		/// <summary>
+		/// Name of the target method (Required)
+		/// </summary>
+		public string Method { get; private set; }
+		/// <summary>
+		/// Parameters to invoke the method with (Optional)
+		/// </summary>
+		public RpcParameters Parameters { get; private set; }
+
+		public static RpcRequest WithNoParameters(string method, RpcId id = default)
+		{
+			return RpcRequest.WithParameters(method, default, id);
+		}
+
+		public static RpcRequest WithParameterList(string method, IList<object> parameterList, RpcId id = default)
+		{
+			parameterList = parameterList ?? new object[0];
+			RpcParameters parameters = RpcParameters.FromList(parameterList);
+			return RpcRequest.WithParameters(method, parameters, id);
+		}
+
+		public static RpcRequest WithParameterMap(string method, IDictionary<string, object> parameterDictionary, RpcId id = default)
+		{
+			parameterDictionary = parameterDictionary ?? new Dictionary<string, object>();
+			RpcParameters parameters = RpcParameters.FromDictionary(parameterDictionary);
+			return RpcRequest.WithParameters(method, parameters, id);
+		}
+
+		public static RpcRequest WithParameters(string method, RpcParameters parameters, RpcId id = default)
+		{
+			if(method == null)
+			{
+				throw new ArgumentNullException(nameof(method));
+			}
+			return new RpcRequest(id, method, parameters);
+		}
+	}
+}

+ 157 - 0
JsonRPCTest/RpcCore/RpcResponse.cs

@@ -0,0 +1,157 @@
+using System;
+
+// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
+// ReSharper disable UnusedMember.Local
+
+namespace EdjCase.JsonRpc.Core
+{
+	public class RpcResponse<T> : RpcResponse
+	{
+		public RpcResponse(RpcId id, T result)
+			:base(id, result, typeof(T))
+		{
+
+		}
+
+		public RpcResponse(RpcId id, RpcError error)
+			:base(id, error)
+		{
+			
+		}
+
+		public new T Result => (T)base.Result;
+		
+
+		public static RpcResponse<T> FromResponse(RpcResponse response)
+		{
+			if (response.HasError)
+			{
+				return new RpcResponse<T>(response.Id, response.Error);
+			}
+			return new RpcResponse<T>(response.Id, (T)response.Result);
+		}
+	}
+
+	public class RpcResponse
+	{
+		protected RpcResponse()
+		{
+		}
+
+		/// <param name="id">Request id</param>
+		protected RpcResponse(RpcId id)
+		{
+			this.Id = id;
+		}
+
+		/// <param name="id">Request id</param>
+		/// <param name="error">Request error</param>
+		public RpcResponse(RpcId id, RpcError error) : this(id)
+		{
+			this.Error = error;
+		}
+
+		/// <param name="id">Request id</param>
+		/// <param name="result">Response result object</param>
+		public RpcResponse(RpcId id, object result, Type resultType) : this(id)
+		{
+			this.Result = result;
+			this.ResultType = resultType;
+		}
+
+		/// <summary>
+		/// Request id
+		/// </summary>
+		public RpcId Id { get; private set; }
+
+		/// <summary>
+		/// Reponse result object (Required)
+		/// </summary>
+		public object Result { get; private set; }
+
+		/// <summary>
+		/// Error from processing Rpc request (Required)
+		/// </summary>
+		public RpcError Error { get; private set; }
+
+		public bool HasError => this.Error != null;
+
+		public Type ResultType { get; }
+
+		public void ThrowErrorIfExists()
+		{
+			if (this.HasError)
+			{
+				throw this.Error.CreateException();
+			}
+		}
+	}
+
+	public class RpcError<T> : RpcError
+	{
+		public RpcError(RpcErrorCode code, string message, T data)
+			: base(code, message, data)
+		{
+		}
+
+		public RpcError(int code, string message, T data)
+			: base(code, message, data)
+		{
+		}
+
+		public new T Data => (T)base.Data;
+	}
+
+	/// <summary>
+	/// Model to represent an Rpc response error
+	/// </summary>
+	public class RpcError
+	{
+
+		/// <param name="code">Rpc error code</param>
+		/// <param name="message">Error message</param>
+		/// <param name="data">Optional error data</param>
+		public RpcError(RpcErrorCode code, string message, object data = null)
+			: this((int)code, message, data)
+		{
+		}
+
+		/// <param name="code">Rpc error code</param>
+		/// <param name="message">Error message</param>
+		/// <param name="data">Optional error data</param>
+		public RpcError(int code, string message, object data = null)
+		{
+			if (string.IsNullOrWhiteSpace(message))
+			{
+				throw new ArgumentNullException(nameof(message));
+			}
+			this.Code = code;
+			this.Message = message;
+			this.Data = data;
+			this.DataType = data?.GetType();
+		}
+
+		/// <summary>
+		/// Rpc error code (Required)
+		/// </summary>
+		public int Code { get; }
+
+		public string Message { get; }
+
+		/// <summary>
+		/// Error data (Optional)
+		/// </summary>
+		public object Data { get; }
+
+		/// <summary>
+		/// Type of the data object
+		/// </summary>
+		public Type DataType { get; }
+
+		public RpcException CreateException()
+		{
+			return new RpcException(this.Code, this.Message, data: this.Data);
+		}
+
+	}
+}

+ 88 - 0
JsonRPCTest/RpcCore/Tools/StreamCompressors.cs

@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EdjCase.JsonRpc.Core.Tools
+{
+	public interface IStreamCompressor
+	{
+		void Compress(Stream inputStream, Stream outputStream, CompressionType compressionType);
+		void Decompress(Stream inputStream, Stream outputStream, CompressionType compressionType);
+	}
+
+	public class DefaultStreamCompressor : IStreamCompressor
+	{
+		/// <summary>
+		/// Decompresses the input stream to the output stream.
+		/// </summary>
+		/// <param name="inputStream">The input stream to decompress.</param>
+		/// <param name="inputStream">The output stream to write to.</param>
+		/// <param name="compressionType">Type of the compression.</param>
+		/// <returns></returns>
+		public void Decompress(Stream inputStream, Stream outputStream, CompressionType compressionType)
+		{
+			Stream compressionStream = null;
+			try
+			{
+				switch (compressionType)
+				{
+					case CompressionType.Gzip:
+						compressionStream = new GZipStream(inputStream, CompressionMode.Decompress, leaveOpen: true);
+						break;
+					case CompressionType.Deflate:
+						compressionStream = new DeflateStream(inputStream, CompressionMode.Decompress, leaveOpen: true);
+						break;
+					default:
+						throw new ArgumentOutOfRangeException(nameof(compressionType), compressionType, null);
+				}
+				compressionStream.CopyTo(outputStream);
+				outputStream.Position = 0;
+			}
+			finally
+			{
+				compressionStream?.Dispose();
+			}
+		}
+
+		/// <summary>
+		/// Compresses the input stream to the output stream.
+		/// </summary>
+		/// <param name="inputStream">The input stream to compress.</param>
+		/// <param name="inputStream">The output stream to write to.</param>
+		/// <param name="compressionType">Type of the compression.</param>
+		/// <returns></returns>
+		public   void Compress(Stream inputStream, Stream outputStream, CompressionType compressionType)
+		{
+			long intialPosition = inputStream.Position;
+			Stream compressionStream = null;
+			try
+			{
+				compressionStream = new DeflateStream(outputStream, CompressionMode.Compress, leaveOpen: true);
+				//switch (compressionType)
+				//{
+				//	case CompressionType.Gzip:
+				//		compressionStream = new GZipStream(outputStream, CompressionMode.Compress, leaveOpen: true);
+				//		break;
+				//	case CompressionType.Deflate:
+				//		compressionStream = new DeflateStream(outputStream, CompressionMode.Compress, leaveOpen: true);
+				//		break;
+				//	default:
+				//		throw new ArgumentOutOfRangeException(nameof(compressionType), compressionType, null);
+				//}
+				
+			 	inputStream.CopyTo(compressionStream);
+			}
+			catch(Exception e) {
+				Console.WriteLine(e.StackTrace);
+			}
+			finally
+			{
+				//compressionStream?.Dispose();
+				inputStream.Position = intialPosition;
+			}
+		}
+	}
+}

+ 34 - 0
JsonRPCTest/RpcCore/Utilities/ExtensionsUtil.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace EdjCase.JsonRpc.Core.Utilities
+{
+	public static class TypeExtensions
+	{
+		/// <summary>
+		/// Determines if the type is a number
+		/// </summary>
+		/// <param name="type">Type of the object</param>
+		/// <param name="includeInteger">Includes a check for whole number types. Defaults to true</param>
+		/// <returns>True if the type is a number, otherwise False</returns>
+		public static bool IsNumericType(this Type type, bool includeInteger = true)
+		{
+			if (includeInteger)
+			{
+				return type == typeof(long)
+						|| type == typeof(int)
+						|| type == typeof(short)
+						|| type == typeof(float)
+						|| type == typeof(double)
+						|| type == typeof(decimal);
+			}
+			else
+			{
+				return type == typeof(float)
+						|| type == typeof(double)
+						|| type == typeof(decimal);
+			}
+		}
+	}
+}

+ 10 - 0
JsonRPCTest/RpcRouter/Abstractions/IRouteContext.cs

@@ -0,0 +1,10 @@
+using EdjCase.JsonRpc.Router.Abstractions;
+using System;
+using System.Security.Claims;
+
+public interface IRouteContext
+{
+	IServiceProvider RequestServices { get; }
+	IRpcRouteProvider RouteProvider { get; }
+	ClaimsPrincipal User { get; }
+}

+ 29 - 0
JsonRPCTest/RpcRouter/Abstractions/IRpcInvoker.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using EdjCase.JsonRpc.Core;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace EdjCase.JsonRpc.Router.Abstractions
+{
+	public interface IRpcInvoker
+	{
+		/// <summary>
+		/// Call the incoming Rpc request method and gives the appropriate response
+		/// </summary>
+		/// <param name="request">Rpc request</param>
+		/// <param name="path">Rpc path that applies to the current request</param>
+		/// <param name="routeContext">The context of the current rpc request</param>
+		/// <returns>An Rpc response for the request</returns>
+		Task<RpcResponse> InvokeRequestAsync(RpcRequest request, RpcPath path, IRouteContext routeContext);
+
+		/// <summary>
+		/// Call the incoming Rpc requests methods and gives the appropriate respones
+		/// </summary>
+		/// <param name="requests">List of Rpc requests to invoke</param>
+		/// <param name="path">Rpc route that applies to the current request</param>
+		/// <param name="routeContext">The context of the current rpc request</param>
+		/// <returns>List of Rpc responses for the requests</returns>
+		Task<List<RpcResponse>> InvokeBatchRequestAsync(IList<RpcRequest> requests, RpcPath path, IRouteContext routeContext);
+	}
+}

+ 22 - 0
JsonRPCTest/RpcRouter/Abstractions/IRpcMethodResult.cs

@@ -0,0 +1,22 @@
+using EdjCase.JsonRpc.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EdjCase.JsonRpc.Router.Abstractions
+{
+	/// <summary>
+	/// Rpc method response object that allows the server to customize an rpc response
+	/// </summary>
+	public interface IRpcMethodResult
+	{
+		/// <summary>
+		/// Turns result data into a rpc response
+		/// </summary>
+		/// <param name="id">Rpc request id</param>
+		/// <param name="serializer">Json serializer function to use for objects for the response</param>
+		/// <returns>Rpc response for request</returns>
+		RpcResponse ToRpcResponse(RpcId id);
+	}
+}

+ 17 - 0
JsonRPCTest/RpcRouter/Abstractions/IRpcParser.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using Edjcase.JsonRpc.Router;
+using EdjCase.JsonRpc.Core;
+
+namespace EdjCase.JsonRpc.Router.Abstractions
+{
+	public interface IRpcParser
+	{
+		/// <summary>
+		/// Parses all the requests from the json in the request
+		/// </summary>
+		/// <param name="jsonString">Json from the http request</param>
+		/// <returns>Result of the parsing. Includes all the parsed requests and any errors</returns>
+		ParsingResult ParseRequests(string jsonString);
+	}
+}

+ 13 - 0
JsonRPCTest/RpcRouter/Abstractions/IRpcRequestHandler.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EdjCase.JsonRpc.Router.Abstractions
+{
+	public interface IRpcRequestHandler
+	{
+		Task<string> HandleRequestAsync(RpcPath requestPath, string requestBody, IRouteContext routeContext);
+	}
+}

+ 11 - 0
JsonRPCTest/RpcRouter/Abstractions/IRpcRequestMatcher.cs

@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using System.Reflection;
+using EdjCase.JsonRpc.Core;
+
+namespace EdjCase.JsonRpc.Router.Abstractions
+{
+	public interface IRpcRequestMatcher
+	{
+		List<RpcMethodInfo> FilterAndBuildMethodInfoByRequest(List<MethodInfo> methods, RpcRequest request);
+	}
+}

+ 13 - 0
JsonRPCTest/RpcRouter/Abstractions/IRpcResponseSerializer.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using EdjCase.JsonRpc.Core;
+
+namespace EdjCase.JsonRpc.Router.Abstractions
+{
+	public interface IRpcResponseSerializer
+	{
+		string Serialize(RpcResponse response);
+		string SerializeBulk(IEnumerable<RpcResponse> responses);
+	}
+}

+ 62 - 0
JsonRPCTest/RpcRouter/Abstractions/IRpcRouteProvider.cs

@@ -0,0 +1,62 @@
+using EdjCase.JsonRpc.Router.MethodProviders;
+using Microsoft.AspNetCore.Authorization;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace EdjCase.JsonRpc.Router.Abstractions
+{
+	/// <summary>
+	/// Interface to provide the middleware with available routes and their criteria
+	/// </summary>
+	public interface IRpcRouteProvider
+	{
+		RpcPath BaseRequestPath { get; }
+		List<IRpcMethodProvider> GetMethodsByPath(RpcPath path);
+	}
+
+#if !NETSTANDARD1_3
+	public class RpcAutoRoutingOptions
+	{
+		/// <summary>
+		/// Sets the required base path for the request url to match against
+		/// </summary>
+		public RpcPath BaseRequestPath { get; set; }
+
+		public Type BaseControllerType { get; set; } = typeof(RpcController);
+	}
+#endif
+
+	public class RpcManualRoutingOptions
+	{
+		/// <summary>
+		/// Sets the required base path for the request url to match against
+		/// </summary>
+		public RpcPath BaseRequestPath { get; set; }
+
+		public Dictionary<RpcPath, List<IRpcMethodProvider>> Routes { get; set; } = new Dictionary<RpcPath, List<IRpcMethodProvider>>();
+
+
+		public void RegisterMethods(RpcPath path, IRpcMethodProvider methodProvider)
+		{
+			if (!this.Routes.TryGetValue(path, out List<IRpcMethodProvider> methodProviders))
+			{
+				methodProviders = new List<IRpcMethodProvider>();
+				this.Routes[path] = methodProviders;
+			}
+			methodProviders.Add(methodProvider);
+		}
+
+		public void RegisterController<T>(RpcPath path = default(RpcPath))
+		{
+			this.RegisterMethods(path, new ControllerPublicMethodProvider(typeof(T)));
+		}
+	}
+
+	public interface IRpcMethodProvider
+	{
+		List<MethodInfo> GetRouteMethods();
+	}
+}

+ 230 - 0
JsonRPCTest/RpcRouter/BuilderExtensions.cs

@@ -0,0 +1,230 @@
+using System;
+using EdjCase.JsonRpc.Core;
+using EdjCase.JsonRpc.Router;
+using EdjCase.JsonRpc.Router.Abstractions;
+using EdjCase.JsonRpc.Router.Defaults;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.Options;
+using EdjCase.JsonRpc.Router.RouteProviders;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using EdjCase.JsonRpc.Core.Tools;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Builder;
+using System.Net.WebSockets;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.AspNetCore.Builder
+{
+	/// <summary>
+	/// Extension class to add JsonRpc router to Asp.Net pipeline
+	/// </summary>
+	public static class BuilderExtensions
+	{
+		/// <summary>
+		/// Extension method to use the JsonRpc router in the Asp.Net pipeline
+		/// </summary>
+		/// <param name="app"><see cref="IApplicationBuilder"/> that is supplied by Asp.Net</param>
+		/// <param name="configureOptions">Optional action to configure auto routing</param>
+		/// <returns><see cref="IApplicationBuilder"/> that includes the Basic auth middleware</returns>
+		public static IApplicationBuilder UseJsonRpc(this IApplicationBuilder app, Action<RpcAutoRoutingOptions> configureOptions = null)
+		{
+			if (app == null)
+			{
+				throw new ArgumentNullException(nameof(app));
+			}
+
+			var options = new RpcAutoRoutingOptions();
+			configureOptions?.Invoke(options);
+			IRpcRouteProvider routeProvider = new RpcAutoRouteProvider(Options.Create(options));
+			return app.UseJsonRpc(routeProvider);
+		}
+
+		/// <summary>
+		/// Extension method to use the JsonRpc router in the Asp.Net pipeline
+		/// </summary>
+		/// <param name="app"><see cref="IApplicationBuilder"/> that is supplied by Asp.Net</param>
+		/// <param name="options">Auto routing configuration</param>
+		/// <returns><see cref="IApplicationBuilder"/> that includes the Basic auth middleware</returns>
+		public static IApplicationBuilder UseJsonRpc(this IApplicationBuilder app, RpcAutoRoutingOptions options)
+		{
+			if (app == null)
+			{
+				throw new ArgumentNullException(nameof(app));
+			}
+			if (options == null)
+			{
+				throw new ArgumentNullException(nameof(options));
+			}
+			IRpcRouteProvider routeProvider = new RpcAutoRouteProvider(Options.Create(options));
+			return app.UseJsonRpc(routeProvider);
+		}
+
+		/// <summary>
+		/// Extension method to use the JsonRpc router in the Asp.Net pipeline
+		/// </summary>
+		/// <param name="app"><see cref="IApplicationBuilder"/> that is supplied by Asp.Net</param>
+		/// <param name="configureOptions">Optional action to configure manual routing</param>
+		/// <returns><see cref="IApplicationBuilder"/> that includes the Basic auth middleware</returns>
+		public static IApplicationBuilder UseManualJsonRpc(this IApplicationBuilder app, Action<RpcManualRoutingOptions> configureOptions = null)
+		{
+			if (app == null)
+			{
+				throw new ArgumentNullException(nameof(app));
+			}
+
+			var options = new RpcManualRoutingOptions();
+			configureOptions?.Invoke(options);
+			IRpcRouteProvider routeProvider = new RpcManualRouteProvider(Options.Create(options));
+			return app.UseJsonRpc(routeProvider);
+		}
+
+		/// <summary>
+		/// Extension method to use the JsonRpc router in the Asp.Net pipeline
+		/// </summary>
+		/// <param name="app"><see cref="IApplicationBuilder"/> that is supplied by Asp.Net</param>
+		/// <param name="options">Manual routing configuration</param>
+		/// <returns><see cref="IApplicationBuilder"/> that includes the Basic auth middleware</returns>
+		public static IApplicationBuilder UseManualJsonRpc(this IApplicationBuilder app, RpcManualRoutingOptions options)
+		{
+			if (app == null)
+			{
+				throw new ArgumentNullException(nameof(app));
+			}
+			if (options == null)
+			{
+				throw new ArgumentNullException(nameof(options));
+			}
+			IRpcRouteProvider routeProvider = new RpcManualRouteProvider(Options.Create(options));
+			return app.UseJsonRpc(routeProvider);
+		}
+
+
+		/// <summary>
+		/// Extension method to use the JsonRpc router in the Asp.Net pipeline
+		/// </summary>
+		/// <param name="app"><see cref="IApplicationBuilder"/> that is supplied by Asp.Net</param>
+		/// <param name="options">Auto routing configuration</param>
+		/// <returns><see cref="IApplicationBuilder"/> that includes the Basic auth middleware</returns>
+		public static IApplicationBuilder UseJsonRpc(this IApplicationBuilder app, IRpcRouteProvider routeProvider)
+		{
+			if (app == null)
+			{
+				throw new ArgumentNullException(nameof(app));
+			}
+			if (routeProvider == null)
+			{
+				throw new ArgumentNullException(nameof(routeProvider));
+			}
+			if (app.ApplicationServices.GetService<RpcServicesMarker>() == null)
+			{
+				throw new InvalidOperationException("AddJsonRpc() needs to be called in the ConfigureServices method.");
+			}
+			var router = new RpcHttpRouter(routeProvider);
+			return app.UseRouter(router);
+		}
+
+
+		/// <summary>
+		/// Extension method to add the JsonRpc router services to the IoC container
+		/// </summary>
+		/// <param name="serviceCollection">IoC serivce container to register JsonRpc dependencies</param>
+		/// <returns>IoC service container</returns>
+		public static IRpcBuilder AddJsonRpc(this IServiceCollection serviceCollection)
+		{
+			if (serviceCollection == null)
+			{
+				throw new ArgumentNullException(nameof(serviceCollection));
+			}
+
+			serviceCollection.AddSingleton(new RpcServicesMarker());
+			serviceCollection
+				.TryAddScoped<IRpcInvoker, DefaultRpcInvoker>();
+			serviceCollection
+				.TryAddScoped<IRpcParser, DefaultRpcParser>();
+			serviceCollection
+				.TryAddScoped<IRpcRequestHandler, RpcRequestHandler>();
+			serviceCollection
+				.TryAddScoped<IStreamCompressor, DefaultStreamCompressor>();
+			serviceCollection
+				.TryAddScoped<IRpcResponseSerializer, DefaultRpcResponseSerializer>();
+			serviceCollection
+				.TryAddScoped<IRpcRouteProvider, RpcAutoRouteProvider>();
+			serviceCollection
+				.TryAddScoped<IRpcRequestMatcher, DefaultRequestMatcher>();
+
+			serviceCollection
+				.AddRouting()
+				.AddAuthorization();
+
+			return new RpcBuilder(serviceCollection);
+		}
+	}
+
+	public interface IRpcBuilder
+	{
+		IServiceCollection Services { get; }
+	}
+
+	internal class RpcBuilder : IRpcBuilder
+	{
+		public IServiceCollection Services { get; }
+
+		public RpcBuilder(IServiceCollection services)
+		{
+			this.Services = services;
+		}
+	}
+
+	public class RpcServicesMarker
+	{
+
+	}
+
+
+	public static class RpcBuilderExtensions
+	{
+		public static IRpcBuilder WithOptions(this IRpcBuilder builder, Action<RpcServerConfiguration> configureOptions)
+		{
+			var configuration = new RpcServerConfiguration();
+			configureOptions?.Invoke(configuration);
+			builder.Services.Configure(configureOptions);
+			return builder;
+		}
+
+		public static IRpcBuilder WithOptions(this IRpcBuilder builder, RpcServerConfiguration configuration)
+		{
+			builder.Services.AddSingleton<IOptions<RpcServerConfiguration>>(Options.Create(configuration));
+			return builder;
+		}
+
+		public static IRpcBuilder WithParser<T>(this IRpcBuilder builder)
+			where T : class, IRpcParser
+		{
+			builder.Services.AddScoped<IRpcParser, T>();
+			return builder;
+		}
+
+		public static IRpcBuilder WithCompressor<T>(this IRpcBuilder builder)
+			where T : class, IStreamCompressor
+		{
+			builder.Services.AddScoped<IStreamCompressor, T>();
+			return builder;
+		}
+
+		public static IRpcBuilder WithReponseSerializer<T>(this IRpcBuilder builder)
+			where T : class, IRpcResponseSerializer
+		{
+			builder.Services.AddScoped<IRpcResponseSerializer, T>();
+			return builder;
+		}
+
+		public static IRpcBuilder WithRequestMatcher<T>(this IRpcBuilder builder)
+			where T : class, IRpcRequestMatcher
+		{
+			builder.Services.AddScoped<IRpcRequestMatcher, T>();
+			return builder;
+		}
+	}
+}

+ 30 - 0
JsonRPCTest/RpcRouter/Configuration.cs

@@ -0,0 +1,30 @@
+using System;
+using Newtonsoft.Json;
+using EdjCase.JsonRpc.Router.Abstractions;
+
+namespace EdjCase.JsonRpc.Router
+{
+	/// <summary>
+	/// Configuration data for the Rpc server that is shared between all middlewares
+	/// </summary>
+	public class RpcServerConfiguration
+	{
+		/// <summary>
+		/// Json serialization settings that will be used in serialization and deserialization
+		/// for rpc requests
+		/// </summary>
+		public JsonSerializerSettings JsonSerializerSettings { get; set; }
+
+
+		/// <summary>
+		/// If true will show exception messages that the server rpc methods throw. Defaults to false
+		/// </summary>
+		public bool ShowServerExceptions { get; set; }
+
+		/// <summary>
+		/// If specified the router will throw an error if there is a batch request count
+		/// greater than the limit
+		/// </summary>
+		public int? BatchRequestLimit { get; set; }
+	}
+}

+ 332 - 0
JsonRPCTest/RpcRouter/Defaults/DefaultRequestMatcher.cs

@@ -0,0 +1,332 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using EdjCase.JsonRpc.Core;
+using EdjCase.JsonRpc.Router.Abstractions;
+using JsonRPCTest.Controllers;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace EdjCase.JsonRpc.Router.Defaults
+{
+	public class DefaultRequestMatcher : IRpcRequestMatcher
+	{
+		private ILogger<DefaultRequestMatcher> logger { get; }
+		private IOptions<RpcServerConfiguration> serverConfig { get; }
+		public DefaultRequestMatcher(ILogger<DefaultRequestMatcher> logger,
+		IOptions<RpcServerConfiguration> serverConfig)
+		{
+			this.logger = logger;
+			this.serverConfig = serverConfig;
+		}
+
+
+		private JsonSerializer jsonSerializerCache { get; set; }
+
+		private JsonSerializer GetJsonSerializer()
+		{
+			if (this.jsonSerializerCache == null)
+			{
+				this.jsonSerializerCache = this.serverConfig.Value?.JsonSerializerSettings == null
+					? JsonSerializer.CreateDefault()
+					: JsonSerializer.Create(this.serverConfig.Value.JsonSerializerSettings);
+			}
+			return this.jsonSerializerCache;
+		}
+
+		public List<RpcMethodInfo> FilterAndBuildMethodInfoByRequest(List<MethodInfo> methods, RpcRequest request)
+		{
+			//Case insenstive check for hybrid approach. Will check for case sensitive if there is ambiguity
+			List<MethodInfo> methodsWithSameName = methods
+				.Where(m => string.Equals(m.Name, request.Method, StringComparison.OrdinalIgnoreCase))
+				.ToList();
+
+			if (!methodsWithSameName.Any())
+			{
+				string methodName = DefaultRequestMatcher.FixCase(request.Method);
+
+				if (methodName != null)
+				{
+					methodsWithSameName = methods
+						.Where(m => string.Equals(m.Name, methodName, StringComparison.OrdinalIgnoreCase))
+						.ToList();
+				}
+			}
+			var potentialMatches = new List<RpcMethodInfo>();
+			foreach (MethodInfo method in methodsWithSameName)
+			{
+				(bool isMatch, RpcMethodInfo methodInfo) = this.HasParameterSignature(method, request);
+				if (isMatch)
+				{
+					potentialMatches.Add(methodInfo);
+				}
+			}
+
+			if (potentialMatches.Count > 1)
+			{
+				//Try to remove ambiguity with 'perfect matching' (usually optional params and types)
+				List<RpcMethodInfo> exactMatches = potentialMatches
+					.Where(p => p.HasExactParameterMatch())
+					.ToList();
+				if (exactMatches.Any())
+				{
+					potentialMatches = exactMatches;
+				}
+				if (potentialMatches.Count > 1)
+				{
+					//Try to remove ambiguity with case sensitive check
+					potentialMatches = potentialMatches
+						.Where(m => string.Equals(m.Method.Name, request.Method, StringComparison.Ordinal))
+						.ToList();
+				}
+			}
+
+			return potentialMatches;
+
+
+		}
+
+		private static string FixCase(string method)
+		{
+			//Snake case
+			if (method.Contains('_'))
+			{
+				return method
+					.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries)
+					.Aggregate((s1, s2) => s1 + s2);
+			}
+			//Spinal case
+			else if (method.Contains('-'))
+			{
+				return method
+					.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries)
+					.Aggregate((s1, s2) => s1 + s2);
+			}
+			else
+			{
+				return null;
+			}
+		}
+
+		/// <summary>
+		/// Converts the object array into the exact types the method needs (e.g. long -> int)
+		/// </summary>
+		/// <param name="parameters">Array of parameters for the method</param>
+		/// <returns>Array of objects with the exact types required by the method</returns>
+		private object[] ConvertParameters(MethodInfo method, object[] parameters)
+		{
+			if (parameters == null || !parameters.Any())
+			{
+				return new object[0];
+			}
+			ParameterInfo[] parameterInfoList = method.GetParameters();
+			for (int index = 0; index < parameters.Length; index++)
+			{
+				ParameterInfo parameterInfo = parameterInfoList[index];
+				parameters[index] = this.ConvertParameter(parameterInfo.ParameterType, parameters[index]);
+			}
+
+			return parameters;
+		}
+
+		private object ConvertParameter(Type parameterType, object parameterValue)
+		{
+			if (parameterValue == null)
+			{
+				return null;
+			}
+			//Missing type is for optional parameters
+			if (parameterValue is Missing)
+			{
+				return parameterValue;
+			}
+			Type nullableType = Nullable.GetUnderlyingType(parameterType);
+			if (nullableType != null)
+			{
+				return this.ConvertParameter(nullableType, parameterValue);
+			}
+			if (parameterValue is string && parameterType == typeof(Guid))
+			{
+				Guid.TryParse((string)parameterValue, out Guid guid);
+				return guid;
+			}
+			if (parameterType.GetTypeInfo().IsEnum)
+			{
+				if (parameterValue is string)
+				{
+					return Enum.Parse(parameterType, (string)parameterValue);
+				}
+				else if (parameterValue is long)
+				{
+					return Enum.ToObject(parameterType, parameterValue);
+				}
+			}
+			if (parameterValue is JToken jToken)
+			{
+				JsonSerializer jsonSerializer = this.GetJsonSerializer();
+				return jToken.ToObject(parameterType, jsonSerializer);
+			}
+			return Convert.ChangeType(parameterValue, parameterType);
+		}
+
+		/// <summary>
+		/// Detects if list of parameters matches the method signature
+		/// </summary>
+		/// <param name="parameterList">Array of parameters for the method</param>
+		/// <returns>True if the method signature matches the parameterList, otherwise False</returns>
+		private (bool Matches, RpcMethodInfo MethodInfo) HasParameterSignature(MethodInfo method, RpcRequest rpcRequest)
+		{
+			object[] orignialParameterList;
+			if (!rpcRequest.Parameters.HasValue)
+			{
+				orignialParameterList = new object[0];
+			}
+			else
+			{
+				switch (rpcRequest.Parameters.Type)
+				{
+					case RpcParametersType.Dictionary:
+						Dictionary<string, object> parameterMap = rpcRequest.Parameters.DictionaryValue;
+						bool canParse = this.TryParseParameterList(method, parameterMap, out orignialParameterList);
+						if (!canParse)
+						{
+							return (false, null);
+						}
+						break;
+					case RpcParametersType.Array:
+						orignialParameterList = rpcRequest.Parameters.ArrayValue;
+						break;
+					default:
+						orignialParameterList = new JToken[0];
+						break;
+				}
+			}
+			ParameterInfo[] parameterInfoList = method.GetParameters();
+			if (orignialParameterList.Length > parameterInfoList.Length)
+			{
+				return (false, null);
+			}
+			object[] correctedParameterList = new object[parameterInfoList.Length];
+
+			for (int i = 0; i < orignialParameterList.Length; i++)
+			{
+				ParameterInfo parameterInfo = parameterInfoList[i];
+				object parameter = orignialParameterList[i];
+				bool isMatch = this.ParameterMatches(parameterInfo, parameter, out object convertedParameter);
+				if (!isMatch)
+				{
+					return (false, null);
+				}
+				correctedParameterList[i] = convertedParameter;
+			}
+
+			if (orignialParameterList.Length < parameterInfoList.Length)
+			{
+				//make a new array at the same length with padded 'missing' parameters (if optional)
+				for (int i = orignialParameterList.Length; i < parameterInfoList.Length; i++)
+				{
+					if (!parameterInfoList[i].IsOptional)
+					{
+						return (false, null);
+					}
+					correctedParameterList[i] = Type.Missing;
+				}
+			}
+
+			var rpcMethodInfo = new RpcMethodInfo(method, correctedParameterList, orignialParameterList);
+			return (true, rpcMethodInfo);
+		}
+
+		/// <summary>
+		/// Detects if the request parameter matches the method parameter
+		/// </summary>
+		/// <param name="parameterInfo">Reflection info about a method parameter</param>
+		/// <param name="value">The request's value for the parameter</param>
+		/// <returns>True if the request parameter matches the type of the method parameter</returns>
+		private bool ParameterMatches(ParameterInfo parameterInfo, object value, out object convertedValue)
+		{
+			Type parameterType = parameterInfo.ParameterType;
+
+			try
+			{
+				if (value is JToken tokenValue)
+				{
+					switch (tokenValue.Type)
+					{
+						case JTokenType.Array:
+							{
+								JsonSerializer serializer = this.GetJsonSerializer();
+								JArray jArray = (JArray)tokenValue;
+								convertedValue = jArray.ToObject(parameterType, serializer);
+								return true;
+							}
+						case JTokenType.Object:
+							{
+								JsonSerializer serializer = this.GetJsonSerializer();
+								JObject jObject = (JObject)tokenValue;
+								convertedValue = jObject.ToObject(parameterType, serializer);
+								return true;
+							}
+						default:
+							convertedValue = tokenValue.ToObject(parameterType);
+							return true;
+					}
+				}
+				else
+				{
+					convertedValue = value;
+					return true;
+				}
+			}
+			catch (Exception ex)
+			{
+				this.logger?.LogWarning($"Parameter '{parameterInfo.Name}' failed to deserialize: " + ex);
+				convertedValue = null;
+				return false;
+			}
+		}
+
+
+		/// <summary>
+		/// Tries to parse the parameter map into an ordered parameter list
+		/// </summary>
+		/// <param name="parametersMap">Map of parameter name to parameter value</param>
+		/// <param name="parameterList">Result of converting the map to an ordered list, null if result is False</param>
+		/// <returns>True if the parameters can convert to an ordered list based on the method signature, otherwise Fasle</returns>
+		private bool TryParseParameterList(MethodInfo method, Dictionary<string, object> parametersMap, out object[] parameterList)
+		{
+			parametersMap = parametersMap
+				.ToDictionary(x => DefaultRequestMatcher.FixCase(x.Key) ?? x.Key, v => v.Value, StringComparer.OrdinalIgnoreCase);
+			ParameterInfo[] parameterInfoList = method.GetParameters();
+			parameterList = new object[parameterInfoList.Count()];
+			
+				foreach (ParameterInfo parameterInfo in parameterInfoList)
+				{
+					if (!parametersMap.ContainsKey(parameterInfo.Name))
+					{
+						//if (parameterInfoList.Length == 1 && parametersMap != null && parametersMap.Keys.Count > 0)
+						//{
+						//	Type type= parameterInfo.ParameterType;
+						//	parameterList[parameterInfo.Position] = JsonNetHelper.FromJson(parametersMap.ToJson(), type);
+						//	return true;
+						//}
+						if (!parameterInfo.IsOptional)
+						{
+							parameterList = null;
+							return false;
+						}
+					}
+					else
+					{
+						parameterList[parameterInfo.Position] = parametersMap[parameterInfo.Name];
+					}
+				}
+			 
+			return true;
+		}
+
+	}
+}

+ 31 - 0
JsonRPCTest/RpcRouter/Defaults/DefaultRouteContext.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using EdjCase.JsonRpc.Router.Abstractions;
+using Microsoft.AspNetCore.Http;
+
+namespace EdjCase.JsonRpc.Router.Defaults
+{
+	public class DefaultRouteContext : IRouteContext
+	{
+		public IServiceProvider RequestServices { get; }
+
+		public ClaimsPrincipal User { get; }
+
+		public IRpcRouteProvider RouteProvider { get; }
+
+		public DefaultRouteContext(IServiceProvider serviceProvider, ClaimsPrincipal user, IRpcRouteProvider routeProvider)
+		{
+			this.RequestServices = serviceProvider;
+			this.User = user;
+			this.RouteProvider = routeProvider;
+		}
+
+		public static IRouteContext FromHttpContext(HttpContext httpContext, IRpcRouteProvider routeProvider)
+		{
+			return new DefaultRouteContext(httpContext.RequestServices, httpContext.User, routeProvider);
+		}
+	}
+}

+ 420 - 0
JsonRPCTest/RpcRouter/Defaults/DefaultRpcInvoker.cs

@@ -0,0 +1,420 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using EdjCase.JsonRpc.Core;
+using EdjCase.JsonRpc.Router.Abstractions;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using EdjCase.JsonRpc.Router.Utilities;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.DependencyInjection;
+using EdjCase.JsonRpc.Router.MethodProviders;
+using System.Collections.Concurrent;
+
+namespace EdjCase.JsonRpc.Router.Defaults
+{
+	/// <summary>
+	/// Default Rpc method invoker that uses asynchronous processing
+	/// </summary>
+	public class DefaultRpcInvoker : IRpcInvoker
+	{
+		/// <summary>
+		/// Logger for logging Rpc invocation
+		/// </summary>
+		private ILogger<DefaultRpcInvoker> logger { get; }
+
+		/// <summary>
+		/// AspNet service to authorize requests
+		/// </summary>
+		private IAuthorizationService authorizationService { get; }
+		/// <summary>
+		/// Provides authorization policies for the authroziation service
+		/// </summary>
+		private IAuthorizationPolicyProvider policyProvider { get; }
+
+		/// <summary>
+		/// Configuration data for the server
+		/// </summary>
+		private IOptions<RpcServerConfiguration> serverConfig { get; }
+
+		/// <summary>
+		/// Matches the route method name and parameters to the correct method to execute
+		/// </summary>
+		private IRpcRequestMatcher rpcRequestMatcher { get; }
+
+		private ConcurrentDictionary<Type, ObjectFactory> objectFactoryCache { get; } = new ConcurrentDictionary<Type, ObjectFactory>();
+		private ConcurrentDictionary<Type, (List<IAuthorizeData>, bool)> classAttributeCache { get; } = new ConcurrentDictionary<Type, (List<IAuthorizeData>, bool)>();
+		private ConcurrentDictionary<MethodInfo, (List<IAuthorizeData>, bool)> methodAttributeCache { get; } = new ConcurrentDictionary<MethodInfo, (List<IAuthorizeData>, bool)>();
+
+
+		/// <param name="authorizationService">Service that authorizes each method for use if configured</param>
+		/// <param name="policyProvider">Provides authorization policies for the authroziation service</param>
+		/// <param name="logger">Optional logger for logging Rpc invocation</param>
+		/// <param name="serverConfig">Configuration data for the server</param>
+		/// <param name="rpcRequestMatcher">Matches the route method name and parameters to the correct method to execute</param>
+		public DefaultRpcInvoker(IAuthorizationService authorizationService, IAuthorizationPolicyProvider policyProvider,
+			ILogger<DefaultRpcInvoker> logger, IOptions<RpcServerConfiguration> serverConfig,
+			IRpcRequestMatcher rpcRequestMatcher)
+		{
+			this.authorizationService = authorizationService;
+			this.policyProvider = policyProvider;
+			this.logger = logger;
+			this.serverConfig = serverConfig;
+			this.rpcRequestMatcher = rpcRequestMatcher;
+		}
+
+
+		/// <summary>
+		/// Call the incoming Rpc requests methods and gives the appropriate respones
+		/// </summary>
+		/// <param name="requests">List of Rpc requests</param>
+		/// <param name="path">Rpc path that applies to the current request</param>
+		/// <param name="httpContext">The context of the current http request</param>
+		/// <returns>List of Rpc responses for the requests</returns>
+		public async Task<List<RpcResponse>> InvokeBatchRequestAsync(IList<RpcRequest> requests, RpcPath path, IRouteContext routeContext)
+		{
+			this.logger?.LogDebug($"Invoking '{requests.Count}' batch requests");
+			var invokingTasks = new List<Task<RpcResponse>>();
+			foreach (RpcRequest request in requests)
+			{
+				Task<RpcResponse> invokingTask = this.InvokeRequestAsync(request, path, routeContext);
+				if (request.Id.HasValue)
+				{
+					//Only wait for non-notification requests
+					invokingTasks.Add(invokingTask);
+				}
+			}
+
+			await Task.WhenAll(invokingTasks.ToArray());
+
+			List<RpcResponse> responses = invokingTasks
+				.Select(t => t.Result)
+				.Where(r => r != null)
+				.ToList();
+
+			this.logger?.LogDebug($"Finished '{requests.Count}' batch requests");
+
+			return responses;
+		}
+
+		/// <summary>
+		/// Call the incoming Rpc request method and gives the appropriate response
+		/// </summary>
+		/// <param name="request">Rpc request</param>
+		/// <param name="path">Rpc path that applies to the current request</param>
+		/// <param name="httpContext">The context of the current http request</param>
+		/// <returns>An Rpc response for the request</returns>
+		public async Task<RpcResponse> InvokeRequestAsync(RpcRequest request, RpcPath path, IRouteContext routeContext)
+		{
+			if (request == null)
+			{
+				throw new ArgumentNullException(nameof(request));
+			}
+
+			this.logger?.LogDebug($"Invoking request with id '{request.Id}'");
+			RpcResponse rpcResponse;
+			try
+			{
+				RpcMethodInfo rpcMethod = this.GetMatchingMethod(path, request, routeContext.RouteProvider, routeContext.RequestServices);
+
+				bool isAuthorized = await this.IsAuthorizedAsync(rpcMethod.Method, routeContext);
+
+				if (isAuthorized)
+				{
+
+					this.logger?.LogDebug($"Attempting to invoke method '{request.Method}'");
+					object result = await this.InvokeAsync(rpcMethod, path, routeContext.RequestServices);
+					this.logger?.LogDebug($"Finished invoking method '{request.Method}'");
+
+					if (result is IRpcMethodResult)
+					{
+						this.logger?.LogTrace($"Result is {nameof(IRpcMethodResult)}.");
+						rpcResponse = ((IRpcMethodResult)result).ToRpcResponse(request.Id);
+					}
+					else
+					{
+						this.logger?.LogTrace($"Result is plain object.");
+						rpcResponse = new RpcResponse(request.Id, result, rpcMethod.Method.ReturnType);
+					}
+				}
+				else
+				{
+					var authError = new RpcError(RpcErrorCode.InvalidRequest, "Unauthorized");
+					rpcResponse = new RpcResponse(request.Id, authError);
+				}
+			}
+			catch (Exception ex)
+			{
+				string errorMessage = "An Rpc error occurred while trying to invoke request.";
+				this.logger?.LogException(ex, errorMessage);
+				RpcError error;
+				if (ex is RpcException rpcException)
+				{
+					error = rpcException.ToRpcError(this.serverConfig.Value.ShowServerExceptions);
+				}
+				else
+				{
+					error = new RpcError(RpcErrorCode.InternalError, errorMessage, ex);
+				}
+				rpcResponse = new RpcResponse(request.Id, error);
+			}
+
+			if (request.Id.HasValue)
+			{
+				this.logger?.LogDebug($"Finished request with id: {request.Id}");
+				//Only give a response if there is an id
+				return rpcResponse;
+			}
+			this.logger?.LogDebug($"Finished request with no id. Not returning a response");
+			return null;
+		}
+
+		private async Task<bool> IsAuthorizedAsync(MethodInfo methodInfo, IRouteContext routeContext)
+		{
+			(List<IAuthorizeData> authorizeDataListClass, bool allowAnonymousOnClass) = this.classAttributeCache.GetOrAdd(methodInfo.DeclaringType, GetClassAttributeInfo);
+			(List<IAuthorizeData> authorizeDataListMethod, bool allowAnonymousOnMethod) = this.methodAttributeCache.GetOrAdd(methodInfo, GetMethodAttributeInfo);
+
+			if (authorizeDataListClass.Any() || authorizeDataListMethod.Any())
+			{
+				if (allowAnonymousOnClass || allowAnonymousOnMethod)
+				{
+					this.logger?.LogDebug("Skipping authorization. Allow anonymous specified for method.");
+				}
+				else
+				{
+					this.logger?.LogDebug($"Running authorization for method.");
+					AuthorizationResult authResult = await this.CheckAuthorize(authorizeDataListClass, routeContext);
+					if (authResult.Succeeded)
+					{
+						//Have to pass both controller and method authorize
+						authResult = await this.CheckAuthorize(authorizeDataListMethod, routeContext);
+					}
+					if (authResult.Succeeded)
+					{
+						this.logger?.LogDebug($"Authorization was successful for user '{routeContext.User.Identity.Name}'.");
+					}
+					else
+					{
+						this.logger?.LogInformation($"Authorization failed for user '{routeContext.User.Identity.Name}'.");
+						return false;
+					}
+				}
+			}
+			else
+			{
+				this.logger?.LogDebug("Skipping authorization. None configured for class or method.");
+			}
+			return true;
+
+			//functions
+			(List<IAuthorizeData> Data, bool allowAnonymous) GetClassAttributeInfo(Type type)
+			{
+				return GetAttributeInfo(type.GetCustomAttributes());
+			}
+
+			(List<IAuthorizeData> Data, bool allowAnonymous) GetMethodAttributeInfo(MethodInfo info)
+			{
+				return GetAttributeInfo(info.GetCustomAttributes());
+			}
+			(List<IAuthorizeData> Data, bool allowAnonymous) GetAttributeInfo(IEnumerable<Attribute> attributes)
+			{
+				bool allowAnonymous = false;
+				var dataList = new List<IAuthorizeData>(10);
+				foreach (Attribute attribute in attributes)
+				{
+					if (attribute is IAuthorizeData data)
+					{
+						dataList.Add(data);
+					}
+					if (!allowAnonymous && attribute is IAllowAnonymous)
+					{
+						allowAnonymous = true;
+					}
+				}
+				return (dataList, allowAnonymous);
+			}
+		}
+
+		private async Task<AuthorizationResult> CheckAuthorize(List<IAuthorizeData> authorizeDataList, IRouteContext routeContext)
+		{
+			if (!authorizeDataList.Any())
+			{
+				return AuthorizationResult.Success();
+			}
+			AuthorizationPolicy policy = await AuthorizationPolicy.CombineAsync(this.policyProvider, authorizeDataList);
+			return await this.authorizationService.AuthorizeAsync(routeContext.User, policy);
+		}
+
+
+		/// <summary>
+		/// Finds the matching Rpc method for the current request
+		/// </summary>
+		/// <param name="path">Rpc route for the current request</param>
+		/// <param name="request">Current Rpc request</param>
+		/// <param name="parameterList">Parameter list parsed from the request</param>
+		/// <param name="serviceProvider">(Optional)IoC Container for rpc method controllers</param>
+		/// <returns>The matching Rpc method to the current request</returns>
+		private RpcMethodInfo GetMatchingMethod(RpcPath path, RpcRequest request, IRpcRouteProvider routeProvider, IServiceProvider serviceProvider)
+		{
+			if (request == null)
+			{
+				throw new ArgumentNullException(nameof(request));
+			}
+			this.logger?.LogDebug($"Attempting to match Rpc request to a method '{request.Method}'");
+			List<MethodInfo> allMethods = this.GetRpcMethods(path, routeProvider);
+
+			List<RpcMethodInfo> matches = this.rpcRequestMatcher.FilterAndBuildMethodInfoByRequest(allMethods, request);
+
+
+			RpcMethodInfo rpcMethod;
+			if (matches.Count > 1)
+			{
+				var methodInfoList = new List<string>();
+				foreach (RpcMethodInfo matchedMethod in matches)
+				{
+					var parameterTypeList = new List<string>();
+					foreach (ParameterInfo parameterInfo in matchedMethod.Method.GetParameters())
+					{
+						string parameterType = parameterInfo.Name + ": " + parameterInfo.ParameterType.Name;
+						if (parameterInfo.IsOptional)
+						{
+							parameterType += "(Optional)";
+						}
+						parameterTypeList.Add(parameterType);
+					}
+					string parameterString = string.Join(", ", parameterTypeList);
+					methodInfoList.Add($"{{Name: '{matchedMethod.Method.Name}', Parameters: [{parameterString}]}}");
+				}
+				string errorMessage = "More than one method matched the rpc request. Unable to invoke due to ambiguity. Methods that matched the same name: " + string.Join(", ", methodInfoList);
+				this.logger?.LogError(errorMessage);
+				throw new RpcException(RpcErrorCode.MethodNotFound, errorMessage);
+			}
+			else if (matches.Count == 0)
+			{
+				//Log diagnostics 
+				string methodsString = string.Join(", ", allMethods.Select(m => m.Name));
+				this.logger?.LogTrace("Methods in route: " + methodsString);
+				
+				const string errorMessage = "No methods matched request.";
+				this.logger?.LogError(errorMessage);
+				throw new RpcException(RpcErrorCode.MethodNotFound, errorMessage);
+			}
+			else
+			{
+				rpcMethod = matches.First();
+			}
+			this.logger?.LogDebug("Request was matched to a method");
+			return rpcMethod;
+		}
+
+		/// <summary>
+		/// Gets all the predefined Rpc methods for a Rpc route
+		/// </summary>
+		/// <param name="path">The route to get Rpc methods for</param>
+		/// <param name="serviceProvider">(Optional) IoC Container for rpc method controllers</param>
+		/// <returns>List of Rpc methods for the specified Rpc route</returns>
+		private List<MethodInfo> GetRpcMethods(RpcPath path, IRpcRouteProvider routeProvider)
+		{
+			var methods = new List<MethodInfo>();
+			foreach (IRpcMethodProvider methodProvider in routeProvider.GetMethodsByPath(path))
+			{
+				foreach (MethodInfo methodInfo in methodProvider.GetRouteMethods())
+				{
+					methods.Add(methodInfo);
+				}
+			}
+			return methods;
+		}
+
+
+		/// <summary>
+		/// Invokes the method with the specified parameters, returns the result of the method
+		/// </summary>
+		/// <exception cref="RpcInvalidParametersException">Thrown when conversion of parameters fails or when invoking the method is not compatible with the parameters</exception>
+		/// <param name="parameters">List of parameters to invoke the method with</param>
+		/// <returns>The result of the invoked method</returns>
+		private async Task<object> InvokeAsync(RpcMethodInfo methodInfo, RpcPath path, IServiceProvider serviceProvider)
+		{
+			object obj = null;
+			if (serviceProvider != null)
+			{
+				//Use service provider (if exists) to create instance
+				ObjectFactory objectFactory = this.objectFactoryCache.GetOrAdd(methodInfo.Method.DeclaringType, (t) => ActivatorUtilities.CreateFactory(t, new Type[0]));
+				obj = objectFactory(serviceProvider, null);
+			}
+			if (obj == null)
+			{
+				//Use reflection to create instance if service provider failed or is null
+				obj = Activator.CreateInstance(methodInfo.Method.DeclaringType);
+			}
+			try
+			{
+				object returnObj = methodInfo.Method.Invoke(obj, methodInfo.ConvertedParameters);
+
+				returnObj = await DefaultRpcInvoker.HandleAsyncResponses(returnObj);
+
+				return returnObj;
+			}
+			catch (TargetInvocationException ex)
+			{
+				var routeInfo = new RpcRouteInfo(methodInfo, path, serviceProvider);
+
+				//Controller error handling
+				RpcErrorFilterAttribute errorFilter = methodInfo.Method.DeclaringType.GetTypeInfo().GetCustomAttribute<RpcErrorFilterAttribute>();
+				if (errorFilter != null)
+				{
+					OnExceptionResult result = errorFilter.OnException(routeInfo, ex.InnerException);
+					if (!result.ThrowException)
+					{
+						return result.ResponseObject;
+					}
+					if (result.ResponseObject is Exception rEx)
+					{
+						throw rEx;
+					}
+				}
+				throw new RpcException(RpcErrorCode.InternalError, "Exception occurred from target method execution.", ex);
+			}
+			catch (Exception ex)
+			{
+				throw new RpcException(RpcErrorCode.InvalidParams, "Exception from attempting to invoke method. Possibly invalid parameters for method.", ex);
+			}
+		}
+
+		/// <summary>
+		/// Handles/Awaits the result object if it is a async Task
+		/// </summary>
+		/// <param name="returnObj">The result of a invoked method</param>
+		/// <returns>Awaits a Task and returns its result if object is a Task, otherwise returns the same object given</returns>
+		private static async Task<object> HandleAsyncResponses(object returnObj)
+		{
+			Task task = returnObj as Task;
+			if (task == null) //Not async request
+			{
+				return returnObj;
+			}
+			try
+			{
+				await task;
+			}
+			catch (Exception ex)
+			{
+				throw new TargetInvocationException(ex);
+			}
+			PropertyInfo propertyInfo = task.GetType().GetProperty("Result");
+			if (propertyInfo != null)
+			{
+				//Type of Task<T>. Wait for result then return it
+				return propertyInfo.GetValue(returnObj);
+			}
+			//Just of type Task with no return result			
+			return null;
+		}
+	}
+}

+ 82 - 0
JsonRPCTest/RpcRouter/Defaults/DefaultRpcMethodResults.cs

@@ -0,0 +1,82 @@
+using EdjCase.JsonRpc.Core;
+using EdjCase.JsonRpc.Router.Abstractions;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EdjCase.JsonRpc.Router.Defaults
+{
+	/// <summary>
+	/// Error result for rpc responses
+	/// </summary>
+	public class RpcMethodErrorResult : IRpcMethodResult
+	{
+		/// <summary>
+		/// Error message
+		/// </summary>
+		public string Message { get; }
+		/// <summary>
+		/// JSON-RPC error code
+		/// </summary>
+		public int ErrorCode { get; }
+
+		/// <summary>
+		/// Data for error response
+		/// </summary>
+		public object Data { get; }
+
+		/// <param name="errorCode">JSON-RPC error code</param>
+		/// <param name="message">(Optional)Error message</param>
+		/// <param name="data">(Optional)Data for error response</param>
+		public RpcMethodErrorResult(int errorCode, string message = null, object data = null)
+		{
+			this.ErrorCode = errorCode;
+			this.Message = message;
+			this.Data = data;
+		}
+
+		/// <summary>
+		/// Turns result data into a rpc response
+		/// </summary>
+		/// <param name="id">Rpc request id</param>
+		/// <param name="serializer">Json serializer function to use for objects for the response</param>
+		/// <returns>Rpc response for request</returns>
+		public RpcResponse ToRpcResponse(RpcId id)
+		{
+			RpcError error = new RpcError(this.ErrorCode, this.Message, this.Data);
+			return new RpcResponse(id, error);
+		}
+	}
+
+	/// <summary>
+	/// Success result for rpc responses
+	/// </summary>
+	public class RpcMethodSuccessResult : IRpcMethodResult
+	{
+		/// <summary>
+		/// Object to return in rpc response
+		/// </summary>
+		public object ReturnObject { get; }
+		/// <summary>
+		/// 
+		/// </summary>
+		/// <param name="returnObject">Object to return in rpc response</param>
+		public RpcMethodSuccessResult(object returnObject = null)
+		{
+			this.ReturnObject = returnObject;
+		}
+
+		/// <summary>
+		/// Turns result data into a rpc response
+		/// </summary>
+		/// <param name="id">Rpc request id</param>
+		/// <param name="serializer">Json serializer function to use for objects for the response</param>
+		/// <returns>Rpc response for request</returns>
+		public RpcResponse ToRpcResponse(RpcId id)
+		{
+			return new RpcResponse(id, this.ReturnObject, this.ReturnObject?.GetType());
+		}
+	}
+}

+ 186 - 0
JsonRPCTest/RpcRouter/Defaults/DefaultRpcParser.cs

@@ -0,0 +1,186 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using EdjCase.JsonRpc.Core;
+using EdjCase.JsonRpc.Router.Abstractions;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using EdjCase.JsonRpc.Router.Utilities;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json.Linq;
+using System.IO;
+using Edjcase.JsonRpc.Router;
+
+namespace EdjCase.JsonRpc.Router.Defaults
+{
+	/// <summary>
+	/// Default Rpc parser that uses <see cref="Newtonsoft.Json"/>
+	/// </summary>
+	public class DefaultRpcParser : IRpcParser
+	{
+		/// <summary>
+		/// Logger for logging Rpc parsing
+		/// </summary>
+		private ILogger<DefaultRpcParser> logger { get; }
+		private IOptions<RpcServerConfiguration> serverConfig { get; }
+
+		/// <summary>
+		/// 
+		/// </summary>
+		/// <param name="logger">Optional logger for logging Rpc parsing</param>
+		public DefaultRpcParser(ILogger<DefaultRpcParser> logger,
+			IOptions<RpcServerConfiguration> serverConfig)
+		{
+			this.logger = logger;
+			this.serverConfig = serverConfig;
+		}
+
+		/// <summary>
+		/// Parses all the requests from the json in the request
+		/// </summary>
+		/// <param name="jsonString">Json from the http request</param>
+		/// <param name="isBulkRequest">If true, the request is a bulk request (even if there is only one)</param>
+		/// <returns>List of Rpc requests that were parsed from the json</returns>
+		public ParsingResult ParseRequests(string jsonString)
+		{
+			this.logger?.LogDebug($"Attempting to parse Rpc request from the json string '{jsonString}'");
+			List<RpcRequestParseResult> rpcRequests;
+			if (string.IsNullOrWhiteSpace(jsonString))
+			{
+				throw new RpcException(RpcErrorCode.InvalidRequest, "Json request was empty");
+			}
+			bool isBulkRequest;
+			try
+			{
+				using (JsonReader jsonReader = new JsonTextReader(new StringReader(jsonString)))
+				{
+					//Fixes the date parsing issue https://github.com/JamesNK/Newtonsoft.Json/issues/862
+					jsonReader.DateParseHandling = DateParseHandling.None;
+
+					JToken token = JToken.Load(jsonReader);
+					switch (token.Type)
+					{
+						case JTokenType.Array:
+							isBulkRequest = true;
+							rpcRequests = ((JArray)token).Select(this.DeserializeRequest).ToList();
+							break;
+						case JTokenType.Object:
+							isBulkRequest = false;
+							RpcRequestParseResult result = this.DeserializeRequest(token);
+							rpcRequests = new List<RpcRequestParseResult> { result };
+							break;
+						default:
+							throw new RpcException(RpcErrorCode.ParseError, "Json body is not an array or an object.");
+					}
+				}
+			}
+			catch (Exception ex) when (!(ex is RpcException))
+			{
+				string errorMessage = "Unable to parse json request into an rpc format.";
+				this.logger?.LogException(ex, errorMessage);
+				throw new RpcException(RpcErrorCode.InvalidRequest, errorMessage, ex);
+			}
+
+			if (rpcRequests == null || !rpcRequests.Any())
+			{
+				throw new RpcException(RpcErrorCode.InvalidRequest, "No rpc json requests found");
+			}
+			this.logger?.LogDebug($"Successfully parsed {rpcRequests.Count} Rpc request(s)");
+			var uniqueIds = new HashSet<RpcId>();
+			foreach (RpcRequestParseResult result in rpcRequests.Where(r => r.Request != null && r.Request.Id.HasValue))
+			{
+				bool unique = uniqueIds.Add(result.Request.Id);
+				if (!unique)
+				{
+					throw new RpcException(RpcErrorCode.InvalidRequest, "Duplicate ids in batch requests are not allowed");
+				}
+			}
+			return ParsingResult.FromResults(rpcRequests, isBulkRequest);
+		}
+		
+		private RpcRequestParseResult DeserializeRequest(JToken token)
+		{
+			RpcId id = null;
+			JToken idToken = token[JsonRpcContants.IdPropertyName];
+			if (idToken != null)
+			{
+				switch (idToken.Type)
+				{
+					case JTokenType.Null:
+						break;
+					case JTokenType.Integer:
+					case JTokenType.Float:
+						id = new RpcId(idToken.Value<double>());
+						break;
+					case JTokenType.String:
+					case JTokenType.Guid:
+						id = new RpcId(idToken.Value<string>());
+						break;
+					default:
+						//Throw exception here because we need an id for the response
+						throw new RpcException(RpcErrorCode.ParseError, "Unable to parse rpc id as string or number.");
+				}
+			}
+			try
+			{
+				string rpcVersion = token.Value<string>(JsonRpcContants.VersionPropertyName);
+				if (string.IsNullOrWhiteSpace(rpcVersion))
+				{
+					return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.InvalidRequest, "The jsonrpc version must be specified."));
+				}
+				if (!string.Equals(rpcVersion, "2.0", StringComparison.OrdinalIgnoreCase))
+				{
+					return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.InvalidRequest, $"The jsonrpc version '{rpcVersion}' is not supported. Supported versions: '2.0'"));
+				}
+
+				string method = token.Value<string>(JsonRpcContants.MethodPropertyName);
+				if (string.IsNullOrWhiteSpace(method))
+				{
+					return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.InvalidRequest, "The request method is required."));
+				}
+
+				RpcParameters parameters = default;
+				JToken paramsToken = token[JsonRpcContants.ParamsPropertyName];
+				if (paramsToken != null)
+				{
+					switch (paramsToken.Type)
+					{
+						case JTokenType.Array:
+							if (paramsToken.Any())
+							{
+								parameters = RpcParameters.FromList(paramsToken.ToArray());
+							}
+							break;
+						case JTokenType.Object:
+							if (paramsToken.Children().Any())
+							{
+								Dictionary<string, object> dict = paramsToken.ToObject<Dictionary<string, JToken>>()
+									.ToDictionary(kv => kv.Key, kv => (object)kv.Value);
+								parameters = RpcParameters.FromDictionary(dict);
+							}
+							break;
+						case JTokenType.Null:
+							break;
+						default:
+							return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.ParseError, "Parameters field could not be parsed."));
+					}
+				}
+
+				return RpcRequestParseResult.Success(new RpcRequest(id, method, parameters));
+			}
+			catch (Exception ex)
+			{
+				RpcError error;
+				if (ex is RpcException rpcException)
+				{
+					error = rpcException.ToRpcError(this.serverConfig.Value.ShowServerExceptions);
+				}
+				else
+				{
+					error = new RpcError(RpcErrorCode.ParseError, "Failed to parse request.", ex);
+				}
+				return RpcRequestParseResult.Fail(id, error);
+			}
+		}
+	}
+}

+ 96 - 0
JsonRPCTest/RpcRouter/Defaults/DefaultRpcResponseSerializer.cs

@@ -0,0 +1,96 @@
+using EdjCase.JsonRpc.Core;
+using EdjCase.JsonRpc.Router.Abstractions;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace EdjCase.JsonRpc.Router.Defaults
+{
+	public class DefaultRpcResponseSerializer : IRpcResponseSerializer
+	{
+		private IOptions<RpcServerConfiguration> serverConfig { get; }
+		public DefaultRpcResponseSerializer(IOptions<RpcServerConfiguration> serverConfig)
+		{
+			this.serverConfig = serverConfig;
+		}
+
+		public string SerializeBulk(IEnumerable<RpcResponse> responses)
+		{
+			return this.SerializeInternal(responses, isBulkRequest: true);
+		}
+
+		public string Serialize(RpcResponse response)
+		{
+			return this.SerializeInternal(new[] { response }, isBulkRequest: false);
+		}
+
+		private string SerializeInternal(IEnumerable<RpcResponse> responses, bool isBulkRequest)
+		{
+			using (StringWriter textWriter = new StringWriter())
+			{
+				using (JsonTextWriter jsonWriter = new JsonTextWriter(textWriter))
+				{
+					if (isBulkRequest)
+					{
+						jsonWriter.WriteStartArray();
+						foreach (RpcResponse response in responses)
+						{
+							this.SerializeResponse(response, jsonWriter);
+						}
+						jsonWriter.WriteEndArray();
+					}
+					else
+					{
+						this.SerializeResponse(responses.Single(), jsonWriter);
+					}
+				}
+				return textWriter.ToString();
+			}
+
+		}
+
+		private void SerializeResponse(RpcResponse response, JsonTextWriter jsonWriter)
+		{
+			jsonWriter.WriteStartObject();
+			jsonWriter.WritePropertyName(JsonRpcContants.IdPropertyName);
+			jsonWriter.WriteValue(response.Id.Value);
+			jsonWriter.WritePropertyName(JsonRpcContants.VersionPropertyName);
+			jsonWriter.WriteValue("2.0");
+			if (!response.HasError)
+			{
+				jsonWriter.WritePropertyName(JsonRpcContants.ResultPropertyName);
+
+				this.SerializeValue(response.Result, jsonWriter);
+			}
+			else
+			{
+				jsonWriter.WritePropertyName(JsonRpcContants.ErrorPropertyName);
+				jsonWriter.WriteStartObject();
+				jsonWriter.WritePropertyName(JsonRpcContants.ErrorCodePropertyName);
+				jsonWriter.WriteValue(response.Error.Code);
+				jsonWriter.WritePropertyName(JsonRpcContants.ErrorMessagePropertyName);
+				jsonWriter.WriteValue(response.Error.Message);
+				jsonWriter.WritePropertyName(JsonRpcContants.ErrorDataPropertyName);
+				this.SerializeValue(response.Error.Data, jsonWriter);
+			}
+			jsonWriter.WriteEndObject();
+		}
+
+		private void SerializeValue(object value, JsonTextWriter jsonWriter)
+		{
+			if (value != null)
+			{
+				string valueJson = JsonConvert.SerializeObject(value, this.serverConfig.Value.JsonSerializerSettings);
+				jsonWriter.WriteRawValue(valueJson);
+			}
+			else
+			{
+				jsonWriter.WriteNull();
+			}
+		}
+	}
+}

+ 61 - 0
JsonRPCTest/RpcRouter/MethodProviders/ControllerPublicMethodProvider.cs

@@ -0,0 +1,61 @@
+using EdjCase.JsonRpc.Router.Abstractions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Reflection;
+
+namespace EdjCase.JsonRpc.Router.MethodProviders
+{
+	/// <summary>
+	/// Method provider finding all public methods through reflection
+	/// </summary>
+	public class ControllerPublicMethodProvider : IRpcMethodProvider
+	{
+		/// <summary>
+		/// List of types to match against
+		/// </summary>
+		public IReadOnlyList<Type> Types { get; }
+
+		private List<MethodInfo> methodCache { get; set; }
+
+		/// <param name="types">List of types to match against</param>
+		public ControllerPublicMethodProvider(List<Type> types)
+		{
+			if (types == null || !types.Any())
+			{
+				throw new ArgumentException("At least one type must be specified.", nameof(types));
+			}
+			this.Types = types;
+		}
+
+		/// <param name="type">Type to match against</param>
+		public ControllerPublicMethodProvider(Type type)
+		{
+			if (type == null)
+			{
+				throw new ArgumentNullException(nameof(type));
+			}
+			this.Types = new List<Type> { type };
+		}
+		
+		public List<MethodInfo> GetRouteMethods()
+		{
+			if (this.methodCache == null)
+			{
+				var methods = new List<MethodInfo>();
+				foreach (Type type in this.Types)
+				{
+					List<MethodInfo> publicMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
+						//Ignore ToString, GetHashCode and Equals
+						.Where(m => m.DeclaringType != typeof(object))
+						.ToList();
+					methods.AddRange(publicMethods);
+				}
+				this.methodCache = methods;
+			}
+			return this.methodCache;
+		}
+	}
+}

+ 76 - 0
JsonRPCTest/RpcRouter/ParsingResult.cs

@@ -0,0 +1,76 @@
+using EdjCase.JsonRpc.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Edjcase.JsonRpc.Router
+{
+	public class ParsingResult
+	{
+		/// <summary>
+		/// Successfully parsed request
+		/// </summary>
+		public List<RpcRequest> Requests { get; }
+		/// <summary>
+		/// Errors with the associated request id
+		/// </summary>
+		public List<(RpcId Id, RpcError Error)> Errors { get; }
+		/// <summary>
+		/// Flag to indicate if the request was an array vs singular
+		/// </summary>
+		public bool IsBulkRequest { get; }
+		/// <summary>
+		/// Count of total requests processed (successful and failed)
+		/// </summary>
+		public int RequestCount { get; }
+
+		public ParsingResult(List<RpcRequest> requests, List<(RpcId, RpcError)> errors, bool isBulkRequest)
+		{
+			this.Requests = requests;
+			this.Errors = errors;
+			this.IsBulkRequest = isBulkRequest;
+			this.RequestCount = requests.Count + errors.Count;
+		}
+
+		internal static ParsingResult FromResults(List<RpcRequestParseResult> results, bool isBulkRequest)
+		{
+			var requests = new List<RpcRequest>();
+			var errors = new List<(RpcId, RpcError)>();
+			foreach (RpcRequestParseResult result in results)
+			{
+				if (result.Error != null)
+				{
+					errors.Add((result.Id, result.Error));
+				}
+				else
+				{
+					requests.Add(result.Request);
+				}
+			}
+			return new ParsingResult(requests, errors, isBulkRequest);
+		}
+	}
+
+	internal class RpcRequestParseResult
+	{
+		public RpcId Id { get; }
+		public RpcRequest Request { get; }
+		public RpcError Error { get; }
+		private RpcRequestParseResult(RpcId id, RpcRequest request, RpcError error)
+		{
+			this.Id = id;
+			this.Request = request;
+			this.Error = error;
+		}
+
+		public static RpcRequestParseResult Success(RpcRequest request)
+		{
+			return new RpcRequestParseResult(request.Id, request, null);
+		}
+
+		public static RpcRequestParseResult Fail(RpcId id, RpcError error)
+		{
+			return new RpcRequestParseResult(id, null, error);
+		}
+	}
+}

+ 99 - 0
JsonRPCTest/RpcRouter/RouteProviders/RpcAutoRouteProvider.cs

@@ -0,0 +1,99 @@
+#if !NETSTANDARD1_3
+using System.Collections.Generic;
+using System.Linq;
+using EdjCase.JsonRpc.Router.Abstractions;
+using System;
+using System.Reflection;
+using EdjCase.JsonRpc.Router.MethodProviders;
+using Microsoft.Extensions.Options;
+
+namespace EdjCase.JsonRpc.Router.RouteProviders
+{
+	/// <summary>
+	/// Default route provider to give the router the configured routes to use
+	/// </summary>
+	public class RpcAutoRouteProvider : IRpcRouteProvider
+	{
+		public IOptions<RpcAutoRoutingOptions> Options { get; }
+
+		public RpcAutoRouteProvider(IOptions<RpcAutoRoutingOptions> options)
+		{
+			this.Options = options ?? throw new ArgumentNullException(nameof(options));
+		}
+
+		public RpcPath BaseRequestPath => this.Options.Value.BaseRequestPath;
+
+
+		private Dictionary<RpcPath, List<IRpcMethodProvider>> routeCache { get; set; }
+
+
+		private Dictionary<RpcPath, List<IRpcMethodProvider>> GetAllRoutes()
+		{
+			if (this.routeCache == null)
+			{
+				//TODO will entry assembly be good enough
+				List<TypeInfo> controllerTypes = Assembly.GetEntryAssembly().DefinedTypes
+					.Where(t => !t.IsAbstract && t.IsSubclassOf(this.Options.Value.BaseControllerType))
+					.ToList();
+
+				var controllerRoutes = new Dictionary<RpcPath, List<IRpcMethodProvider>>();
+				foreach (TypeInfo controllerType in controllerTypes)
+				{
+					var attribute = controllerType.GetCustomAttribute<RpcRouteAttribute>(true);
+					string routePathString;
+					if (attribute == null || attribute.RouteName == null)
+					{
+						if (controllerType.Name.EndsWith("Controller"))
+						{
+							routePathString = controllerType.Name.Substring(0, controllerType.Name.IndexOf("Controller"));
+						}
+						else
+						{
+							routePathString = controllerType.Name;
+						}
+					}
+					else
+					{
+						routePathString = attribute.RouteName;
+					}
+					RpcPath routePath = RpcPath.Parse(routePathString);
+					if (!controllerRoutes.TryGetValue(routePath, out List<IRpcMethodProvider> methodProviders))
+					{
+						methodProviders = new List<IRpcMethodProvider>();
+						controllerRoutes[routePath] = methodProviders;
+					}
+					methodProviders.Add(new ControllerPublicMethodProvider(controllerType.AsType()));
+				}
+				this.routeCache = controllerRoutes;
+			}
+			return this.routeCache;
+		}
+
+		/// <summary>
+		/// Gets all the routes from all the controllers derived from the 
+		/// configured base controller type
+		/// </summary>
+		/// <returns>All the available routes</returns>
+		public HashSet<RpcPath> GetRoutes()
+		{
+			Dictionary<RpcPath, List<IRpcMethodProvider>> routes = this.GetAllRoutes();
+			return new HashSet<RpcPath>(routes.Keys);
+		}
+
+		/// <summary>
+		/// Gets all the method providers for the specified path
+		/// </summary>
+		/// <param name="path">Path to the methods</param>
+		/// <returns>All method providers for the specified path</returns>
+		public List<IRpcMethodProvider> GetMethodsByPath(RpcPath path)
+		{
+			Dictionary<RpcPath, List<IRpcMethodProvider>> routes = this.GetAllRoutes();
+			if (!routes.TryGetValue(path, out List<IRpcMethodProvider> methods))
+			{
+				return new List<IRpcMethodProvider>();
+			}
+			return methods;
+		}
+	}
+}
+#endif

+ 52 - 0
JsonRPCTest/RpcRouter/RouteProviders/RpcManualRouteProvider.cs

@@ -0,0 +1,52 @@
+using EdjCase.JsonRpc.Router.Abstractions;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EdjCase.JsonRpc.Router.RouteProviders
+{
+	public class RpcManualRouteProvider : IRpcRouteProvider
+	{
+		private IOptions<RpcManualRoutingOptions> Options { get; }
+
+		public RpcManualRouteProvider(IOptions<RpcManualRoutingOptions> options)
+		{
+			this.Options = options ?? throw new ArgumentNullException(nameof(options));
+		}
+
+
+		public RpcPath BaseRequestPath => this.Options.Value.BaseRequestPath;
+
+
+		/// <summary>
+		/// Gets all the routes from all the cofiguration
+		/// </summary>
+		/// <returns>All the available routes</returns>
+		public HashSet<RpcPath> GetRoutes()
+		{
+			if(this.Options.Value.Routes?.Keys != null)
+			{
+				return new HashSet<RpcPath>(this.Options.Value.Routes.Keys);
+			}
+			return new HashSet<RpcPath>();
+		}
+
+		/// <summary>
+		/// Gets all the method providers for the specified path
+		/// </summary>
+		/// <param name="path">Path to the methods</param>
+		/// <returns>All method providers for the specified path</returns>
+		public List<IRpcMethodProvider> GetMethodsByPath(RpcPath path)
+		{
+			if(this.Options.Value.Routes == null 
+				|| !this.Options.Value.Routes.TryGetValue(path, out List<IRpcMethodProvider> methods))
+			{
+				return new List<IRpcMethodProvider>();
+			}
+			return methods;
+		}
+	}
+}

+ 42 - 0
JsonRPCTest/RpcRouter/RouteProviders/RpcSingleRouteProvider.cs

@@ -0,0 +1,42 @@
+using EdjCase.JsonRpc.Router.Abstractions;
+using EdjCase.JsonRpc.Router.MethodProviders;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EdjCase.JsonRpc.Router.RouteProviders
+{
+	public class RpcSingleRouteProvider : IRpcRouteProvider
+	{
+		public IOptions<SingleRouteOptions> Options { get; }
+		public RpcPath BaseRequestPath { get; }
+
+		public RpcSingleRouteProvider(IOptions<SingleRouteOptions> options)
+		{
+			this.Options = options ?? throw new ArgumentNullException(nameof(options));
+			this.BaseRequestPath = this.Options.Value?.BaseRequestPath ?? RpcPath.Default;
+		}
+
+
+		/// <summary>
+		/// Gets all the method providers for the specified path
+		/// </summary>
+		/// <param name="path">Path to the methods</param>
+		/// <returns>All method providers for the specified path</returns>
+		public List<IRpcMethodProvider> GetMethodsByPath(RpcPath path) => this.Options.Value?.MethodProviders ?? new List<IRpcMethodProvider>();
+	}
+
+	public class SingleRouteOptions
+	{
+		public List<IRpcMethodProvider> MethodProviders { get; set; } = new List<IRpcMethodProvider>();
+		public RpcPath BaseRequestPath { get; set; } = RpcPath.Default;
+
+		public void AddClass<T>()
+		{
+			this.MethodProviders.Add(new ControllerPublicMethodProvider(typeof(T)));
+		}
+	}
+}

+ 0 - 0
JsonRPCTest/RpcRouter/RpcController.cs


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff