diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 0000000..66cdc24 --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,40 @@ + + +Please answer these questions before submitting your issue. + +- Why do you submit this issue? +- [ ] Question or discussion +- [ ] Bug +- [ ] Requirement +- [ ] Feature or performance improvement + +___ +### Question +- What do you want to know? + +___ +### Bug +- Which version of CAP, OS and .NET Core? + +- Which company or project? + +- What happen? +If possible, provide a way for reproducing the error. e.g. demo application, component version. + +___ +### Requirement or improvement +- Please describe about your requirements or improvement suggestions. \ No newline at end of file diff --git a/CAP.sln b/CAP.sln index cee4bf6..828d82a 100644 --- a/CAP.sln +++ b/CAP.sln @@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution CHANGELOG.md = CHANGELOG.md CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md ConfigureMSDTC.ps1 = ConfigureMSDTC.ps1 + .github\ISSUE_TEMPLATE = .github\ISSUE_TEMPLATE LICENSE.txt = LICENSE.txt README.md = README.md README.zh-cn.md = README.zh-cn.md @@ -54,15 +55,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MySql.Test", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.MySql", "samples\Sample.RabbitMQ.MySql\Sample.RabbitMQ.MySql.csproj", "{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.SqlServer", "samples\Sample.RabbitMQ.SqlServer\Sample.RabbitMQ.SqlServer.csproj", "{AF17B956-B79E-48B7-9B5B-EB15A386B112}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.PostgreSql", "src\DotNetCore.CAP.PostgreSql\DotNetCore.CAP.PostgreSql.csproj", "{82C403AB-ED68-4084-9A1D-11334F9F08F9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.PostgreSql", "samples\Sample.RabbitMQ.PostgreSql\Sample.RabbitMQ.PostgreSql.csproj", "{A17E8E72-DFFC-4822-BB38-73D59A8B264E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.PostgreSql.Test", "test\DotNetCore.CAP.PostgreSql.Test\DotNetCore.CAP.PostgreSql.Test.csproj", "{7CA3625D-1817-4695-881D-7E79A1E1DED2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.SqlServer", "samples\Sample.Kafka.SqlServer\Sample.Kafka.SqlServer.csproj", "{573B4D39-5489-48B3-9B6C-5234249CB980}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.MySql", "samples\Sample.Kafka.MySql\Sample.Kafka.MySql.csproj", "{9CB51105-A85B-42A4-AFDE-A4FC34D9EA91}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -105,26 +102,18 @@ Global {9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Debug|Any CPU.Build.0 = Debug|Any CPU {9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Release|Any CPU.ActiveCfg = Release|Any CPU {9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Release|Any CPU.Build.0 = Release|Any CPU - {AF17B956-B79E-48B7-9B5B-EB15A386B112}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF17B956-B79E-48B7-9B5B-EB15A386B112}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF17B956-B79E-48B7-9B5B-EB15A386B112}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF17B956-B79E-48B7-9B5B-EB15A386B112}.Release|Any CPU.Build.0 = Release|Any CPU {82C403AB-ED68-4084-9A1D-11334F9F08F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {82C403AB-ED68-4084-9A1D-11334F9F08F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {82C403AB-ED68-4084-9A1D-11334F9F08F9}.Release|Any CPU.ActiveCfg = Release|Any CPU {82C403AB-ED68-4084-9A1D-11334F9F08F9}.Release|Any CPU.Build.0 = Release|Any CPU - {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A17E8E72-DFFC-4822-BB38-73D59A8B264E}.Release|Any CPU.Build.0 = Release|Any CPU {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Release|Any CPU.ActiveCfg = Release|Any CPU {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Release|Any CPU.Build.0 = Release|Any CPU - {573B4D39-5489-48B3-9B6C-5234249CB980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {573B4D39-5489-48B3-9B6C-5234249CB980}.Debug|Any CPU.Build.0 = Debug|Any CPU - {573B4D39-5489-48B3-9B6C-5234249CB980}.Release|Any CPU.ActiveCfg = Release|Any CPU - {573B4D39-5489-48B3-9B6C-5234249CB980}.Release|Any CPU.Build.0 = Release|Any CPU + {9CB51105-A85B-42A4-AFDE-A4FC34D9EA91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CB51105-A85B-42A4-AFDE-A4FC34D9EA91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CB51105-A85B-42A4-AFDE-A4FC34D9EA91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CB51105-A85B-42A4-AFDE-A4FC34D9EA91}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -139,11 +128,9 @@ Global {FA15685A-778A-4D2A-A2FE-27FAD2FFA65B} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {80A84F62-1558-427B-BA74-B47AA8A665B5} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} {9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873} = {3A6B6931-A123-477A-9469-8B468B5385AF} - {AF17B956-B79E-48B7-9B5B-EB15A386B112} = {3A6B6931-A123-477A-9469-8B468B5385AF} {82C403AB-ED68-4084-9A1D-11334F9F08F9} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} - {A17E8E72-DFFC-4822-BB38-73D59A8B264E} = {3A6B6931-A123-477A-9469-8B468B5385AF} {7CA3625D-1817-4695-881D-7E79A1E1DED2} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} - {573B4D39-5489-48B3-9B6C-5234249CB980} = {3A6B6931-A123-477A-9469-8B468B5385AF} + {9CB51105-A85B-42A4-AFDE-A4FC34D9EA91} = {3A6B6931-A123-477A-9469-8B468B5385AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} diff --git a/ConfigureMSDTC.ps1 b/ConfigureMSDTC.ps1 deleted file mode 100644 index d574b34..0000000 --- a/ConfigureMSDTC.ps1 +++ /dev/null @@ -1,40 +0,0 @@ -# -# Enable MSDTC -# - -Write-Host "Enabling MSDTC..." -ForegroundColor Yellow -$DTCSecurity = "Incoming" -$RegPath = "HKLM:\SOFTWARE\Microsoft\MSDTC\" - -#Set Security and MSDTC path - -$RegSecurityPath = "$RegPath\Security" - -Set-ItemProperty path $RegSecurityPath name "NetworkDtcAccess" value 1 -Set-ItemProperty path $RegSecurityPath name "NetworkDtcAccessClients" value 1 -Set-ItemProperty path $RegSecurityPath name "NetworkDtcAccessTransactions" value 1 -Set-ItemProperty path $RegSecurityPath name "NetworkDtcAccessInbound" value 1 -Set-ItemProperty path $RegSecurityPath name "NetworkDtcAccessOutbound" value 1 -Set-ItemProperty path $RegSecurityPath name "LuTransactions" value 1 - -if ($DTCSecurity eq "None") -{ - Set-ItemProperty path $RegPath name "TurnOffRpcSecurity" value 1 - Set-ItemProperty path $RegPath name "AllowOnlySecureRpcCalls" value 0 - Set-ItemProperty path $RegPath name "FallbackToUnsecureRPCIfNecessary" value 0 -} -elseif ($DTCSecurity eq "Incoming") -{ - Set-ItemProperty path $RegPath name "TurnOffRpcSecurity" value 0 - Set-ItemProperty path $RegPath name "AllowOnlySecureRpcCalls" value 0 - Set-ItemProperty path $RegPath name "FallbackToUnsecureRPCIfNecessary" value 1 -} -else -{ - Set-ItemProperty path $RegPath name "TurnOffRpcSecurity" value 0 - Set-ItemProperty path $RegPath name "AllowOnlySecureRpcCalls" value 1 - Set-ItemProperty path $RegPath name "FallbackToUnsecureRPCIfNecessary" value 0 -} - -Restart-Service MSDTC -Write-Host "MSDTC has been configured" foregroundcolor green diff --git a/README.md b/README.md index 14d2d09..4dbadde 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![AppVeyor](https://ci.appveyor.com/api/projects/status/4mpe0tbu7n126vyw?svg=true)](https://ci.appveyor.com/project/yuleyule66/cap) [![NuGet](https://img.shields.io/nuget/v/DotNetCore.CAP.svg)](https://www.nuget.org/packages/DotNetCore.CAP/) [![NuGet Preview](https://img.shields.io/nuget/vpre/DotNetCore.CAP.svg?label=nuget-pre)](https://www.nuget.org/packages/DotNetCore.CAP/) -[![Member project of .NET China Foundation](https://img.shields.io/badge/member_project_of-.NET_CHINA-red.svg?style=flat&colorB=9E20C8)](https://github.com/dotnetcore) +[![Member project of .NET Core Community](https://img.shields.io/badge/member%20project%20of-NCC-9e20c9.svg)](https://github.com/dotnetcore) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dotnetcore/CAP/master/LICENSE.txt) CAP is a library based on .Net standard, which is a solution to deal with distributed transactions, also has the function of EventBus, it is lightweight, easy to use, and efficiently. @@ -187,7 +187,10 @@ Then inject your `ISubscriberService` class in Startup.cs ```c# public void ConfigureServices(IServiceCollection services) { + //Note: The injection of services needs before of `services.AddCap()` services.AddTransient(); + + services.AddCap(x=>{}); } ``` @@ -218,6 +221,8 @@ services.AddCap(x => }); ``` +The default dashboard address is :[http://localhost:xxx/cap](http://localhost:xxx/cap) , you can also change the `cap` suffix to others with `d.MatchPath` configuration options. + ![dashboard](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220827302-189215107.png) ![received](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220934115-1107747665.png) diff --git a/README.zh-cn.md b/README.zh-cn.md index 76d18dc..639e4c5 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -3,7 +3,7 @@ [![AppVeyor](https://ci.appveyor.com/api/projects/status/4mpe0tbu7n126vyw?svg=true)](https://ci.appveyor.com/project/yuleyule66/cap) [![NuGet](https://img.shields.io/nuget/v/DotNetCore.CAP.svg)](https://www.nuget.org/packages/DotNetCore.CAP/) [![NuGet Preview](https://img.shields.io/nuget/vpre/DotNetCore.CAP.svg?label=nuget-pre)](https://www.nuget.org/packages/DotNetCore.CAP/) -[![Member project of .NET China Foundation](https://img.shields.io/badge/member_project_of-.NET_CHINA-red.svg?style=flat&colorB=9E20C8)](https://github.com/dotnetcore) +[![Member project of .NET Core Community](https://img.shields.io/badge/member%20project%20of-NCC-9e20c9.svg)](https://github.com/dotnetcore) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dotnetcore/CAP/master/LICENSE.txt) CAP 是一个基于 .NET Standard 的 C# 库,它是一种处理分布式事务的解决方案,同样具有 EventBus 的功能,它具有轻量级、易使用、高性能等特点。 @@ -187,7 +187,10 @@ namespace xxx.Service ```c# public void ConfigureServices(IServiceCollection services) { + //注意: 注入的服务需要在 `services.AddCap()` 之前 services.AddTransient(); + + services.AddCap(x=>{}); } ``` @@ -218,6 +221,8 @@ services.AddCap(x => }); ``` +仪表盘默认的访问地址是:[http://localhost:xxx/cap](http://localhost:xxx/cap),你可以在`d.MatchPath`配置项中修改`cap`路径后缀为其他的名字。 + ![dashboard](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220827302-189215107.png) ![received](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220934115-1107747665.png) diff --git a/appveyor.yml b/appveyor.yml index 05802e7..2bdfaba 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,7 +11,6 @@ services: - mysql - postgresql build_script: -- ps: ./ConfigureMSDTC.ps1 - ps: ./build.ps1 test: off artifacts: diff --git a/build.cake b/build.cake index 02b52c4..1b68025 100644 --- a/build.cake +++ b/build.cake @@ -68,6 +68,7 @@ Task("Pack") { Configuration = build.Configuration, VersionSuffix = build.Version.Suffix, + IncludeSymbols = true, OutputDirectory = "./artifacts/packages" }; foreach (var project in build.ProjectFiles) diff --git a/build/version.props b/build/version.props index 4ba57d7..dcd0b81 100644 --- a/build/version.props +++ b/build/version.props @@ -1,7 +1,7 @@ 2 - 1 + 2 3 $(VersionMajor).$(VersionMinor).$(VersionPatch) diff --git a/samples/Sample.Kafka.MySql/Controllers/ValuesController.cs b/samples/Sample.Kafka.MySql/Controllers/ValuesController.cs new file mode 100644 index 0000000..6f04946 --- /dev/null +++ b/samples/Sample.Kafka.MySql/Controllers/ValuesController.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using DotNetCore.CAP; +using Microsoft.AspNetCore.Mvc; +using MySql.Data.MySqlClient; + +namespace Sample.Kafka.MySql.Controllers +{ + [Route("api/[controller]")] + public class ValuesController : Controller, ICapSubscribe + { + private readonly ICapPublisher _capBus; + + public ValuesController(ICapPublisher producer) + { + _capBus = producer; + } + + [Route("~/publish")] + public async Task PublishMessage() + { + using (var connection = new MySqlConnection("Server=192.168.10.110;Database=testcap;UserId=root;Password=123123;")) + { + connection.Open(); + var transaction = connection.BeginTransaction(); + + //your business code here + + await _capBus.PublishAsync("xxx.xxx.test2", 123456, transaction); + + transaction.Commit(); + } + + return Ok("publish successful!"); + } + + [CapSubscribe("xxx.xxx.test2")] + public void Test2(int value) + { + Console.WriteLine("Subscriber output message: " + value); + } + } +} \ No newline at end of file diff --git a/samples/Sample.Kafka.SqlServer/Program.cs b/samples/Sample.Kafka.MySql/Program.cs similarity index 55% rename from samples/Sample.Kafka.SqlServer/Program.cs rename to samples/Sample.Kafka.MySql/Program.cs index c8cd00a..976b20a 100644 --- a/samples/Sample.Kafka.SqlServer/Program.cs +++ b/samples/Sample.Kafka.MySql/Program.cs @@ -1,10 +1,8 @@ -using System.IO; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; +using NLog.Web; -namespace Sample.Kafka.SqlServer +namespace Sample.Kafka.MySql { public class Program { @@ -17,7 +15,11 @@ namespace Sample.Kafka.SqlServer public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() + .ConfigureLogging((hostingContext, builder) => + { + hostingContext.HostingEnvironment.ConfigureNLog("nlog.config"); + }) + .UseNLog() .Build(); - } } \ No newline at end of file diff --git a/samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj b/samples/Sample.Kafka.MySql/Sample.Kafka.MySql.csproj similarity index 59% rename from samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj rename to samples/Sample.Kafka.MySql/Sample.Kafka.MySql.csproj index 47ab06b..bf80ce5 100644 --- a/samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj +++ b/samples/Sample.Kafka.MySql/Sample.Kafka.MySql.csproj @@ -2,21 +2,28 @@ netcoreapp2.0 - Sample.Kafka.SqlServer + Sample.Kafka.MySql NU1701 NU1701 - + + + - + + + + PreserveNewest + + diff --git a/samples/Sample.Kafka.MySql/Startup.cs b/samples/Sample.Kafka.MySql/Startup.cs new file mode 100644 index 0000000..59c4636 --- /dev/null +++ b/samples/Sample.Kafka.MySql/Startup.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Sample.Kafka.MySql +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddCap(x => + { + x.UseMySql("Server=localhost;Database=testcap;UserId=root;Password=123123;"); + x.UseKafka("localhost:9092"); + x.UseDashboard(); + }); + + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseMvc(); + + app.UseCap(); + } + } +} \ No newline at end of file diff --git a/samples/Sample.Kafka.MySql/appsettings.json b/samples/Sample.Kafka.MySql/appsettings.json new file mode 100644 index 0000000..20aa907 --- /dev/null +++ b/samples/Sample.Kafka.MySql/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug" + } + } +} diff --git a/samples/Sample.Kafka.SqlServer/AppDbContext.cs b/samples/Sample.Kafka.SqlServer/AppDbContext.cs deleted file mode 100644 index 78d6492..0000000 --- a/samples/Sample.Kafka.SqlServer/AppDbContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Sample.Kafka.SqlServer -{ - public class AppDbContext : DbContext - { - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - //optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=Sample.Kafka.SqlServer;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True"); - optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=TestCap;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True"); - } - } -} diff --git a/samples/Sample.Kafka.SqlServer/CmsContentSerializer.cs b/samples/Sample.Kafka.SqlServer/CmsContentSerializer.cs deleted file mode 100644 index dbbd736..0000000 --- a/samples/Sample.Kafka.SqlServer/CmsContentSerializer.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using DotNetCore.CAP.Abstractions; -using DotNetCore.CAP.Models; -using Newtonsoft.Json; - -namespace Sample.RabbitMQ.SqlServer -{ - public class MessageContent : CapMessage - { - [JsonProperty("id")] - public override string Id { get; set; } - - [JsonProperty("createdTime")] - public override DateTime Timestamp { get; set; } - - [JsonProperty("msgBody")] - public override string Content { get; set; } - - [JsonProperty("callbackTopicName")] - public override string CallbackName { get; set; } - } - - public class MyMessagePacker : IMessagePacker - { - private readonly IContentSerializer _serializer; - - public MyMessagePacker(IContentSerializer serializer) - { - _serializer = serializer; - } - - public string Pack(CapMessage obj) - { - var content = new MessageContent - { - Id = obj.Id, - Content = obj.Content, - CallbackName = obj.CallbackName, - Timestamp = obj.Timestamp - }; - return _serializer.Serialize(content); - } - - public CapMessage UnPack(string packingMessage) - { - return _serializer.DeSerialize(packingMessage); - } - } -} - diff --git a/samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs b/samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs deleted file mode 100644 index 77a28b3..0000000 --- a/samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using DotNetCore.CAP; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; - -namespace Sample.Kafka.SqlServer.Controllers -{ - public class Person - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("uname")] - public string Name { get; set; } - - public HAHA Haha { get; set; } - - public override string ToString() - { - return "Name:" + Name + ";Id:" + Id + "Haha:" + Haha?.ToString(); - } - } - - public class HAHA - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("uname")] - public string Name { get; set; } - public override string ToString() - { - return "Name:" + Name + ";Id:" + Id; - } - } - - - [Route("api/[controller]")] - public class ValuesController : Controller, ICapSubscribe - { - private readonly ICapPublisher _capBus; - private readonly AppDbContext _dbContext; - - public ValuesController(ICapPublisher producer, AppDbContext dbContext) - { - _capBus = producer; - _dbContext = dbContext; - } - - - [Route("~/publish")] - public IActionResult PublishMessage() - { - var p = new Person - { - Id = Guid.NewGuid().ToString(), - Name = "杨晓东", - Haha = new HAHA - { - Id = Guid.NewGuid().ToString(), - Name = "1-1杨晓东", - } - }; - - _capBus.Publish("wl.yxd.test", p, "wl.yxd.test.callback"); - - - //_capBus.Publish("wl.cj.test", p); - return Ok(); - } - - [CapSubscribe("wl.yxd.test.callback")] - public void KafkaTestCallback(Person p) - { - Console.WriteLine("回调内容:" + p); - } - - - [CapSubscribe("wl.cj.test")] - public string KafkaTestReceived(Person person) - { - Console.WriteLine(person); - Debug.WriteLine(person); - return "this is callback message"; - } - - [Route("~/publishWithTrans")] - public async Task PublishMessageWithTransaction() - { - using (var trans = await _dbContext.Database.BeginTransactionAsync()) - { - await _capBus.PublishAsync("sample.rabbitmq.mysql", ""); - - trans.Commit(); - } - return Ok(); - } - - [CapSubscribe("sample.rabbitmq.mysql33333", Group = "Test.Group")] - public void KafkaTest22(Person person) - { - var aa = _dbContext.Database; - - _dbContext.Dispose(); - - Console.WriteLine("[sample.kafka.sqlserver] message received " + person.ToString()); - Debug.WriteLine("[sample.kafka.sqlserver] message received " + person.ToString()); - } - - //[CapSubscribe("sample.rabbitmq.mysql22222")] - //public void KafkaTest22(DateTime time) - //{ - // Console.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); - // Debug.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); - //} - - [CapSubscribe("sample.rabbitmq.mysql22222")] - public async Task KafkaTest33(DateTime time) - { - Console.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); - Debug.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); - return await Task.FromResult(time); - } - - [NonAction] - [CapSubscribe("sample.kafka.sqlserver3")] - [CapSubscribe("sample.kafka.sqlserver4")] - public void KafkaTest() - { - Console.WriteLine("[sample.kafka.sqlserver] message received"); - Debug.WriteLine("[sample.kafka.sqlserver] message received"); - } - } -} \ No newline at end of file diff --git a/samples/Sample.Kafka.SqlServer/Startup.cs b/samples/Sample.Kafka.SqlServer/Startup.cs deleted file mode 100644 index bc15d25..0000000 --- a/samples/Sample.Kafka.SqlServer/Startup.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Sample.RabbitMQ.SqlServer; - -namespace Sample.Kafka.SqlServer -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddDbContext(); - - services.AddCap(x => - { - x.UseEntityFramework(); - x.UseKafka("192.168.2.215:9092"); - x.UseDashboard(); - //x.UseDiscovery(d => - //{ - // d.DiscoveryServerHostName = "localhost"; - // d.DiscoveryServerPort = 8500; - // d.CurrentNodeHostName = "localhost"; - // d.CurrentNodePort = 5820; - // d.NodeName = "CAP 2号节点"; - //}); - }).AddMessagePacker(); - services.AddMvc(); - } - - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - app.UseMvc(); - - app.UseCap(); - } - } -} \ No newline at end of file diff --git a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs index f803e26..d960a8f 100644 --- a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace Sample.RabbitMQ.MySql { @@ -10,8 +6,7 @@ namespace Sample.RabbitMQ.MySql { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseMySql("Server=localhost;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;Allow User Variables=True"); - //optionsBuilder.UseMySql("Server=192.168.2.206;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;"); + optionsBuilder.UseMySql("Server=localhost;Database=testcap;UserId=root;Password=123123;"); } } } diff --git a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs index 0b394cb..1b47e5e 100644 --- a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using DotNetCore.CAP; using Microsoft.AspNetCore.Mvc; @@ -24,7 +21,7 @@ namespace Sample.RabbitMQ.MySql.Controllers public IActionResult PublishMessage() { _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now); - + return Ok(); } @@ -53,7 +50,7 @@ namespace Sample.RabbitMQ.MySql.Controllers [CapSubscribe("sample.rabbitmq.mysql")] public void ReceiveMessage(DateTime time) { - Console.WriteLine("[sample.rabbitmq.mysql] message received: "+ DateTime.Now.ToString() +" , sent time: " + time.ToString()); + Console.WriteLine("[sample.rabbitmq.mysql] message received: " + DateTime.Now + ",sent time: " + time); } } } diff --git a/samples/Sample.RabbitMQ.MySql/Program.cs b/samples/Sample.RabbitMQ.MySql/Program.cs index 94095e2..3cbbe15 100644 --- a/samples/Sample.RabbitMQ.MySql/Program.cs +++ b/samples/Sample.RabbitMQ.MySql/Program.cs @@ -1,12 +1,6 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; +using NLog.Web; namespace Sample.RabbitMQ.MySql { @@ -20,6 +14,11 @@ namespace Sample.RabbitMQ.MySql public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() + .ConfigureLogging((hostingContext, builder) => + { + hostingContext.HostingEnvironment.ConfigureNLog("nlog.config"); + }) + .UseNLog() .Build(); } } diff --git a/samples/Sample.RabbitMQ.MySql/Properties/launchSettings.json b/samples/Sample.RabbitMQ.MySql/Properties/launchSettings.json new file mode 100644 index 0000000..369b41c --- /dev/null +++ b/samples/Sample.RabbitMQ.MySql/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:57171/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "cap", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Sample.RabbitMQ.MySql": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "cap", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:57173/" + } + } +} \ No newline at end of file diff --git a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj index c7f5883..5c981a3 100644 --- a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj +++ b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj @@ -10,8 +10,9 @@ - + + @@ -21,5 +22,10 @@ + + + PreserveNewest + + diff --git a/samples/Sample.RabbitMQ.MySql/Startup.cs b/samples/Sample.RabbitMQ.MySql/Startup.cs index 12a2986..6525770 100644 --- a/samples/Sample.RabbitMQ.MySql/Startup.cs +++ b/samples/Sample.RabbitMQ.MySql/Startup.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/samples/Sample.RabbitMQ.MySql/appsettings.json b/samples/Sample.RabbitMQ.MySql/appsettings.json new file mode 100644 index 0000000..20aa907 --- /dev/null +++ b/samples/Sample.RabbitMQ.MySql/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug" + } + } +} diff --git a/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs b/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs deleted file mode 100644 index d4bde28..0000000 --- a/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; - -namespace Sample.RabbitMQ.PostgreSql -{ - public class AppDbContext : DbContext - { - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseNpgsql("Server=localhost;Database=Sample.RabbitMQ.PostgreSql;UserId=postgres;Password=123123;"); - } - } -} diff --git a/samples/Sample.RabbitMQ.PostgreSql/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.PostgreSql/Controllers/ValuesController.cs deleted file mode 100644 index 10ea6d1..0000000 --- a/samples/Sample.RabbitMQ.PostgreSql/Controllers/ValuesController.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading.Tasks; -using DotNetCore.CAP; -using Microsoft.AspNetCore.Mvc; - -namespace Sample.RabbitMQ.PostgreSql.Controllers -{ - [Route("api/[controller]")] - public class ValuesController : Controller - { - private readonly AppDbContext _dbContext; - private readonly ICapPublisher _capBus; - - public ValuesController(AppDbContext dbContext, ICapPublisher capPublisher) - { - _dbContext = dbContext; - _capBus = capPublisher; - } - - [Route("~/publish")] - public IActionResult PublishMessage() - { - _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now); - - return Ok(); - } - - - [Route("~/publish2")] - public IActionResult PublishMessage2() - { - _capBus.Publish("sample.kafka.sqlserver4", DateTime.Now); - - return Ok(); - } - - [Route("~/publishWithTrans")] - public async Task PublishMessageWithTransaction() - { - using (var trans = await _dbContext.Database.BeginTransactionAsync()) - { - await _capBus.PublishAsync("sample.kafka.sqlserver", ""); - - trans.Commit(); - } - return Ok(); - } - - [NonAction] - [CapSubscribe("sample.rabbitmq.mysql")] - public void ReceiveMessage() - { - Console.WriteLine("[sample.rabbitmq.mysql] message received"); - Debug.WriteLine("[sample.rabbitmq.mysql] message received"); - } - } -} diff --git a/samples/Sample.RabbitMQ.PostgreSql/Program.cs b/samples/Sample.RabbitMQ.PostgreSql/Program.cs deleted file mode 100644 index 3cb0755..0000000 --- a/samples/Sample.RabbitMQ.PostgreSql/Program.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; - -namespace Sample.RabbitMQ.PostgreSql -{ - public class Program - { - public static void Main(string[] args) - { - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); - } -} diff --git a/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj b/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj deleted file mode 100644 index 5310334..0000000 --- a/samples/Sample.RabbitMQ.PostgreSql/Sample.RabbitMQ.PostgreSql.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp2.0 - - - - - - - - - - - - - - diff --git a/samples/Sample.RabbitMQ.PostgreSql/Startup.cs b/samples/Sample.RabbitMQ.PostgreSql/Startup.cs deleted file mode 100644 index b0477c0..0000000 --- a/samples/Sample.RabbitMQ.PostgreSql/Startup.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace Sample.RabbitMQ.PostgreSql -{ - public class Startup - { - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddDbContext(); - services.AddCap(x => - { - x.UseEntityFramework(); - x.UseRabbitMQ("localhost"); - x.UseDashboard(); - x.UseDiscovery(d => - { - d.DiscoveryServerHostName = "localhost"; - d.DiscoveryServerPort = 8500; - d.CurrentNodeHostName = "localhost"; - d.CurrentNodePort = 5800; - d.NodeName = "CAP 一号节点"; - }); - }); - services.AddMvc(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) - { - app.UseMvc(); - - app.UseCap(); - } - } -} diff --git a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs deleted file mode 100644 index 1ba1fdb..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Sample.RabbitMQ.SqlServer.Controllers; - -namespace Sample.RabbitMQ.SqlServer -{ - public class AppDbContext : DbContext - { - - public DbSet Persons { get; set; } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - //optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=TestCap;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True"); - optionsBuilder.UseSqlServer("Server=DESKTOP-M9R8T31;Initial Catalog=Sample.Kafka.SqlServer;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=True"); - } - } -} diff --git a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs deleted file mode 100644 index 6c0ccef..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using DotNetCore.CAP; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Sample.RabbitMQ.SqlServer.Controllers -{ - public class Person - { - public int Id { get; set; } - public string Name { get; set; } - public int Age { get; set; } - - public override string ToString() - { - return "Name:" + Name + ";Age:" + Age; - } - } - - - [Route("api/[controller]")] - public class ValuesController : Controller, ICapSubscribe - { - private readonly ICapPublisher _capBus; - private readonly AppDbContext _dbContext; - - public ValuesController(ICapPublisher producer, AppDbContext dbContext) - { - _capBus = producer; - _dbContext = dbContext; - } - - [Route("~/publish")] - public IActionResult PublishMessage() - { - - _capBus.Publish("sample.rabbitmq.sqlserver.order.check", DateTime.Now); - - //var person = new Person - //{ - // Name = "杨晓东", - // Age = 11, - // Id = 23 - //}; - //_capBus.Publish("sample.rabbitmq.mysql33333", person); - - return Ok(); - } - - [Route("~/publishWithTrans")] - public async Task PublishMessageWithTransaction() - { - using (var trans = await _dbContext.Database.BeginTransactionAsync()) - { - await _capBus.PublishAsync("sample.rabbitmq.mysql", ""); - - trans.Commit(); - } - return Ok(); - } - - [CapSubscribe("sample.rabbitmq.mysql33333",Group ="Test.Group")] - public void KafkaTest22(Person person) - { - var aa = _dbContext.Database; - - _dbContext.Dispose(); - - Console.WriteLine("[sample.kafka.sqlserver] message received " + person.ToString()); - Debug.WriteLine("[sample.kafka.sqlserver] message received " + person.ToString()); - } - - //[CapSubscribe("sample.rabbitmq.mysql22222")] - //public void KafkaTest22(DateTime time) - //{ - // Console.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); - // Debug.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); - //} - - [CapSubscribe("sample.rabbitmq.mysql22222")] - public async Task KafkaTest33(DateTime time) - { - Console.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); - Debug.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); - return await Task.FromResult(time); - } - - [NonAction] - [CapSubscribe("sample.kafka.sqlserver3")] - [CapSubscribe("sample.kafka.sqlserver4")] - public void KafkaTest() - { - Console.WriteLine("[sample.kafka.sqlserver] message received"); - Debug.WriteLine("[sample.kafka.sqlserver] message received"); - } - } -} \ No newline at end of file diff --git a/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.Designer.cs b/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.Designer.cs deleted file mode 100644 index 3d8122b..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.Designer.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Storage.Internal; -using Sample.RabbitMQ.SqlServer; -using System; - -namespace Sample.RabbitMQ.SqlServer.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20170824130007_AddPersons")] - partial class AddPersons - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Sample.RabbitMQ.SqlServer.Controllers.Person", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Age"); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("Persons"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.cs b/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.cs deleted file mode 100644 index 2fc048b..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using System; -using System.Collections.Generic; - -namespace Sample.RabbitMQ.SqlServer.Migrations -{ - public partial class AddPersons : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Persons", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - Age = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Persons", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Persons"); - } - } -} diff --git a/samples/Sample.RabbitMQ.SqlServer/Migrations/AppDbContextModelSnapshot.cs b/samples/Sample.RabbitMQ.SqlServer/Migrations/AppDbContextModelSnapshot.cs deleted file mode 100644 index 0a20ac3..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Migrations/AppDbContextModelSnapshot.cs +++ /dev/null @@ -1,39 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Storage.Internal; -using Sample.RabbitMQ.SqlServer; -using System; - -namespace Sample.RabbitMQ.SqlServer.Migrations -{ - [DbContext(typeof(AppDbContext))] - partial class AppDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Sample.RabbitMQ.SqlServer.Controllers.Person", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Age"); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("Persons"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/Sample.RabbitMQ.SqlServer/Program.cs b/samples/Sample.RabbitMQ.SqlServer/Program.cs deleted file mode 100644 index ebbd50e..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.IO; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; - -namespace Sample.RabbitMQ.SqlServer -{ - public class Program - { - - public static void Main(string[] args) - { - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseUrls("http://*:5800") - .UseStartup() - .Build(); - - } -} \ No newline at end of file diff --git a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj deleted file mode 100644 index 97ec8d2..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netcoreapp2.0 - Sample.RabbitMQ.SqlServer - - - - - - - - - - - - - - - diff --git a/samples/Sample.RabbitMQ.SqlServer/Services/ICmsService.cs b/samples/Sample.RabbitMQ.SqlServer/Services/ICmsService.cs deleted file mode 100644 index 5119cce..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Services/ICmsService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using DotNetCore.CAP; - -namespace Sample.RabbitMQ.SqlServer.Services -{ - public interface ICmsService - { - void Add(); - } -} diff --git a/samples/Sample.RabbitMQ.SqlServer/Services/IOrderService.cs b/samples/Sample.RabbitMQ.SqlServer/Services/IOrderService.cs deleted file mode 100644 index 9f8e3ad..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Services/IOrderService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sample.RabbitMQ.SqlServer.Services -{ - public interface IOrderService - { - void Check(); - } -} diff --git a/samples/Sample.RabbitMQ.SqlServer/Services/Impl/CmsService.cs b/samples/Sample.RabbitMQ.SqlServer/Services/Impl/CmsService.cs deleted file mode 100644 index b8c7800..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Services/Impl/CmsService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using DotNetCore.CAP; - -namespace Sample.RabbitMQ.SqlServer.Services.Impl -{ - public class CmsService : ICmsService, ICapSubscribe - { - public void Add() - { - throw new NotImplementedException(); - } - } -} diff --git a/samples/Sample.RabbitMQ.SqlServer/Services/Impl/OrderService.cs b/samples/Sample.RabbitMQ.SqlServer/Services/Impl/OrderService.cs deleted file mode 100644 index 81d0eb7..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Services/Impl/OrderService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using DotNetCore.CAP; - -namespace Sample.RabbitMQ.SqlServer.Services.Impl -{ - public class OrderService : IOrderService, ICapSubscribe - { - [CapSubscribe("sample.rabbitmq.sqlserver.order.check")] - public void Check() - { - Console.WriteLine("out"); - } - } -} diff --git a/samples/Sample.RabbitMQ.SqlServer/Startup.cs b/samples/Sample.RabbitMQ.SqlServer/Startup.cs deleted file mode 100644 index 4821b97..0000000 --- a/samples/Sample.RabbitMQ.SqlServer/Startup.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Sample.RabbitMQ.SqlServer.Services; -using Sample.RabbitMQ.SqlServer.Services.Impl; - -namespace Sample.RabbitMQ.SqlServer -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddDbContext(); - - services.AddScoped(); - services.AddTransient(); - - services.AddCap(x => - { - x.UseEntityFramework(); - x.UseRabbitMQ("localhost"); - x.UseDashboard(); - x.UseDiscovery(d => - { - d.DiscoveryServerHostName = "localhost"; - d.DiscoveryServerPort = 8500; - - d.CurrentNodeHostName = "192.168.1.11"; - d.CurrentNodePort = 5800; - d.NodeName = "CAP Node Windows"; - d.NodeId = 1; - }); - }); - - services.AddMvc(); - } - - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - loggerFactory.AddConsole(); - loggerFactory.AddDebug(); - - app.UseMvc(); - - app.UseCap(); - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs b/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs index b2e2129..88db8fe 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Kafka; using Microsoft.Extensions.DependencyInjection; @@ -23,9 +26,9 @@ namespace DotNetCore.CAP services.AddSingleton(kafkaOptions); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs index 1b1f687..0e4161b 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -45,16 +48,21 @@ namespace DotNetCore.CAP if (_kafkaConfig == null) { if (string.IsNullOrWhiteSpace(Servers)) + { throw new ArgumentNullException(nameof(Servers)); + } MainConfig["bootstrap.servers"] = Servers; MainConfig["queue.buffering.max.ms"] = "10"; MainConfig["socket.blocking.max.ms"] = "10"; MainConfig["enable.auto.commit"] = "false"; MainConfig["log.connection.close"] = "false"; - + MainConfig["request.timeout.ms"] = "3000"; + MainConfig["message.timeout.ms"] = "5000"; + _kafkaConfig = MainConfig.AsEnumerable(); } + return _kafkaConfig; } } diff --git a/src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs index be980d6..e32278e 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP; // ReSharper disable once CheckNamespace @@ -24,7 +27,10 @@ namespace Microsoft.Extensions.DependencyInjection /// public static CapOptions UseKafka(this CapOptions options, Action configure) { - if (configure == null) throw new ArgumentNullException(nameof(configure)); + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } options.RegisterExtension(new KafkaCapOptionsExtension(configure)); diff --git a/src/DotNetCore.CAP.Kafka/CAP.SubscribeAttribute.cs b/src/DotNetCore.CAP.Kafka/CAP.SubscribeAttribute.cs index 161b073..ad4021e 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.SubscribeAttribute.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.SubscribeAttribute.cs @@ -1,4 +1,7 @@ -using DotNetCore.CAP.Abstractions; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using DotNetCore.CAP.Abstractions; // ReSharper disable once CheckNamespace namespace DotNetCore.CAP diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index 2bc9d2f..72e580e 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -8,14 +8,14 @@ $(PackageTags);Kafka - + NU1605;NU1701 NU1701;CS1591 - bin\Debug\netstandard2.0\DotNetCore.CAP.Kafka.xml + bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.Kafka.xml - + diff --git a/src/DotNetCore.CAP.Kafka/ConnectionPool.cs b/src/DotNetCore.CAP.Kafka/IConnectionPool.Default.cs similarity index 68% rename from src/DotNetCore.CAP.Kafka/ConnectionPool.cs rename to src/DotNetCore.CAP.Kafka/IConnectionPool.Default.cs index 3746439..21b8398 100644 --- a/src/DotNetCore.CAP.Kafka/ConnectionPool.cs +++ b/src/DotNetCore.CAP.Kafka/IConnectionPool.Default.cs @@ -1,25 +1,38 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Threading; using Confluent.Kafka; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; namespace DotNetCore.CAP.Kafka { public class ConnectionPool : IConnectionPool, IDisposable { + private readonly ILogger _logger; private readonly Func _activator; - private readonly ConcurrentQueue _pool = new ConcurrentQueue(); + private readonly ConcurrentQueue _pool; private int _count; - private int _maxSize; - public ConnectionPool(KafkaOptions options) + public ConnectionPool(ILogger logger, KafkaOptions options) { + _logger = logger; + _pool = new ConcurrentQueue(); _maxSize = options.ConnectionPoolSize; _activator = CreateActivator(options); + ServersAddress = options.Servers; + + _logger.LogDebug("Kafka configuration of CAP :\r\n {0}", + JsonConvert.SerializeObject(options.AsKafkaConfig(), Formatting.Indented)); } + public string ServersAddress { get; } + Producer IConnectionPool.Rent() { return Rent(); @@ -35,7 +48,9 @@ namespace DotNetCore.CAP.Kafka _maxSize = 0; while (_pool.TryDequeue(out var context)) + { context.Dispose(); + } } private static Func CreateActivator(KafkaOptions options) diff --git a/src/DotNetCore.CAP.Kafka/IConnectionPool.cs b/src/DotNetCore.CAP.Kafka/IConnectionPool.cs index 36d5912..ed86d7f 100644 --- a/src/DotNetCore.CAP.Kafka/IConnectionPool.cs +++ b/src/DotNetCore.CAP.Kafka/IConnectionPool.cs @@ -1,10 +1,14 @@ - +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using Confluent.Kafka; namespace DotNetCore.CAP.Kafka { public interface IConnectionPool { + string ServersAddress { get; } + Producer Rent(); bool Return(Producer context); diff --git a/src/DotNetCore.CAP.Kafka/IPublishMessageSender.Kafka.cs b/src/DotNetCore.CAP.Kafka/IPublishMessageSender.Kafka.cs new file mode 100644 index 0000000..4527eda --- /dev/null +++ b/src/DotNetCore.CAP.Kafka/IPublishMessageSender.Kafka.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Text; +using System.Threading.Tasks; +using DotNetCore.CAP.Internal; +using DotNetCore.CAP.Processor.States; +using Microsoft.Extensions.Logging; + +namespace DotNetCore.CAP.Kafka +{ + internal class KafkaPublishMessageSender : BasePublishMessageSender + { + private readonly IConnectionPool _connectionPool; + private readonly ILogger _logger; + + public KafkaPublishMessageSender( + CapOptions options, IStateChanger stateChanger, IStorageConnection connection, + IConnectionPool connectionPool, ILogger logger) + : base(logger, options, connection, stateChanger) + { + _logger = logger; + _connectionPool = connectionPool; + ServersAddress = _connectionPool.ServersAddress; + } + + public override async Task PublishAsync(string keyName, string content) + { + var producer = _connectionPool.Rent(); + + try + { + var contentBytes = Encoding.UTF8.GetBytes(content); + var message = await producer.ProduceAsync(keyName, null, contentBytes); + + if (message.Error.HasError) + { + throw new PublisherSentFailedException(message.Error.ToString()); + } + + _logger.LogDebug($"kafka topic message [{keyName}] has been published."); + + return OperateResult.Success; + } + catch (Exception ex) + { + var wapperEx = new PublisherSentFailedException(ex.Message, ex); + + return OperateResult.Failed(wapperEx); + } + finally + { + var returned = _connectionPool.Return(producer); + if (!returned) + { + producer.Dispose(); + } + } + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs b/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs index 0ebfc1f..f279afb 100644 --- a/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs +++ b/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Text; using System.Threading; @@ -26,13 +29,19 @@ namespace DotNetCore.CAP.Kafka public event EventHandler OnLog; + public string ServersAddress => _kafkaOptions.Servers; + public void Subscribe(IEnumerable topics) { if (topics == null) + { throw new ArgumentNullException(nameof(topics)); + } if (_consumerClient == null) + { InitKafkaClient(); + } _consumerClient.Subscribe(topics); } @@ -44,6 +53,7 @@ namespace DotNetCore.CAP.Kafka cancellationToken.ThrowIfCancellationRequested(); _consumerClient.Poll(timeout); } + // ReSharper disable once FunctionNeverReturns } @@ -54,7 +64,7 @@ namespace DotNetCore.CAP.Kafka public void Reject() { - // Ignore, Kafka will not commit offset when not commit. + _consumerClient.Assign(_consumerClient.Assignment); } public void Dispose() @@ -66,16 +76,18 @@ namespace DotNetCore.CAP.Kafka private void InitKafkaClient() { - _kafkaOptions.MainConfig["group.id"] = _groupId; + lock (_kafkaOptions) + { + _kafkaOptions.MainConfig["group.id"] = _groupId; - var config = _kafkaOptions.AsKafkaConfig(); - _consumerClient = new Consumer(config, null, StringDeserializer); - _consumerClient.OnConsumeError += ConsumerClient_OnConsumeError; - _consumerClient.OnMessage += ConsumerClient_OnMessage; - _consumerClient.OnError += ConsumerClient_OnError; + var config = _kafkaOptions.AsKafkaConfig(); + _consumerClient = new Consumer(config, null, StringDeserializer); + _consumerClient.OnConsumeError += ConsumerClient_OnConsumeError; + _consumerClient.OnMessage += ConsumerClient_OnMessage; + _consumerClient.OnError += ConsumerClient_OnError; + } } - private void ConsumerClient_OnConsumeError(object sender, Message e) { var message = e.Deserialize(null, StringDeserializer); diff --git a/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs b/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs index 8bda50a..54ddc5b 100644 --- a/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs +++ b/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Kafka +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Kafka { internal sealed class KafkaConsumerClientFactory : IConsumerClientFactory { diff --git a/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs b/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs deleted file mode 100644 index 6bbda6b..0000000 --- a/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Text; -using System.Threading.Tasks; -using DotNetCore.CAP.Processor.States; -using Microsoft.Extensions.Logging; - -namespace DotNetCore.CAP.Kafka -{ - internal class PublishQueueExecutor : BasePublishQueueExecutor - { - private readonly ConnectionPool _connectionPool; - private readonly ILogger _logger; - - public PublishQueueExecutor( - CapOptions options, - IStateChanger stateChanger, - ConnectionPool connectionPool, - ILogger logger) - : base(options, stateChanger, logger) - { - _logger = logger; - _connectionPool = connectionPool; - } - - public override async Task PublishAsync(string keyName, string content) - { - var producer = _connectionPool.Rent(); - try - { - var contentBytes = Encoding.UTF8.GetBytes(content); - - var message = await producer.ProduceAsync(keyName, null, contentBytes); - - if (!message.Error.HasError) - { - _logger.LogDebug($"kafka topic message [{keyName}] has been published."); - - return OperateResult.Success; - } - return OperateResult.Failed(new OperateError - { - Code = message.Error.Code.ToString(), - Description = message.Error.Reason - }); - - } - catch (Exception ex) - { - _logger.LogError(ex, - $"An error occurred during sending the topic message to kafka. Topic:[{keyName}], Exception: {ex.Message}"); - - return OperateResult.Failed(ex); - } - finally - { - var returned = _connectionPool.Return(producer); - if (!returned) - producer.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/CAP.EFOptions.cs b/src/DotNetCore.CAP.MySql/CAP.EFOptions.cs index 3cd77a3..c2b9983 100644 --- a/src/DotNetCore.CAP.MySql/CAP.EFOptions.cs +++ b/src/DotNetCore.CAP.MySql/CAP.EFOptions.cs @@ -1,10 +1,20 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; // ReSharper disable once CheckNamespace namespace DotNetCore.CAP { public class EFOptions { + public const string DefaultSchema = "cap"; + + /// + /// Gets or sets the table name prefix to use when creating database objects. + /// + public string TableNamePrefix { get; set; } = DefaultSchema; + /// /// EF db context type. /// diff --git a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs index 3d29fa1..0c58540 100644 --- a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.MySql; using DotNetCore.CAP.Processor; using Microsoft.EntityFrameworkCore; @@ -23,24 +26,34 @@ namespace DotNetCore.CAP services.AddSingleton(); services.AddScoped(); services.AddScoped(); - services.AddTransient(); + services.AddTransient(); + + AddSingletionMySqlOptions(services); + } + private void AddSingletionMySqlOptions(IServiceCollection services) + { var mysqlOptions = new MySqlOptions(); + _configure(mysqlOptions); if (mysqlOptions.DbContextType != null) + { services.AddSingleton(x => { using (var scope = x.CreateScope()) { var provider = scope.ServiceProvider; - var dbContext = (DbContext) provider.GetService(mysqlOptions.DbContextType); + var dbContext = (DbContext)provider.GetService(mysqlOptions.DbContextType); mysqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; return mysqlOptions; } }); + } else + { services.AddSingleton(mysqlOptions); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs b/src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs index 5bec849..bcac400 100644 --- a/src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs +++ b/src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs @@ -1,4 +1,5 @@ -// ReSharper disable once CheckNamespace +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. namespace DotNetCore.CAP { @@ -8,7 +9,5 @@ namespace DotNetCore.CAP /// Gets or sets the database's connection string that will be used to store database entities. /// public string ConnectionString { get; set; } - - public string TableNamePrefix { get; set; } = "cap"; } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs index 5f66f6c..f59b75d 100644 --- a/src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP; using Microsoft.EntityFrameworkCore; @@ -14,7 +17,11 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseMySql(this CapOptions options, Action configure) { - if (configure == null) throw new ArgumentNullException(nameof(configure)); + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + options.RegisterExtension(new MySqlCapOptionsExtension(configure)); @@ -24,18 +31,22 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseEntityFramework(this CapOptions options) where TContext : DbContext { - return options.UseEntityFramework(opt => { opt.DbContextType = typeof(TContext); }); + return options.UseEntityFramework(opt => { }); } public static CapOptions UseEntityFramework(this CapOptions options, Action configure) where TContext : DbContext { - if (configure == null) throw new ArgumentNullException(nameof(configure)); - - var efOptions = new EFOptions {DbContextType = typeof(TContext)}; - configure(efOptions); - - options.RegisterExtension(new MySqlCapOptionsExtension(configure)); + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + options.RegisterExtension(new MySqlCapOptionsExtension(x => + { + configure(x); + x.DbContextType = typeof(TContext); + })); return options; } diff --git a/src/DotNetCore.CAP.MySql/CapPublisher.cs b/src/DotNetCore.CAP.MySql/CapPublisher.cs index 276335b..662e257 100644 --- a/src/DotNetCore.CAP.MySql/CapPublisher.cs +++ b/src/DotNetCore.CAP.MySql/CapPublisher.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Data; using System.Threading.Tasks; using Dapper; @@ -14,27 +17,31 @@ namespace DotNetCore.CAP.MySql public class CapPublisher : CapPublisherBase, ICallbackPublisher { private readonly DbContext _dbContext; - private readonly ILogger _logger; private readonly MySqlOptions _options; - public CapPublisher(IServiceProvider provider, - ILogger logger, + public CapPublisher(ILogger logger, IDispatcher dispatcher, IServiceProvider provider, MySqlOptions options) + : base(logger, dispatcher) { ServiceProvider = provider; _options = options; - _logger = logger; - if (_options.DbContextType == null) return; + if (_options.DbContextType == null) + { + return; + } + IsUsingEF = true; _dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType); } - public async Task PublishAsync(CapPublishedMessage message) + public async Task PublishCallbackAsync(CapPublishedMessage message) { using (var conn = new MySqlConnection(_options.ConnectionString)) { - await conn.ExecuteAsync(PrepareSql(), message); + var id = await conn.ExecuteScalarAsync(PrepareSql(), message); + message.Id = id; + Enqueue(message); } } @@ -51,25 +58,20 @@ namespace DotNetCore.CAP.MySql dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); dbTrans = dbContextTransaction.GetDbTransaction(); } + DbTransaction = dbTrans; } - protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, + protected override int Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) { - dbConnection.Execute(PrepareSql(), message, dbTransaction); - - _logger.LogInformation("Published Message has been persisted in the database. name:" + message); + return dbConnection.ExecuteScalar(PrepareSql(), message, dbTransaction); } - protected override Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, + protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) { - dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); - - _logger.LogInformation("Published Message has been persisted in the database. name:" + message); - - return Task.CompletedTask; + return await dbConnection.ExecuteScalarAsync(PrepareSql(), message, dbTransaction); } #region private methods @@ -77,7 +79,7 @@ namespace DotNetCore.CAP.MySql private string PrepareSql() { return - $"INSERT INTO `{_options.TableNamePrefix}.published` (`Name`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; + $"INSERT INTO `{_options.TableNamePrefix}.published` (`Name`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName);SELECT LAST_INSERT_ID()"; } #endregion private methods diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index cb75f46..1e85be1 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -8,16 +8,16 @@ $(PackageTags);MySQL - - bin\Debug\netstandard2.0\DotNetCore.CAP.MySql.xml + + bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.MySql.xml 1701;1702;1705;CS1591 - - - + + + diff --git a/src/DotNetCore.CAP.MySql/FetchedMessage.cs b/src/DotNetCore.CAP.MySql/FetchedMessage.cs deleted file mode 100644 index a0f3b8e..0000000 --- a/src/DotNetCore.CAP.MySql/FetchedMessage.cs +++ /dev/null @@ -1,11 +0,0 @@ -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP.MySql -{ - internal class FetchedMessage - { - public int MessageId { get; set; } - - public MessageType MessageType { get; set; } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.MySql/ICollectProcessor.MySql.cs similarity index 80% rename from src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs rename to src/DotNetCore.CAP.MySql/ICollectProcessor.MySql.cs index a59d9e2..4cd0a23 100644 --- a/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.MySql/ICollectProcessor.MySql.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Threading.Tasks; using Dapper; using DotNetCore.CAP.Processor; @@ -7,7 +10,7 @@ using MySql.Data.MySqlClient; namespace DotNetCore.CAP.MySql { - internal class DefaultAdditionalProcessor : IAdditionalProcessor + internal class MySqlCollectProcessor : ICollectProcessor { private const int MaxBatch = 1000; private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); @@ -15,7 +18,7 @@ namespace DotNetCore.CAP.MySql private readonly MySqlOptions _options; private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); - public DefaultAdditionalProcessor(ILogger logger, + public MySqlCollectProcessor(ILogger logger, MySqlOptions mysqlOptions) { _logger = logger; @@ -24,8 +27,6 @@ namespace DotNetCore.CAP.MySql public async Task ProcessAsync(ProcessingContext context) { - _logger.LogDebug("Collecting expired entities."); - var tables = new[] { $"{_options.TableNamePrefix}.published", @@ -34,6 +35,8 @@ namespace DotNetCore.CAP.MySql foreach (var table in tables) { + _logger.LogDebug($"Collecting expired data from table [{table}]."); + int removedCount; do { diff --git a/src/DotNetCore.CAP.MySql/MySqlFetchedMessage.cs b/src/DotNetCore.CAP.MySql/MySqlFetchedMessage.cs deleted file mode 100644 index c4ab741..0000000 --- a/src/DotNetCore.CAP.MySql/MySqlFetchedMessage.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Dapper; -using DotNetCore.CAP.Models; -using MySql.Data.MySqlClient; - -namespace DotNetCore.CAP.MySql -{ - public class MySqlFetchedMessage : IFetchedMessage - { - private readonly MySqlOptions _options; - private readonly string _processId; - - public MySqlFetchedMessage(int messageId, MessageType type, string processId, MySqlOptions options) - { - MessageId = messageId; - MessageType = type; - - _processId = processId; - _options = options; - } - - public int MessageId { get; } - - public MessageType MessageType { get; } - - public void RemoveFromQueue() - { - using (var connection = new MySqlConnection(_options.ConnectionString)) - { - connection.Execute($"DELETE FROM `{_options.TableNamePrefix}.queue` WHERE `ProcessId`=@ProcessId" - , new { ProcessId = _processId }); - } - } - - public void Requeue() - { - using (var connection = new MySqlConnection(_options.ConnectionString)) - { - connection.Execute($"UPDATE `{_options.TableNamePrefix}.queue` SET `ProcessId`=NULL WHERE `ProcessId`=@ProcessId" - , new { ProcessId = _processId }); - } - } - - public void Dispose() - { - // ignored - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/MySqlMonitoringApi.cs b/src/DotNetCore.CAP.MySql/MySqlMonitoringApi.cs index 49fd270..6fd0888 100644 --- a/src/DotNetCore.CAP.MySql/MySqlMonitoringApi.cs +++ b/src/DotNetCore.CAP.MySql/MySqlMonitoringApi.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -28,9 +31,7 @@ set transaction isolation level read committed; select count(Id) from `{0}.published` where StatusName = N'Succeeded'; select count(Id) from `{0}.received` where StatusName = N'Succeeded'; select count(Id) from `{0}.published` where StatusName = N'Failed'; -select count(Id) from `{0}.received` where StatusName = N'Failed'; -select count(Id) from `{0}.published` where StatusName in (N'Processing',N'Scheduled',N'Enqueued'); -select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Scheduled',N'Enqueued');", _prefix); +select count(Id) from `{0}.received` where StatusName = N'Failed';", _prefix); var statistics = UseConnection(connection => { @@ -42,10 +43,8 @@ select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Sched stats.PublishedFailed = multi.ReadSingle(); stats.ReceivedFailed = multi.ReadSingle(); - - stats.PublishedProcessing = multi.ReadSingle(); - stats.ReceivedProcessing = multi.ReadSingle(); } + return stats; }); return statistics; @@ -70,17 +69,24 @@ select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Sched var tableName = queryDto.MessageType == MessageType.Publish ? "published" : "received"; var where = string.Empty; if (!string.IsNullOrEmpty(queryDto.StatusName)) - if (string.Equals(queryDto.StatusName, StatusName.Processing, - StringComparison.CurrentCultureIgnoreCase)) - where += " and StatusName in (N'Processing',N'Scheduled',N'Enqueued')"; - else - where += " and StatusName=@StatusName"; + { + where += " and StatusName=@StatusName"; + } + if (!string.IsNullOrEmpty(queryDto.Name)) + { where += " and Name=@Name"; + } + if (!string.IsNullOrEmpty(queryDto.Group)) + { where += " and Group=@Group"; + } + if (!string.IsNullOrEmpty(queryDto.Content)) + { where += " and Content like '%@Content%'"; + } var sqlQuery = $"select * from `{_prefix}.{tableName}` where 1=1 {where} order by Added desc limit @Limit offset @Offset"; @@ -101,11 +107,6 @@ select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Sched return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Failed)); } - public int PublishedProcessingCount() - { - return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Processing)); - } - public int PublishedSucceededCount() { return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Succeeded)); @@ -116,11 +117,6 @@ select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Sched return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Failed)); } - public int ReceivedProcessingCount() - { - return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Processing)); - } - public int ReceivedSucceededCount() { return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Succeeded)); @@ -128,9 +124,7 @@ select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Sched private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName) { - var sqlQuery = statusName == StatusName.Processing - ? $"select count(Id) from `{_prefix}.{tableName}` where StatusName in (N'Processing',N'Scheduled',N'Enqueued')" - : $"select count(Id) from `{_prefix}.{tableName}` where StatusName = @state"; + var sqlQuery = $"select count(Id) from `{_prefix}.{tableName}` where StatusName = @state"; var count = connection.ExecuteScalar(sqlQuery, new {state = statusName}); return count; @@ -179,7 +173,12 @@ select aggr.* from ( .ToDictionary(x => (string) x.Key, x => (int) x.Count); foreach (var key in keyMaps.Keys) - if (!valuesMap.ContainsKey(key)) valuesMap.Add(key, 0); + { + if (!valuesMap.ContainsKey(key)) + { + valuesMap.Add(key, 0); + } + } var result = new Dictionary(); for (var i = 0; i < keyMaps.Count; i++) diff --git a/src/DotNetCore.CAP.MySql/MySqlStorage.cs b/src/DotNetCore.CAP.MySql/MySqlStorage.cs index 3755b0d..98fa0de 100644 --- a/src/DotNetCore.CAP.MySql/MySqlStorage.cs +++ b/src/DotNetCore.CAP.MySql/MySqlStorage.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Data; using System.Threading; @@ -11,10 +14,10 @@ namespace DotNetCore.CAP.MySql { public class MySqlStorage : IStorage { + private readonly CapOptions _capOptions; private readonly IDbConnection _existingConnection = null; private readonly ILogger _logger; private readonly MySqlOptions _options; - private readonly CapOptions _capOptions; public MySqlStorage(ILogger logger, MySqlOptions options, @@ -37,7 +40,11 @@ namespace DotNetCore.CAP.MySql public async Task InitializeAsync(CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) return; + if (cancellationToken.IsCancellationRequested) + { + return; + } + var sql = CreateDbTablesScript(_options.TableNamePrefix); using (var connection = new MySqlConnection(_options.ConnectionString)) { @@ -51,11 +58,7 @@ namespace DotNetCore.CAP.MySql { var batchSql = $@" -CREATE TABLE IF NOT EXISTS `{prefix}.queue` ( - `MessageId` int(11) NOT NULL, - `MessageType` tinyint(4) NOT NULL, - `ProcessId` varchar(50) DEFAULT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `{prefix}.queue`; CREATE TABLE IF NOT EXISTS `{prefix}.received` ( `Id` int(127) NOT NULL AUTO_INCREMENT, @@ -102,7 +105,9 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` ( var connection = _existingConnection ?? new MySqlConnection(_options.ConnectionString); if (connection.State == ConnectionState.Closed) + { connection.Open(); + } return connection; } @@ -115,7 +120,9 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` ( internal void ReleaseConnection(IDbConnection connection) { if (connection != null && !IsExistingConnection(connection)) + { connection.Dispose(); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs b/src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs index 1ed4e0e..691717d 100644 --- a/src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs +++ b/src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -13,8 +16,6 @@ namespace DotNetCore.CAP.MySql private readonly CapOptions _capOptions; private readonly string _prefix; - private const string DateTimeMaxValue = "9999-12-31 23:59:59"; - public MySqlStorageConnection(MySqlOptions options, CapOptions capOptions) { _capOptions = capOptions; @@ -39,50 +40,32 @@ namespace DotNetCore.CAP.MySql } } - public Task FetchNextMessageAsync() + public async Task> GetPublishedMessagesOfNeedRetry() { - var processId = ObjectId.GenerateNewStringId(); - var sql = $@" -UPDATE `{_prefix}.queue` SET `ProcessId`=@ProcessId WHERE `ProcessId` IS NULL LIMIT 1; -SELECT `MessageId`,`MessageType` FROM `{_prefix}.queue` WHERE `ProcessId`=@ProcessId;"; - - return FetchNextMessageCoreAsync(sql, processId); - } - - public async Task GetNextPublishedMessageToBeEnqueuedAsync() - { - var sql = $@" -UPDATE `{_prefix}.published` SET Id=LAST_INSERT_ID(Id),ExpiresAt='{DateTimeMaxValue}' WHERE ExpiresAt IS NULL AND `StatusName` = '{StatusName.Scheduled}' LIMIT 1; -SELECT * FROM `{_prefix}.published` WHERE Id=LAST_INSERT_ID();"; + var fourMinsAgo = DateTime.Now.AddMinutes(-4); + var sql = + $"SELECT * FROM `{_prefix}.published` WHERE `Retries`<{_capOptions.FailedRetryCount} AND `Added`<'{fourMinsAgo}' AND (`StatusName` = '{StatusName.Failed}' OR `StatusName` = '{StatusName.Scheduled}') LIMIT 200;"; using (var connection = new MySqlConnection(Options.ConnectionString)) { - connection.Execute("SELECT LAST_INSERT_ID(0)"); - return await connection.QueryFirstOrDefaultAsync(sql); + return await connection.QueryAsync(sql); } } - public async Task> GetFailedPublishedMessages() + public async Task StoreReceivedMessageAsync(CapReceivedMessage message) { - var sql = $"SELECT * FROM `{_prefix}.published` WHERE `Retries`<{_capOptions.FailedRetryCount} AND `StatusName` = '{StatusName.Failed}' LIMIT 200;"; - - using (var connection = new MySqlConnection(Options.ConnectionString)) + if (message == null) { - return await connection.QueryAsync(sql); + throw new ArgumentNullException(nameof(message)); } - } - - public async Task StoreReceivedMessageAsync(CapReceivedMessage message) - { - if (message == null) throw new ArgumentNullException(nameof(message)); var sql = $@" INSERT INTO `{_prefix}.received`(`Name`,`Group`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`) -VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; +VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);SELECT LAST_INSERT_ID();"; using (var connection = new MySqlConnection(Options.ConnectionString)) { - await connection.ExecuteAsync(sql, message); + return await connection.ExecuteScalarAsync(sql, message); } } @@ -95,22 +78,11 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; } } - public async Task GetNextReceivedMessageToBeEnqueuedAsync() + public async Task> GetReceivedMessagesOfNeedRetry() { - var sql = $@" -UPDATE `{_prefix}.received` SET Id=LAST_INSERT_ID(Id),ExpiresAt='{DateTimeMaxValue}' WHERE ExpiresAt IS NULL AND `StatusName` = '{StatusName.Scheduled}' LIMIT 1; -SELECT * FROM `{_prefix}.received` WHERE Id=LAST_INSERT_ID();"; - - using (var connection = new MySqlConnection(Options.ConnectionString)) - { - connection.Execute("SELECT LAST_INSERT_ID(0)"); - return await connection.QueryFirstOrDefaultAsync(sql); - } - } - - public async Task> GetFailedReceivedMessages() - { - var sql = $"SELECT * FROM `{_prefix}.received` WHERE `Retries`<{_capOptions.FailedRetryCount} AND `StatusName` = '{StatusName.Failed}' LIMIT 200;"; + var fourMinsAgo = DateTime.Now.AddMinutes(-4); + var sql = + $"SELECT * FROM `{_prefix}.received` WHERE `Retries`<{_capOptions.FailedRetryCount} AND `Added`<'{fourMinsAgo}' AND (`StatusName` = '{StatusName.Failed}' OR `StatusName` = '{StatusName.Scheduled}') LIMIT 200;"; using (var connection = new MySqlConnection(Options.ConnectionString)) { return await connection.QueryAsync(sql); @@ -139,20 +111,6 @@ SELECT * FROM `{_prefix}.received` WHERE Id=LAST_INSERT_ID();"; } } - private async Task FetchNextMessageCoreAsync(string sql, string processId) - { - FetchedMessage fetchedMessage; - using (var connection = new MySqlConnection(Options.ConnectionString)) - { - fetchedMessage = await connection.QuerySingleOrDefaultAsync(sql, new { ProcessId = processId }); - } - - if (fetchedMessage == null) - return null; - - return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, processId, Options); - } - public void Dispose() { } diff --git a/src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs b/src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs index 0dbb424..7121b09 100644 --- a/src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs +++ b/src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Data; using System.Threading.Tasks; using Dapper; @@ -11,7 +14,7 @@ namespace DotNetCore.CAP.MySql { private readonly IDbConnection _dbConnection; - private readonly IDbTransaction _dbTransaction; + //private readonly IDbTransaction _dbTransaction; private readonly string _prefix; public MySqlStorageTransaction(MySqlStorageConnection connection) @@ -20,55 +23,45 @@ namespace DotNetCore.CAP.MySql _prefix = options.TableNamePrefix; _dbConnection = new MySqlConnection(options.ConnectionString); - _dbConnection.Open(); - _dbTransaction = _dbConnection.BeginTransaction(IsolationLevel.ReadCommitted); + // _dbConnection.Open(); for performance + // _dbTransaction = _dbConnection.BeginTransaction(IsolationLevel.ReadCommitted); } public void UpdateMessage(CapPublishedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $"UPDATE `{_prefix}.published` SET `Retries` = @Retries,`Content`= @Content,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; - _dbConnection.Execute(sql, message, _dbTransaction); + _dbConnection.Execute(sql, message); } public void UpdateMessage(CapReceivedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $"UPDATE `{_prefix}.received` SET `Retries` = @Retries,`Content`= @Content,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; - _dbConnection.Execute(sql, message, _dbTransaction); - } - - public void EnqueueMessage(CapPublishedMessage message) - { - if (message == null) throw new ArgumentNullException(nameof(message)); - - var sql = $"INSERT INTO `{_prefix}.queue`(`MessageId`,`MessageType`) values(@MessageId,@MessageType);"; - _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish}, - _dbTransaction); - } - - public void EnqueueMessage(CapReceivedMessage message) - { - if (message == null) throw new ArgumentNullException(nameof(message)); - - var sql = $"INSERT INTO `{_prefix}.queue`(`MessageId`,`MessageType`) values(@MessageId,@MessageType);"; - _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe}, - _dbTransaction); + _dbConnection.Execute(sql, message); } public Task CommitAsync() { - _dbTransaction.Commit(); + _dbConnection.Close(); + _dbConnection.Dispose(); + //_dbTransaction.Commit(); return Task.CompletedTask; } public void Dispose() { - _dbTransaction.Dispose(); + //_dbTransaction.Dispose(); _dbConnection.Dispose(); } } diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs b/src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs index 294ed6a..883d297 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; // ReSharper disable once CheckNamespace namespace DotNetCore.CAP diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs index 9fde846..08ffb90 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP; using Microsoft.EntityFrameworkCore; @@ -14,7 +17,10 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UsePostgreSql(this CapOptions options, Action configure) { - if (configure == null) throw new ArgumentNullException(nameof(configure)); + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } options.RegisterExtension(new PostgreSqlCapOptionsExtension(configure)); @@ -24,18 +30,22 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseEntityFramework(this CapOptions options) where TContext : DbContext { - return options.UseEntityFramework(opt => { opt.DbContextType = typeof(TContext); }); + return options.UseEntityFramework(opt => { }); } public static CapOptions UseEntityFramework(this CapOptions options, Action configure) where TContext : DbContext { - if (configure == null) throw new ArgumentNullException(nameof(configure)); - - var efOptions = new EFOptions {DbContextType = typeof(TContext)}; - configure(efOptions); - - options.RegisterExtension(new PostgreSqlCapOptionsExtension(configure)); + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + options.RegisterExtension(new PostgreSqlCapOptionsExtension(x => + { + configure(x); + x.DbContextType = typeof(TContext); + })); return options; } diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs index e2d4a14..edc1729 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.PostgreSql; using DotNetCore.CAP.Processor; using Microsoft.EntityFrameworkCore; @@ -23,24 +26,33 @@ namespace DotNetCore.CAP services.AddSingleton(); services.AddScoped(); services.AddScoped(); - services.AddTransient(); + services.AddTransient(); + + AddSingletonPostgreSqlOptions(services); + } + private void AddSingletonPostgreSqlOptions(IServiceCollection services) + { var postgreSqlOptions = new PostgreSqlOptions(); _configure(postgreSqlOptions); if (postgreSqlOptions.DbContextType != null) + { services.AddSingleton(x => { using (var scope = x.CreateScope()) { var provider = scope.ServiceProvider; - var dbContext = (DbContext)provider.GetService(postgreSqlOptions.DbContextType); + var dbContext = (DbContext) provider.GetService(postgreSqlOptions.DbContextType); postgreSqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; return postgreSqlOptions; } }); + } else + { services.AddSingleton(postgreSqlOptions); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs index 7368b94..9f52154 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs @@ -1,4 +1,5 @@ -// ReSharper disable once CheckNamespace +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. namespace DotNetCore.CAP { diff --git a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs index 2c62532..e299dc4 100644 --- a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs +++ b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Data; using System.Threading.Tasks; using Dapper; @@ -14,16 +17,14 @@ namespace DotNetCore.CAP.PostgreSql public class CapPublisher : CapPublisherBase, ICallbackPublisher { private readonly DbContext _dbContext; - private readonly ILogger _logger; private readonly PostgreSqlOptions _options; - public CapPublisher(IServiceProvider provider, - ILogger logger, - PostgreSqlOptions options) + public CapPublisher(ILogger logger, IDispatcher dispatcher, + IServiceProvider provider, PostgreSqlOptions options) + : base(logger, dispatcher) { ServiceProvider = provider; _options = options; - _logger = logger; if (_options.DbContextType != null) { @@ -31,12 +32,14 @@ namespace DotNetCore.CAP.PostgreSql _dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType); } } - - public async Task PublishAsync(CapPublishedMessage message) + + public async Task PublishCallbackAsync(CapPublishedMessage message) { using (var conn = new NpgsqlConnection(_options.ConnectionString)) { - await conn.ExecuteAsync(PrepareSql(), message); + var id = await conn.ExecuteScalarAsync(PrepareSql(), message); + message.Id = id; + Enqueue(message); } } @@ -53,25 +56,20 @@ namespace DotNetCore.CAP.PostgreSql dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); dbTrans = dbContextTransaction.GetDbTransaction(); } + DbTransaction = dbTrans; } - protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, + protected override int Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) { - dbConnection.Execute(PrepareSql(), message, dbTransaction); - - _logger.LogInformation("Published Message has been persisted in the database. name:" + message); + return dbConnection.ExecuteScalar(PrepareSql(), message, dbTransaction); } - protected override Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, + protected override Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) { - dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); - - _logger.LogInformation("Published Message has been persisted in the database. name:" + message); - - return Task.CompletedTask; + return dbConnection.ExecuteScalarAsync(PrepareSql(), message, dbTransaction); } #region private methods @@ -79,7 +77,7 @@ namespace DotNetCore.CAP.PostgreSql private string PrepareSql() { return - $"INSERT INTO \"{_options.Schema}\".\"published\" (\"Name\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; + $"INSERT INTO \"{_options.Schema}\".\"published\" (\"Name\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName) RETURNING \"Id\";"; } #endregion private methods diff --git a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj index b366894..81b1b5c 100644 --- a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj +++ b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj @@ -8,16 +8,16 @@ $(PackageTags);PostgreSQL - - bin\Debug\netstandard2.0\DotNetCore.CAP.PostgreSql.xml + + bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.PostgreSql.xml 1701;1702;1705;CS1591 - - - + + + diff --git a/src/DotNetCore.CAP.PostgreSql/FetchedMessage.cs b/src/DotNetCore.CAP.PostgreSql/FetchedMessage.cs deleted file mode 100644 index 4abd0f3..0000000 --- a/src/DotNetCore.CAP.PostgreSql/FetchedMessage.cs +++ /dev/null @@ -1,11 +0,0 @@ -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP.PostgreSql -{ - internal class FetchedMessage - { - public int MessageId { get; set; } - - public MessageType MessageType { get; set; } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.PostgreSql/ICollectlProcessor.PostgreSql.cs similarity index 79% rename from src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs rename to src/DotNetCore.CAP.PostgreSql/ICollectlProcessor.PostgreSql.cs index 256e14a..d82f0b8 100644 --- a/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.PostgreSql/ICollectlProcessor.PostgreSql.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Threading.Tasks; using Dapper; using DotNetCore.CAP.Processor; @@ -7,7 +10,7 @@ using Npgsql; namespace DotNetCore.CAP.PostgreSql { - internal class DefaultAdditionalProcessor : IAdditionalProcessor + internal class PostgreSqlCollectProcessor : ICollectProcessor { private const int MaxBatch = 1000; @@ -21,7 +24,7 @@ namespace DotNetCore.CAP.PostgreSql private readonly PostgreSqlOptions _options; private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); - public DefaultAdditionalProcessor(ILogger logger, + public PostgreSqlCollectProcessor(ILogger logger, PostgreSqlOptions sqlServerOptions) { _logger = logger; @@ -30,10 +33,10 @@ namespace DotNetCore.CAP.PostgreSql public async Task ProcessAsync(ProcessingContext context) { - _logger.LogDebug("Collecting expired entities."); - foreach (var table in Tables) { + _logger.LogDebug($"Collecting expired data from table [{_options.Schema}].[{table}]."); + var removedCount = 0; do { diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlFetchedMessage.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlFetchedMessage.cs deleted file mode 100644 index 943db78..0000000 --- a/src/DotNetCore.CAP.PostgreSql/PostgreSqlFetchedMessage.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Data; -using System.Threading; -using Dapper; -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP.PostgreSql -{ - public class PostgreSqlFetchedMessage : IFetchedMessage - { - private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1); - private readonly IDbConnection _connection; - private readonly object _lockObject = new object(); - private readonly Timer _timer; - private readonly IDbTransaction _transaction; - - public PostgreSqlFetchedMessage(int messageId, - MessageType type, - IDbConnection connection, - IDbTransaction transaction) - { - MessageId = messageId; - MessageType = type; - _connection = connection; - _transaction = transaction; - _timer = new Timer(ExecuteKeepAliveQuery, null, KeepAliveInterval, KeepAliveInterval); - } - - public int MessageId { get; } - - public MessageType MessageType { get; } - - public void RemoveFromQueue() - { - lock (_lockObject) - { - _transaction.Commit(); - } - } - - public void Requeue() - { - lock (_lockObject) - { - _transaction.Rollback(); - } - } - - public void Dispose() - { - lock (_lockObject) - { - _timer?.Dispose(); - _transaction.Dispose(); - _connection.Dispose(); - } - } - - private void ExecuteKeepAliveQuery(object obj) - { - lock (_lockObject) - { - try - { - _connection?.Execute("SELECT 1", _transaction); - } - catch - { - // ignored - } - } - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs index df6020a..8fcf9f8 100644 --- a/src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs +++ b/src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -27,9 +30,7 @@ namespace DotNetCore.CAP.PostgreSql select count(""Id"") from ""{0}"".""published"" where ""StatusName"" = N'Succeeded'; select count(""Id"") from ""{0}"".""received"" where ""StatusName"" = N'Succeeded'; select count(""Id"") from ""{0}"".""published"" where ""StatusName"" = N'Failed'; -select count(""Id"") from ""{0}"".""received"" where ""StatusName"" = N'Failed'; -select count(""Id"") from ""{0}"".""published"" where ""StatusName"" in (N'Processing',N'Scheduled',N'Enqueued'); -select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Processing',N'Scheduled',N'Enqueued');", +select count(""Id"") from ""{0}"".""received"" where ""StatusName"" = N'Failed';", _options.Schema); var statistics = UseConnection(connection => @@ -42,10 +43,8 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce stats.PublishedFailed = multi.ReadSingle(); stats.ReceivedFailed = multi.ReadSingle(); - - stats.PublishedProcessing = multi.ReadSingle(); - stats.ReceivedProcessing = multi.ReadSingle(); } + return stats; }); return statistics; @@ -57,17 +56,24 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce var where = string.Empty; if (!string.IsNullOrEmpty(queryDto.StatusName)) - if (string.Equals(queryDto.StatusName, StatusName.Processing, - StringComparison.CurrentCultureIgnoreCase)) - where += " and \"StatusName\" in (N'Processing',N'Scheduled',N'Enqueued')"; - else - where += " and Lower(\"StatusName\") = Lower(@StatusName)"; + { + where += " and Lower(\"StatusName\") = Lower(@StatusName)"; + } + if (!string.IsNullOrEmpty(queryDto.Name)) + { where += " and Lower(\"Name\") = Lower(@Name)"; + } + if (!string.IsNullOrEmpty(queryDto.Group)) + { where += " and Lower(\"Group\") = Lower(@Group)"; + } + if (!string.IsNullOrEmpty(queryDto.Content)) + { where += " and \"Content\" ILike '%@Content%'"; + } var sqlQuery = $"select * from \"{_options.Schema}\".\"{tableName}\" where 1=1 {where} order by \"Added\" desc offset @Offset limit @Limit"; @@ -88,11 +94,6 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Failed)); } - public int PublishedProcessingCount() - { - return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Processing)); - } - public int PublishedSucceededCount() { return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Succeeded)); @@ -103,11 +104,6 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Failed)); } - public int ReceivedProcessingCount() - { - return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Processing)); - } - public int ReceivedSucceededCount() { return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Succeeded)); @@ -129,11 +125,10 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName) { - var sqlQuery = statusName == StatusName.Processing - ? $"select count(\"Id\") from \"{_options.Schema}\".\"{tableName}\" where \"StatusName\" in (N'Processing',N'Scheduled',N'Enqueued')" - : $"select count(\"Id\") from \"{_options.Schema}\".\"{tableName}\" where Lower(\"StatusName\") = Lower(@state)"; + var sqlQuery = + $"select count(\"Id\") from \"{_options.Schema}\".\"{tableName}\" where Lower(\"StatusName\") = Lower(@state)"; - var count = connection.ExecuteScalar(sqlQuery, new { state = statusName }); + var count = connection.ExecuteScalar(sqlQuery, new {state = statusName}); return count; } @@ -175,12 +170,17 @@ with aggr as ( ) select ""Key"",""Count"" from aggr where ""Key""= Any(@keys);"; - var valuesMap = connection.Query(sqlQuery,new { keys = keyMaps.Keys.ToList(), statusName }) - .ToList() - .ToDictionary(x => (string)x.Key, x => (int)x.Count); + var valuesMap = connection.Query(sqlQuery, new {keys = keyMaps.Keys.ToList(), statusName}) + .ToList() + .ToDictionary(x => (string) x.Key, x => (int) x.Count); foreach (var key in keyMaps.Keys) - if (!valuesMap.ContainsKey(key)) valuesMap.Add(key, 0); + { + if (!valuesMap.ContainsKey(key)) + { + valuesMap.Add(key, 0); + } + } var result = new Dictionary(); for (var i = 0; i < keyMaps.Count; i++) diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs index e1fd4f0..34c8038 100644 --- a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs +++ b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Data; using System.Threading; @@ -11,9 +14,9 @@ namespace DotNetCore.CAP.PostgreSql { public class PostgreSqlStorage : IStorage { + private readonly CapOptions _capOptions; private readonly IDbConnection _existingConnection = null; private readonly ILogger _logger; - private readonly CapOptions _capOptions; private readonly PostgreSqlOptions _options; public PostgreSqlStorage(ILogger logger, @@ -37,7 +40,10 @@ namespace DotNetCore.CAP.PostgreSql public async Task InitializeAsync(CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) return; + if (cancellationToken.IsCancellationRequested) + { + return; + } var sql = CreateDbTablesScript(_options.Schema); @@ -45,6 +51,7 @@ namespace DotNetCore.CAP.PostgreSql { await connection.ExecuteAsync(sql); } + _logger.LogDebug("Ensuring all create database tables script are applied."); } @@ -68,7 +75,9 @@ namespace DotNetCore.CAP.PostgreSql var connection = _existingConnection ?? new NpgsqlConnection(_options.ConnectionString); if (connection.State == ConnectionState.Closed) + { connection.Open(); + } return connection; } @@ -81,7 +90,9 @@ namespace DotNetCore.CAP.PostgreSql internal void ReleaseConnection(IDbConnection connection) { if (connection != null && !IsExistingConnection(connection)) + { connection.Dispose(); + } } protected virtual string CreateDbTablesScript(string schema) @@ -89,10 +100,7 @@ namespace DotNetCore.CAP.PostgreSql var batchSql = $@" CREATE SCHEMA IF NOT EXISTS ""{schema}""; -CREATE TABLE IF NOT EXISTS ""{schema}"".""queue""( - ""MessageId"" int NOT NULL , - ""MessageType"" int NOT NULL -); +DROP TABLE IF EXISTS ""{schema}"".""queue""; CREATE TABLE IF NOT EXISTS ""{schema}"".""received""( ""Id"" SERIAL PRIMARY KEY NOT NULL, diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs index 4874f8a..763407c 100644 --- a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs +++ b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs @@ -1,6 +1,8 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections.Generic; -using System.Data; using System.Threading.Tasks; using Dapper; using DotNetCore.CAP.Infrastructure; @@ -36,44 +38,31 @@ namespace DotNetCore.CAP.PostgreSql } } - public Task FetchNextMessageAsync() - { - var sql = $@"DELETE FROM ""{Options.Schema}"".""queue"" WHERE ""MessageId"" = (SELECT ""MessageId"" FROM ""{Options.Schema}"".""queue"" FOR UPDATE SKIP LOCKED LIMIT 1) RETURNING *;"; - return FetchNextMessageCoreAsync(sql); - } - - public async Task GetNextPublishedMessageToBeEnqueuedAsync() + public async Task> GetPublishedMessagesOfNeedRetry() { + var fourMinsAgo = DateTime.Now.AddMinutes(-4); var sql = - $"SELECT * FROM \"{Options.Schema}\".\"published\" WHERE \"StatusName\" = '{StatusName.Scheduled}' FOR UPDATE SKIP LOCKED LIMIT 1;"; + $"SELECT * FROM \"{Options.Schema}\".\"published\" WHERE \"Retries\"<{_capOptions.FailedRetryCount} AND \"Added\"<'{fourMinsAgo}' AND (\"StatusName\"='{StatusName.Failed}' OR \"StatusName\"='{StatusName.Scheduled}') LIMIT 200;"; using (var connection = new NpgsqlConnection(Options.ConnectionString)) { - return await connection.QueryFirstOrDefaultAsync(sql); + return await connection.QueryAsync(sql); } } - public async Task> GetFailedPublishedMessages() + public async Task StoreReceivedMessageAsync(CapReceivedMessage message) { - var sql = - $"SELECT * FROM \"{Options.Schema}\".\"published\" WHERE \"Retries\"<{_capOptions.FailedRetryCount} AND \"StatusName\"='{StatusName.Failed}' LIMIT 200;"; - - using (var connection = new NpgsqlConnection(Options.ConnectionString)) + if (message == null) { - return await connection.QueryAsync(sql); + throw new ArgumentNullException(nameof(message)); } - } - - public async Task StoreReceivedMessageAsync(CapReceivedMessage message) - { - if (message == null) throw new ArgumentNullException(nameof(message)); var sql = - $"INSERT INTO \"{Options.Schema}\".\"received\"(\"Name\",\"Group\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; + $"INSERT INTO \"{Options.Schema}\".\"received\"(\"Name\",\"Group\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName) RETURNING \"Id\";"; using (var connection = new NpgsqlConnection(Options.ConnectionString)) { - await connection.ExecuteAsync(sql, message); + return await connection.ExecuteScalarAsync(sql, message); } } @@ -86,20 +75,11 @@ namespace DotNetCore.CAP.PostgreSql } } - public async Task GetNextReceivedMessageToBeEnqueuedAsync() - { - var sql = - $"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"StatusName\" = '{StatusName.Scheduled}' FOR UPDATE SKIP LOCKED LIMIT 1;"; - using (var connection = new NpgsqlConnection(Options.ConnectionString)) - { - return await connection.QueryFirstOrDefaultAsync(sql); - } - } - - public async Task> GetFailedReceivedMessages() + public async Task> GetReceivedMessagesOfNeedRetry() { + var fourMinsAgo = DateTime.Now.AddMinutes(-4); var sql = - $"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"Retries\"<{_capOptions.FailedRetryCount} AND \"StatusName\"='{StatusName.Failed}' LIMIT 200;"; + $"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"Retries\"<{_capOptions.FailedRetryCount} AND \"Added\"<'{fourMinsAgo}' AND (\"StatusName\"='{StatusName.Failed}' OR \"StatusName\"='{StatusName.Scheduled}') LIMIT 200;"; using (var connection = new NpgsqlConnection(Options.ConnectionString)) { return await connection.QueryAsync(sql); @@ -131,35 +111,5 @@ namespace DotNetCore.CAP.PostgreSql return connection.Execute(sql) > 0; } } - - private async Task FetchNextMessageCoreAsync(string sql, object args = null) - { - //here don't use `using` to dispose - var connection = new NpgsqlConnection(Options.ConnectionString); - await connection.OpenAsync(); - var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); - FetchedMessage fetchedMessage; - try - { - fetchedMessage = await connection.QueryFirstOrDefaultAsync(sql, args, transaction); - } - catch (NpgsqlException) - { - transaction.Dispose(); - connection.Dispose(); - throw; - } - - if (fetchedMessage == null) - { - transaction.Rollback(); - transaction.Dispose(); - connection.Dispose(); - return null; - } - - return new PostgreSqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, - transaction); - } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs index 3cb4931..d64c46a 100644 --- a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs +++ b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Data; using System.Threading.Tasks; using Dapper; @@ -26,7 +29,10 @@ namespace DotNetCore.CAP.PostgreSql public void UpdateMessage(CapPublishedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $@"UPDATE ""{ @@ -37,7 +43,10 @@ namespace DotNetCore.CAP.PostgreSql public void UpdateMessage(CapReceivedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $@"UPDATE ""{ @@ -46,9 +55,24 @@ namespace DotNetCore.CAP.PostgreSql _dbConnection.Execute(sql, message, _dbTransaction); } + public Task CommitAsync() + { + _dbTransaction.Commit(); + return Task.CompletedTask; + } + + public void Dispose() + { + _dbTransaction.Dispose(); + _dbConnection.Dispose(); + } + public void EnqueueMessage(CapPublishedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);"; _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish}, @@ -57,23 +81,14 @@ namespace DotNetCore.CAP.PostgreSql public void EnqueueMessage(CapReceivedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);"; _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe}, _dbTransaction); } - - public Task CommitAsync() - { - _dbTransaction.Commit(); - return Task.CompletedTask; - } - - public void Dispose() - { - _dbTransaction.Dispose(); - _dbConnection.Dispose(); - } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs index bdf0487..b8bd74f 100644 --- a/src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP; // ReSharper disable once CheckNamespace @@ -13,7 +16,10 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseRabbitMQ(this CapOptions options, Action configure) { - if (configure == null) throw new ArgumentNullException(nameof(configure)); + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } options.RegisterExtension(new RabbitMQCapOptionsExtension(configure)); diff --git a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs index d76ab91..c0abcc8 100644 --- a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs +++ b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs @@ -1,5 +1,7 @@ -// ReSharper disable once CheckNamespace +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ReSharper disable once CheckNamespace namespace DotNetCore.CAP { public class RabbitMQOptions @@ -56,7 +58,7 @@ namespace DotNetCore.CAP /// /// Topic exchange name when declare a topic exchange. /// - public string TopicExchangeName { get; set; } = DefaultExchangeName; + public string ExchangeName { get; set; } = DefaultExchangeName; /// /// Timeout setting for connection attempts (in milliseconds). diff --git a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs index c2eb13e..a952313 100644 --- a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.RabbitMQ; using Microsoft.Extensions.DependencyInjection; @@ -24,8 +27,8 @@ namespace DotNetCore.CAP services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/CAP.SubscribeAttribute.cs b/src/DotNetCore.CAP.RabbitMQ/CAP.SubscribeAttribute.cs index 811a281..a09a8d6 100644 --- a/src/DotNetCore.CAP.RabbitMQ/CAP.SubscribeAttribute.cs +++ b/src/DotNetCore.CAP.RabbitMQ/CAP.SubscribeAttribute.cs @@ -1,4 +1,7 @@ -using DotNetCore.CAP.Abstractions; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using DotNetCore.CAP.Abstractions; // ReSharper disable once CheckNamespace namespace DotNetCore.CAP diff --git a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj index 419514f..6a68173 100644 --- a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj +++ b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj @@ -8,8 +8,8 @@ $(PackageTags);RabbitMQ - - bin\Debug\netstandard2.0\DotNetCore.CAP.RabbitMQ.xml + + bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.RabbitMQ.xml 1701;1702;1705;CS1591 diff --git a/src/DotNetCore.CAP.RabbitMQ/ConnectionChannelPool.cs b/src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.Default.cs similarity index 79% rename from src/DotNetCore.CAP.RabbitMQ/ConnectionChannelPool.cs rename to src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.Default.cs index 3ddedb1..e15fdfa 100644 --- a/src/DotNetCore.CAP.RabbitMQ/ConnectionChannelPool.cs +++ b/src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.Default.cs @@ -1,8 +1,12 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Threading; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using RabbitMQ.Client; namespace DotNetCore.CAP.RabbitMQ @@ -12,19 +16,24 @@ namespace DotNetCore.CAP.RabbitMQ private const int DefaultPoolSize = 15; private readonly Func _connectionActivator; private readonly ILogger _logger; - private readonly ConcurrentQueue _pool = new ConcurrentQueue(); + private readonly ConcurrentQueue _pool; private IConnection _connection; private int _count; private int _maxSize; - public ConnectionChannelPool(ILogger logger, - RabbitMQOptions options) + public ConnectionChannelPool(ILogger logger, RabbitMQOptions options) { _logger = logger; _maxSize = DefaultPoolSize; - + _pool = new ConcurrentQueue(); _connectionActivator = CreateConnection(options); + + HostAddress = options.HostName + ":" + options.Port; + Exchange = options.ExchangeName; + + _logger.LogDebug("RabbitMQ configuration of CAP :\r\n {0}", + JsonConvert.SerializeObject(options, Formatting.Indented)); } IModel IConnectionChannelPool.Rent() @@ -37,10 +46,17 @@ namespace DotNetCore.CAP.RabbitMQ return Return(connection); } + public string HostAddress { get; } + + public string Exchange { get; } + public IConnection GetConnection() { if (_connection != null && _connection.IsOpen) + { return _connection; + } + _connection = _connectionActivator(); _connection.ConnectionShutdown += RabbitMQ_ConnectionShutdown; return _connection; @@ -51,7 +67,9 @@ namespace DotNetCore.CAP.RabbitMQ _maxSize = 0; while (_pool.TryDequeue(out var context)) + { context.Dispose(); + } } private static Func CreateConnection(RabbitMQOptions options) @@ -73,7 +91,7 @@ namespace DotNetCore.CAP.RabbitMQ private void RabbitMQ_ConnectionShutdown(object sender, ShutdownEventArgs e) { - _logger.LogWarning($"RabbitMQ client connection closed! {e}"); + _logger.LogWarning($"RabbitMQ client connection closed! --> {e.ReplyText}"); } public virtual IModel Rent() diff --git a/src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.cs b/src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.cs index c39985b..5361529 100644 --- a/src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.cs +++ b/src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.cs @@ -1,9 +1,16 @@ -using RabbitMQ.Client; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using RabbitMQ.Client; namespace DotNetCore.CAP.RabbitMQ { public interface IConnectionChannelPool { + string HostAddress { get; } + + string Exchange { get; } + IConnection GetConnection(); IModel Rent(); diff --git a/src/DotNetCore.CAP.RabbitMQ/IPublishMessageSender.RabbitMQ.cs b/src/DotNetCore.CAP.RabbitMQ/IPublishMessageSender.RabbitMQ.cs new file mode 100644 index 0000000..3dacba3 --- /dev/null +++ b/src/DotNetCore.CAP.RabbitMQ/IPublishMessageSender.RabbitMQ.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Text; +using System.Threading.Tasks; +using DotNetCore.CAP.Internal; +using DotNetCore.CAP.Processor.States; +using Microsoft.Extensions.Logging; +using RabbitMQ.Client; + +namespace DotNetCore.CAP.RabbitMQ +{ + internal sealed class RabbitMQPublishMessageSender : BasePublishMessageSender + { + private readonly IConnectionChannelPool _connectionChannelPool; + private readonly ILogger _logger; + private readonly string _exchange; + + public RabbitMQPublishMessageSender(ILogger logger, CapOptions options, + IStorageConnection connection, IConnectionChannelPool connectionChannelPool, IStateChanger stateChanger) + : base(logger, options, connection, stateChanger) + { + _logger = logger; + _connectionChannelPool = connectionChannelPool; + _exchange = _connectionChannelPool.Exchange; + ServersAddress = _connectionChannelPool.HostAddress; + } + + public override Task PublishAsync(string keyName, string content) + { + var channel = _connectionChannelPool.Rent(); + try + { + var body = Encoding.UTF8.GetBytes(content); + channel.ExchangeDeclare(_exchange, RabbitMQOptions.ExchangeType, true); + channel.BasicPublish(_exchange, keyName, null, body); + + _logger.LogDebug($"RabbitMQ topic message [{keyName}] has been published."); + + return Task.FromResult(OperateResult.Success); + } + catch (Exception ex) + { + var wapperEx = new PublisherSentFailedException(ex.Message, ex); + var errors = new OperateError + { + Code = ex.HResult.ToString(), + Description = ex.Message + }; + + return Task.FromResult(OperateResult.Failed(wapperEx, errors)); + } + finally + { + var returned = _connectionChannelPool.Return(channel); + if (!returned) + { + channel.Dispose(); + } + } + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs deleted file mode 100644 index 3f0045e..0000000 --- a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Text; -using System.Threading.Tasks; -using DotNetCore.CAP.Processor.States; -using Microsoft.Extensions.Logging; -using RabbitMQ.Client; - -namespace DotNetCore.CAP.RabbitMQ -{ - internal sealed class PublishQueueExecutor : BasePublishQueueExecutor - { - private readonly IConnectionChannelPool _connectionChannelPool; - private readonly ILogger _logger; - private readonly RabbitMQOptions _rabbitMQOptions; - - public PublishQueueExecutor(ILogger logger, CapOptions options, - RabbitMQOptions rabbitMQOptions, IConnectionChannelPool connectionChannelPool, IStateChanger stateChanger) - : base(options, stateChanger, logger) - { - _logger = logger; - _connectionChannelPool = connectionChannelPool; - _rabbitMQOptions = rabbitMQOptions; - } - - public override Task PublishAsync(string keyName, string content) - { - var channel = _connectionChannelPool.Rent(); - try - { - var body = Encoding.UTF8.GetBytes(content); - - channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType, true); - channel.BasicPublish(_rabbitMQOptions.TopicExchangeName, - keyName, - null, - body); - - _logger.LogDebug($"RabbitMQ topic message [{keyName}] has been published."); - - return Task.FromResult(OperateResult.Success); - } - catch (Exception ex) - { - _logger.LogError( - $"RabbitMQ topic message [{keyName}] has been raised an exception of sending. the exception is: {ex.Message}"); - - return Task.FromResult(OperateResult.Failed(ex, - new OperateError - { - Code = ex.HResult.ToString(), - Description = ex.Message - })); - } - finally - { - var returned = _connectionChannelPool.Return(channel); - if (!returned) - channel.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs index da483f0..972b6dd 100644 --- a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs +++ b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Text; using System.Threading; @@ -13,9 +16,9 @@ namespace DotNetCore.CAP.RabbitMQ private readonly string _exchageName; private readonly string _queueName; private readonly RabbitMQOptions _rabbitMQOptions; + private IModel _channel; private IConnection _connection; - private IModel _channel; private ulong _deliveryTag; public RabbitMQConsumerClient(string queueName, @@ -25,7 +28,7 @@ namespace DotNetCore.CAP.RabbitMQ _queueName = queueName; _connectionChannelPool = connectionChannelPool; _rabbitMQOptions = options; - _exchageName = options.TopicExchangeName; + _exchageName = options.ExchangeName; InitClient(); } @@ -34,12 +37,19 @@ namespace DotNetCore.CAP.RabbitMQ public event EventHandler OnLog; + public string ServersAddress => _rabbitMQOptions.HostName; + public void Subscribe(IEnumerable topics) { - if (topics == null) throw new ArgumentNullException(nameof(topics)); + if (topics == null) + { + throw new ArgumentNullException(nameof(topics)); + } foreach (var topic in topics) + { _channel.QueueBind(_queueName, _exchageName, topic); + } } public void Listening(TimeSpan timeout, CancellationToken cancellationToken) @@ -58,6 +68,7 @@ namespace DotNetCore.CAP.RabbitMQ cancellationToken.ThrowIfCancellationRequested(); cancellationToken.WaitHandle.WaitOne(timeout); } + // ReSharper disable once FunctionNeverReturns } @@ -82,14 +93,15 @@ namespace DotNetCore.CAP.RabbitMQ _connection = _connectionChannelPool.GetConnection(); _channel = _connection.CreateModel(); - + _channel.ExchangeDeclare( _exchageName, RabbitMQOptions.ExchangeType, true); - var arguments = new Dictionary { - { "x-message-ttl", _rabbitMQOptions.QueueMessageExpires } + var arguments = new Dictionary + { + {"x-message-ttl", _rabbitMQOptions.QueueMessageExpires} }; _channel.QueueDeclare(_queueName, true, false, false, arguments); } @@ -100,7 +112,7 @@ namespace DotNetCore.CAP.RabbitMQ { var args = new LogMessageEventArgs { - LogType = MqLogType.ConsumerCancelled, + LogType = MqLogType.ConsumerCancelled, Reason = e.ConsumerTag }; OnLog?.Invoke(sender, args); @@ -143,7 +155,7 @@ namespace DotNetCore.CAP.RabbitMQ var args = new LogMessageEventArgs { LogType = MqLogType.ConsumerShutdown, - Reason = e.ToString() + Reason = e.ReplyText }; OnLog?.Invoke(sender, args); } diff --git a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs index 0a03c58..52c3dd1 100644 --- a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs +++ b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.RabbitMQ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.RabbitMQ { internal sealed class RabbitMQConsumerClientFactory : IConsumerClientFactory { diff --git a/src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs b/src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs index 0c62833..243b1c8 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; // ReSharper disable once CheckNamespace namespace DotNetCore.CAP diff --git a/src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs index c977921..6aca9cc 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP; using Microsoft.EntityFrameworkCore; @@ -14,7 +17,10 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseSqlServer(this CapOptions options, Action configure) { - if (configure == null) throw new ArgumentNullException(nameof(configure)); + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } options.RegisterExtension(new SqlServerCapOptionsExtension(configure)); @@ -24,18 +30,22 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseEntityFramework(this CapOptions options) where TContext : DbContext { - return options.UseEntityFramework(opt => { opt.DbContextType = typeof(TContext); }); + return options.UseEntityFramework(opt => { }); } public static CapOptions UseEntityFramework(this CapOptions options, Action configure) where TContext : DbContext { - if (configure == null) throw new ArgumentNullException(nameof(configure)); - - var efOptions = new EFOptions {DbContextType = typeof(TContext)}; - configure(efOptions); - - options.RegisterExtension(new SqlServerCapOptionsExtension(configure)); + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + options.RegisterExtension(new SqlServerCapOptionsExtension(x => + { + configure(x); + x.DbContextType = typeof(TContext); + })); return options; } diff --git a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs index ba17ff0..91e04f7 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Processor; using DotNetCore.CAP.SqlServer; using Microsoft.EntityFrameworkCore; @@ -23,7 +26,8 @@ namespace DotNetCore.CAP services.AddSingleton(); services.AddScoped(); services.AddScoped(); - services.AddTransient(); + services.AddTransient(); + AddSqlServerOptions(services); } @@ -34,18 +38,22 @@ namespace DotNetCore.CAP _configure(sqlServerOptions); if (sqlServerOptions.DbContextType != null) + { services.AddSingleton(x => { using (var scope = x.CreateScope()) { var provider = scope.ServiceProvider; - var dbContext = (DbContext) provider.GetService(sqlServerOptions.DbContextType); + var dbContext = (DbContext)provider.GetService(sqlServerOptions.DbContextType); sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; return sqlServerOptions; } }); + } else + { services.AddSingleton(sqlServerOptions); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs index f2d44a4..b5c1e5f 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs @@ -1,4 +1,5 @@ -// ReSharper disable once CheckNamespace +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. namespace DotNetCore.CAP { diff --git a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs index 07ad92d..b93ca68 100644 --- a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs +++ b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Data; using System.Data.SqlClient; using System.Threading.Tasks; @@ -14,28 +17,31 @@ namespace DotNetCore.CAP.SqlServer public class CapPublisher : CapPublisherBase, ICallbackPublisher { private readonly DbContext _dbContext; - private readonly ILogger _logger; private readonly SqlServerOptions _options; - public CapPublisher(IServiceProvider provider, - ILogger logger, - SqlServerOptions options) + public CapPublisher(ILogger logger, IDispatcher dispatcher, + IServiceProvider provider, SqlServerOptions options) + : base(logger, dispatcher) { ServiceProvider = provider; - _logger = logger; _options = options; - if (_options.DbContextType == null) return; + if (_options.DbContextType == null) + { + return; + } IsUsingEF = true; - _dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType); + _dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType); } - public async Task PublishAsync(CapPublishedMessage message) + public async Task PublishCallbackAsync(CapPublishedMessage message) { using (var conn = new SqlConnection(_options.ConnectionString)) { - await conn.ExecuteAsync(PrepareSql(), message); + var id = await conn.ExecuteScalarAsync(PrepareSql(), message); + message.Id = id; + Enqueue(message); } } @@ -52,25 +58,20 @@ namespace DotNetCore.CAP.SqlServer dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); dbTrans = dbContextTransaction.GetDbTransaction(); } + DbTransaction = dbTrans; } - protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, + protected override int Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) { - dbConnection.Execute(PrepareSql(), message, dbTransaction); - - _logger.LogInformation("published message has been persisted to the database. name:" + message); + return dbConnection.ExecuteScalar(PrepareSql(), message, dbTransaction); } - protected override Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, + protected override Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) { - dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); - - _logger.LogInformation("published message has been persisted to the database. name:" + message); - - return Task.CompletedTask; + return dbConnection.ExecuteScalarAsync(PrepareSql(), message, dbTransaction); } #region private methods @@ -78,7 +79,7 @@ namespace DotNetCore.CAP.SqlServer private string PrepareSql() { return - $"INSERT INTO {_options.Schema}.[Published] ([Name],[Content],[Retries],[Added],[ExpiresAt],[StatusName])VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; + $"INSERT INTO {_options.Schema}.[Published] ([Name],[Content],[Retries],[Added],[ExpiresAt],[StatusName])VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName);SELECT SCOPE_IDENTITY();"; } #endregion private methods diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index 18ef872..7d03a24 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -8,16 +8,16 @@ $(PackageTags);SQL Server - - bin\Debug\netstandard2.0\DotNetCore.CAP.SqlServer.xml + + bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.SqlServer.xml 1701;1702;1705;CS1591 - - - + + + diff --git a/src/DotNetCore.CAP.SqlServer/FetchedMessage.cs b/src/DotNetCore.CAP.SqlServer/FetchedMessage.cs deleted file mode 100644 index 990b61e..0000000 --- a/src/DotNetCore.CAP.SqlServer/FetchedMessage.cs +++ /dev/null @@ -1,11 +0,0 @@ -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP.SqlServer -{ - internal class FetchedMessage - { - public int MessageId { get; set; } - - public MessageType MessageType { get; set; } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.SqlServer/ICollectProcessor.SqlServer.cs similarity index 78% rename from src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs rename to src/DotNetCore.CAP.SqlServer/ICollectProcessor.SqlServer.cs index e87f87a..c7a4d03 100644 --- a/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.SqlServer/ICollectProcessor.SqlServer.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Data.SqlClient; using System.Threading.Tasks; using Dapper; @@ -7,7 +10,7 @@ using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.SqlServer { - public class DefaultAdditionalProcessor : IAdditionalProcessor + public class SqlServerCollectProcessor : ICollectProcessor { private const int MaxBatch = 1000; @@ -21,7 +24,7 @@ namespace DotNetCore.CAP.SqlServer private readonly SqlServerOptions _options; private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); - public DefaultAdditionalProcessor(ILogger logger, + public SqlServerCollectProcessor(ILogger logger, SqlServerOptions sqlServerOptions) { _logger = logger; @@ -30,10 +33,10 @@ namespace DotNetCore.CAP.SqlServer public async Task ProcessAsync(ProcessingContext context) { - _logger.LogDebug("Collecting expired entities."); - foreach (var table in Tables) { + _logger.LogDebug($"Collecting expired data from table [{_options.Schema}].[{table}]."); + int removedCount; do { diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerFetchedMessage.cs b/src/DotNetCore.CAP.SqlServer/SqlServerFetchedMessage.cs deleted file mode 100644 index e423258..0000000 --- a/src/DotNetCore.CAP.SqlServer/SqlServerFetchedMessage.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Data; -using System.Threading; -using Dapper; -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP.SqlServer -{ - public class SqlServerFetchedMessage : IFetchedMessage - { - private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1); - private readonly IDbConnection _connection; - private readonly object _lockObject = new object(); - private readonly Timer _timer; - private readonly IDbTransaction _transaction; - - public SqlServerFetchedMessage(int messageId, - MessageType type, - IDbConnection connection, - IDbTransaction transaction) - { - MessageId = messageId; - MessageType = type; - _connection = connection; - _transaction = transaction; - _timer = new Timer(ExecuteKeepAliveQuery, null, KeepAliveInterval, KeepAliveInterval); - } - - public int MessageId { get; } - - public MessageType MessageType { get; } - - public void RemoveFromQueue() - { - lock (_lockObject) - { - _transaction.Commit(); - } - } - - public void Requeue() - { - lock (_lockObject) - { - _transaction.Rollback(); - } - } - - public void Dispose() - { - lock (_lockObject) - { - _timer?.Dispose(); - _transaction.Dispose(); - _connection.Dispose(); - } - } - - private void ExecuteKeepAliveQuery(object obj) - { - lock (_lockObject) - { - try - { - _connection?.Execute("SELECT 1", _transaction); - } - catch - { - // ignored - } - } - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs b/src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs index 1d0d1c0..732143e 100644 --- a/src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs +++ b/src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -28,9 +31,7 @@ set transaction isolation level read committed; select count(Id) from [{0}].Published with (nolock) where StatusName = N'Succeeded'; select count(Id) from [{0}].Received with (nolock) where StatusName = N'Succeeded'; select count(Id) from [{0}].Published with (nolock) where StatusName = N'Failed'; -select count(Id) from [{0}].Received with (nolock) where StatusName = N'Failed'; -select count(Id) from [{0}].Published with (nolock) where StatusName in (N'Processing',N'Scheduled',N'Enqueued'); -select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Processing',N'Scheduled',N'Enqueued');", +select count(Id) from [{0}].Received with (nolock) where StatusName = N'Failed';", _options.Schema); var statistics = UseConnection(connection => @@ -43,10 +44,8 @@ select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Proces stats.PublishedFailed = multi.ReadSingle(); stats.ReceivedFailed = multi.ReadSingle(); - - stats.PublishedProcessing = multi.ReadSingle(); - stats.ReceivedProcessing = multi.ReadSingle(); } + return stats; }); return statistics; @@ -71,17 +70,24 @@ select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Proces var tableName = queryDto.MessageType == MessageType.Publish ? "Published" : "Received"; var where = string.Empty; if (!string.IsNullOrEmpty(queryDto.StatusName)) - if (string.Equals(queryDto.StatusName, StatusName.Processing, - StringComparison.CurrentCultureIgnoreCase)) - where += " and statusname in (N'Processing',N'Scheduled',N'Enqueued')"; - else - where += " and statusname=@StatusName"; + { + where += " and statusname=@StatusName"; + } + if (!string.IsNullOrEmpty(queryDto.Name)) + { where += " and name=@Name"; + } + if (!string.IsNullOrEmpty(queryDto.Group)) + { where += " and group=@Group"; + } + if (!string.IsNullOrEmpty(queryDto.Content)) + { where += " and content like '%@Content%'"; + } var sqlQuery = $"select * from [{_options.Schema}].{tableName} where 1=1 {where} order by Added desc offset @Offset rows fetch next @Limit rows only"; @@ -102,11 +108,6 @@ select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Proces return UseConnection(conn => GetNumberOfMessage(conn, "Published", StatusName.Failed)); } - public int PublishedProcessingCount() - { - return UseConnection(conn => GetNumberOfMessage(conn, "Published", StatusName.Processing)); - } - public int PublishedSucceededCount() { return UseConnection(conn => GetNumberOfMessage(conn, "Published", StatusName.Succeeded)); @@ -117,11 +118,6 @@ select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Proces return UseConnection(conn => GetNumberOfMessage(conn, "Received", StatusName.Failed)); } - public int ReceivedProcessingCount() - { - return UseConnection(conn => GetNumberOfMessage(conn, "Received", StatusName.Processing)); - } - public int ReceivedSucceededCount() { return UseConnection(conn => GetNumberOfMessage(conn, "Received", StatusName.Succeeded)); @@ -129,9 +125,8 @@ select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Proces private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName) { - var sqlQuery = statusName == StatusName.Processing - ? $"select count(Id) from [{_options.Schema}].{tableName} with (nolock) where StatusName in (N'Processing',N'Scheduled',N'Enqueued')" - : $"select count(Id) from [{_options.Schema}].{tableName} with (nolock) where StatusName = @state"; + var sqlQuery = + $"select count(Id) from [{_options.Schema}].{tableName} with (nolock) where StatusName = @state"; var count = connection.ExecuteScalar(sqlQuery, new {state = statusName}); return count; @@ -182,7 +177,12 @@ select [Key], [Count] from aggr with (nolock) where [Key] in @keys;"; .ToDictionary(x => (string) x.Key, x => (int) x.Count); foreach (var key in keyMaps.Keys) - if (!valuesMap.ContainsKey(key)) valuesMap.Add(key, 0); + { + if (!valuesMap.ContainsKey(key)) + { + valuesMap.Add(key, 0); + } + } var result = new Dictionary(); for (var i = 0; i < keyMaps.Count; i++) diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs b/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs index 57825b5..4a0f164 100644 --- a/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs +++ b/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Data; using System.Data.SqlClient; @@ -11,9 +14,9 @@ namespace DotNetCore.CAP.SqlServer { public class SqlServerStorage : IStorage { + private readonly CapOptions _capOptions; private readonly IDbConnection _existingConnection = null; private readonly ILogger _logger; - private readonly CapOptions _capOptions; private readonly SqlServerOptions _options; public SqlServerStorage(ILogger logger, @@ -37,7 +40,10 @@ namespace DotNetCore.CAP.SqlServer public async Task InitializeAsync(CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) return; + if (cancellationToken.IsCancellationRequested) + { + return; + } var sql = CreateDbTablesScript(_options.Schema); @@ -45,6 +51,7 @@ namespace DotNetCore.CAP.SqlServer { await connection.ExecuteAsync(sql); } + _logger.LogDebug("Ensuring all create database tables script are applied."); } @@ -54,15 +61,12 @@ namespace DotNetCore.CAP.SqlServer $@" IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{schema}') BEGIN - EXEC('CREATE SCHEMA {schema}') + EXEC('CREATE SCHEMA [{schema}]') END; -IF OBJECT_ID(N'[{schema}].[Queue]',N'U') IS NULL +IF OBJECT_ID(N'[{schema}].[Queue]',N'U') IS NOT NULL BEGIN - CREATE TABLE [{schema}].[Queue]( - [MessageId] [int] NOT NULL, - [MessageType] [tinyint] NOT NULL - ) ON [PRIMARY] + DROP TABLE [{schema}].[Queue]; END; IF OBJECT_ID(N'[{schema}].[Received]',N'U') IS NULL @@ -122,7 +126,9 @@ END;"; var connection = _existingConnection ?? new SqlConnection(_options.ConnectionString); if (connection.State == ConnectionState.Closed) + { connection.Open(); + } return connection; } @@ -135,7 +141,9 @@ END;"; internal void ReleaseConnection(IDbConnection connection) { if (connection != null && !IsExistingConnection(connection)) + { connection.Dispose(); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs b/src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs index 424c43d..9b931c1 100644 --- a/src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs +++ b/src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs @@ -1,6 +1,8 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections.Generic; -using System.Data; using System.Data.SqlClient; using System.Threading.Tasks; using Dapper; @@ -36,49 +38,32 @@ namespace DotNetCore.CAP.SqlServer } } - public Task FetchNextMessageAsync() - { - var sql = $@" -DELETE TOP (1) -FROM [{Options.Schema}].[Queue] WITH (readpast, updlock, rowlock) -OUTPUT DELETED.MessageId,DELETED.[MessageType];"; - - return FetchNextMessageCoreAsync(sql); - } - - public async Task GetNextPublishedMessageToBeEnqueuedAsync() + public async Task> GetPublishedMessagesOfNeedRetry() { + var fourMinsAgo = DateTime.Now.AddMinutes(-4); var sql = - $"SELECT TOP (1) * FROM [{Options.Schema}].[Published] WITH (readpast) WHERE StatusName = '{StatusName.Scheduled}'"; + $"SELECT TOP (200) * FROM [{Options.Schema}].[Published] WITH (readpast) WHERE Retries<{_capOptions.FailedRetryCount} AND Added<'{fourMinsAgo}' AND (StatusName = '{StatusName.Failed}' OR StatusName = '{StatusName.Scheduled}')"; using (var connection = new SqlConnection(Options.ConnectionString)) { - return await connection.QueryFirstOrDefaultAsync(sql); + return await connection.QueryAsync(sql); } } - public async Task> GetFailedPublishedMessages() + public async Task StoreReceivedMessageAsync(CapReceivedMessage message) { - var sql = - $"SELECT TOP (200) * FROM [{Options.Schema}].[Published] WITH (readpast) WHERE Retries<{_capOptions.FailedRetryCount} AND StatusName = '{StatusName.Failed}'"; - - using (var connection = new SqlConnection(Options.ConnectionString)) + if (message == null) { - return await connection.QueryAsync(sql); + throw new ArgumentNullException(nameof(message)); } - } - - public async Task StoreReceivedMessageAsync(CapReceivedMessage message) - { - if (message == null) throw new ArgumentNullException(nameof(message)); var sql = $@" INSERT INTO [{Options.Schema}].[Received]([Name],[Group],[Content],[Retries],[Added],[ExpiresAt],[StatusName]) -VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; +VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);SELECT SCOPE_IDENTITY();"; using (var connection = new SqlConnection(Options.ConnectionString)) { - await connection.ExecuteAsync(sql, message); + return await connection.ExecuteScalarAsync(sql, message); } } @@ -91,20 +76,11 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; } } - public async Task GetNextReceivedMessageToBeEnqueuedAsync() - { - var sql = - $"SELECT TOP (1) * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Scheduled}'"; - using (var connection = new SqlConnection(Options.ConnectionString)) - { - return await connection.QueryFirstOrDefaultAsync(sql); - } - } - - public async Task> GetFailedReceivedMessages() + public async Task> GetReceivedMessagesOfNeedRetry() { + var fourMinsAgo = DateTime.Now.AddMinutes(-4); var sql = - $"SELECT TOP (200) * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE Retries<{_capOptions.FailedRetryCount} AND StatusName = '{StatusName.Failed}'"; + $"SELECT TOP (200) * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE Retries<{_capOptions.FailedRetryCount} AND Added<'{fourMinsAgo}' AND (StatusName = '{StatusName.Failed}' OR StatusName = '{StatusName.Scheduled}')"; using (var connection = new SqlConnection(Options.ConnectionString)) { return await connection.QueryAsync(sql); @@ -136,35 +112,5 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; public void Dispose() { } - - private async Task FetchNextMessageCoreAsync(string sql, object args = null) - { - //here don't use `using` to dispose - var connection = new SqlConnection(Options.ConnectionString); - await connection.OpenAsync(); - var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); - FetchedMessage fetchedMessage; - try - { - fetchedMessage = await connection.QueryFirstOrDefaultAsync(sql, args, transaction); - } - catch (SqlException) - { - transaction.Dispose(); - connection.Dispose(); - throw; - } - - if (fetchedMessage == null) - { - transaction.Rollback(); - transaction.Dispose(); - connection.Dispose(); - return null; - } - - return new SqlServerFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, - transaction); - } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs b/src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs index 3cf8ae6..8889e4a 100644 --- a/src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs +++ b/src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Data; using System.Data.SqlClient; using System.Threading.Tasks; @@ -26,7 +29,10 @@ namespace DotNetCore.CAP.SqlServer public void UpdateMessage(CapPublishedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $"UPDATE [{_schema}].[Published] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;"; @@ -35,16 +41,34 @@ namespace DotNetCore.CAP.SqlServer public void UpdateMessage(CapReceivedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $"UPDATE [{_schema}].[Received] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;"; _dbConnection.Execute(sql, message, _dbTransaction); } + public Task CommitAsync() + { + _dbTransaction.Commit(); + return Task.CompletedTask; + } + + public void Dispose() + { + _dbTransaction.Dispose(); + _dbConnection.Dispose(); + } + public void EnqueueMessage(CapPublishedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $"INSERT INTO [{_schema}].[Queue] values(@MessageId,@MessageType);"; _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish}, @@ -53,23 +77,14 @@ namespace DotNetCore.CAP.SqlServer public void EnqueueMessage(CapReceivedMessage message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var sql = $"INSERT INTO [{_schema}].[Queue] values(@MessageId,@MessageType);"; _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe}, _dbTransaction); } - - public Task CommitAsync() - { - _dbTransaction.Commit(); - return Task.CompletedTask; - } - - public void Dispose() - { - _dbTransaction.Dispose(); - _dbConnection.Dispose(); - } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs index 37e9808..4d7a321 100644 --- a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs +++ b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs @@ -1,14 +1,33 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Data; +using System.Diagnostics; using System.Threading.Tasks; +using DotNetCore.CAP.Diagnostics; using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Models; -using DotNetCore.CAP.Processor; +using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.Abstractions { public abstract class CapPublisherBase : ICapPublisher, IDisposable { + private readonly IDispatcher _dispatcher; + private readonly ILogger _logger; + + // diagnostics listener + // ReSharper disable once InconsistentNaming + private static readonly DiagnosticListener s_diagnosticListener = + new DiagnosticListener(CapDiagnosticListenerExtensions.DiagnosticListenerName); + + protected CapPublisherBase(ILogger logger, IDispatcher dispatcher) + { + _logger = logger; + _dispatcher = dispatcher; + } + protected IDbConnection DbConnection { get; set; } protected IDbTransaction DbTransaction { get; set; } protected bool IsCapOpenedTrans { get; set; } @@ -21,9 +40,7 @@ namespace DotNetCore.CAP.Abstractions CheckIsUsingEF(name); PrepareConnectionForEF(); - var content = Serialize(contentObj, callbackName); - - PublishWithTrans(name, content); + PublishWithTrans(name, contentObj, callbackName); } public Task PublishAsync(string name, T contentObj, string callbackName = null) @@ -31,9 +48,7 @@ namespace DotNetCore.CAP.Abstractions CheckIsUsingEF(name); PrepareConnectionForEF(); - var content = Serialize(contentObj, callbackName); - - return PublishWithTransAsync(name, content); + return PublishWithTransAsync(name, contentObj, callbackName); } public void Publish(string name, T contentObj, IDbTransaction dbTransaction, string callbackName = null) @@ -41,9 +56,7 @@ namespace DotNetCore.CAP.Abstractions CheckIsAdoNet(name); PrepareConnectionForAdo(dbTransaction); - var content = Serialize(contentObj, callbackName); - - PublishWithTrans(name, content); + PublishWithTrans(name, contentObj, callbackName); } public Task PublishAsync(string name, T contentObj, IDbTransaction dbTransaction, string callbackName = null) @@ -51,17 +64,20 @@ namespace DotNetCore.CAP.Abstractions CheckIsAdoNet(name); PrepareConnectionForAdo(dbTransaction); - var content = Serialize(contentObj, callbackName); + return PublishWithTransAsync(name, contentObj, callbackName); + } - return PublishWithTransAsync(name, content); + protected void Enqueue(CapPublishedMessage message) + { + _dispatcher.EnqueueToPublish(message); } protected abstract void PrepareConnectionForEF(); - protected abstract void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, + protected abstract int Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message); - protected abstract Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, + protected abstract Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message); protected virtual string Serialize(T obj, string callbackName = null) @@ -89,7 +105,6 @@ namespace DotNetCore.CAP.Abstractions { CallbackName = callbackName }; - return packer.Pack(message); } @@ -108,23 +123,38 @@ namespace DotNetCore.CAP.Abstractions private void CheckIsUsingEF(string name) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + if (!IsUsingEF) + { throw new InvalidOperationException( "If you are using the EntityFramework, you need to configure the DbContextType first." + - " otherwise you need to use overloaded method with IDbConnection and IDbTransaction."); + " otherwise you need to use overloaded method with IDbTransaction."); + } } private void CheckIsAdoNet(string name) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + if (IsUsingEF) + { throw new InvalidOperationException( "If you are using the EntityFramework, you do not need to use this overloaded."); + } } - private async Task PublishWithTransAsync(string name, string content) + private async Task PublishWithTransAsync(string name, T contentObj, string callbackName = null) { + Guid operationId = default(Guid); + var content = Serialize(contentObj, callbackName); + var message = new CapPublishedMessage { Name = name, @@ -132,15 +162,39 @@ namespace DotNetCore.CAP.Abstractions StatusName = StatusName.Scheduled }; - await ExecuteAsync(DbConnection, DbTransaction, message); + try + { + operationId = s_diagnosticListener.WritePublishMessageStoreBefore(message); - ClosedCap(); + var id = await ExecuteAsync(DbConnection, DbTransaction, message); + + ClosedCap(); + + if (id > 0) + { + _logger.LogInformation($"message [{message}] has been persisted in the database."); + s_diagnosticListener.WritePublishMessageStoreAfter(operationId, message); - PublishQueuer.PulseEvent.Set(); + message.Id = id; + + Enqueue(message); + } + } + catch (Exception e) + { + _logger.LogError("An exception was occurred when publish message. exception message:" + e.Message, e); + s_diagnosticListener.WritePublishMessageStoreError(operationId, message, e); + Console.WriteLine(e); + throw; + } } - private void PublishWithTrans(string name, string content) + private void PublishWithTrans(string name, T contentObj, string callbackName = null) { + Guid operationId = default(Guid); + + var content = Serialize(contentObj, callbackName); + var message = new CapPublishedMessage { Name = name, @@ -148,11 +202,29 @@ namespace DotNetCore.CAP.Abstractions StatusName = StatusName.Scheduled }; - Execute(DbConnection, DbTransaction, message); + try + { + operationId = s_diagnosticListener.WritePublishMessageStoreBefore(message); + + var id = Execute(DbConnection, DbTransaction, message); - ClosedCap(); + ClosedCap(); - PublishQueuer.PulseEvent.Set(); + if (id > 0) + { + _logger.LogInformation($"message [{message}] has been persisted in the database."); + s_diagnosticListener.WritePublishMessageStoreAfter(operationId, message); + message.Id = id; + Enqueue(message); + } + } + catch (Exception e) + { + _logger.LogError("An exception was occurred when publish message. exception message:" + e.Message, e); + s_diagnosticListener.WritePublishMessageStoreError(operationId, message, e); + Console.WriteLine(e); + throw; + } } private void ClosedCap() @@ -162,8 +234,11 @@ namespace DotNetCore.CAP.Abstractions DbTransaction.Commit(); DbTransaction.Dispose(); } + if (IsCapOpenedConn) + { DbConnection.Dispose(); + } } public void Dispose() diff --git a/src/DotNetCore.CAP/Abstractions/IContentSerializer.cs b/src/DotNetCore.CAP/Abstractions/IContentSerializer.cs index 402843a..108f975 100644 --- a/src/DotNetCore.CAP/Abstractions/IContentSerializer.cs +++ b/src/DotNetCore.CAP/Abstractions/IContentSerializer.cs @@ -1,11 +1,17 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Abstractions { /// /// Message content serializer. - /// By default, CAP will use Json as a serializer, and you can customize this interface to achieve serialization of other methods. + /// + /// By default, CAP will use Json as a serializer, and you can customize this interface to achieve serialization of + /// other methods. + /// /// public interface IContentSerializer { @@ -40,7 +46,7 @@ namespace DotNetCore.CAP.Abstractions /// /// /// We use the wrapper to provide some additional information for the message content,which is important for CAP。 - /// Typically, we may need to customize the field display name of the message, + /// Typically, we may need to customize the field display name of the message, /// which includes interacting with other message components, which can be adapted in this manner /// public interface IMessagePacker @@ -52,9 +58,9 @@ namespace DotNetCore.CAP.Abstractions string Pack(CapMessage obj); /// - /// Unpack a message strings to object. + /// Unpack a message strings to object. /// /// The string of packed message. CapMessage UnPack(string packingMessage); - } + } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Abstractions/IModelBinderFactory.cs b/src/DotNetCore.CAP/Abstractions/IModelBinderFactory.cs index df3423f..ce5de92 100644 --- a/src/DotNetCore.CAP/Abstractions/IModelBinderFactory.cs +++ b/src/DotNetCore.CAP/Abstractions/IModelBinderFactory.cs @@ -1,4 +1,7 @@ -using System.Reflection; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; using DotNetCore.CAP.Abstractions.ModelBinding; namespace DotNetCore.CAP.Abstractions diff --git a/src/DotNetCore.CAP/Abstractions/ISubscriberExecutor.cs b/src/DotNetCore.CAP/Abstractions/ISubscriberExecutor.cs deleted file mode 100644 index 18c9f5d..0000000 --- a/src/DotNetCore.CAP/Abstractions/ISubscriberExecutor.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Threading.Tasks; -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP.Abstractions -{ - /// - /// Consumer method executor. - /// - public interface ISubscriberExecutor - { - /// - /// Execute the consumer method. - /// - /// The received message. - Task ExecuteAsync(CapReceivedMessage receivedMessage); - } -} diff --git a/src/DotNetCore.CAP/Abstractions/ModelBinding/IModelBinder.cs b/src/DotNetCore.CAP/Abstractions/ModelBinding/IModelBinder.cs index ad276b7..6914c47 100644 --- a/src/DotNetCore.CAP/Abstractions/ModelBinding/IModelBinder.cs +++ b/src/DotNetCore.CAP/Abstractions/ModelBinding/IModelBinder.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; namespace DotNetCore.CAP.Abstractions.ModelBinding { diff --git a/src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs b/src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs index 0a23d2b..62a9337 100644 --- a/src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs +++ b/src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs @@ -1,4 +1,7 @@ -using DotNetCore.CAP.Internal; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using DotNetCore.CAP.Internal; namespace DotNetCore.CAP.Abstractions.ModelBinding { @@ -42,7 +45,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding public override string ToString() { if (IsSuccess) + { return $"Success '{Model}'"; + } + return "Failed"; } @@ -50,7 +56,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding { var other = obj as ModelBindingResult?; if (other == null) + { return false; + } + return Equals(other.Value); } diff --git a/src/DotNetCore.CAP/Abstractions/TopicAttribute.cs b/src/DotNetCore.CAP/Abstractions/TopicAttribute.cs index 59dd897..6b83de4 100644 --- a/src/DotNetCore.CAP/Abstractions/TopicAttribute.cs +++ b/src/DotNetCore.CAP/Abstractions/TopicAttribute.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Abstractions { diff --git a/src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs b/src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs index 51ccbff..2a8a586 100644 --- a/src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs +++ b/src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP; using DotNetCore.CAP.Dashboard.GatewayProxy; using Microsoft.Extensions.DependencyInjection; @@ -19,7 +22,9 @@ namespace Microsoft.AspNetCore.Builder public static IApplicationBuilder UseCap(this IApplicationBuilder app) { if (app == null) + { throw new ArgumentNullException(nameof(app)); + } CheckRequirement(app); @@ -31,7 +36,10 @@ namespace Microsoft.AspNetCore.Builder if (provider.GetService() != null) { if (provider.GetService() != null) + { app.UseMiddleware(); + } + app.UseMiddleware(); } @@ -42,18 +50,24 @@ namespace Microsoft.AspNetCore.Builder { var marker = app.ApplicationServices.GetService(); if (marker == null) + { throw new InvalidOperationException( "AddCap() must be called on the service collection. eg: services.AddCap(...)"); + } var messageQueueMarker = app.ApplicationServices.GetService(); if (messageQueueMarker == null) + { throw new InvalidOperationException( "You must be config used message queue provider at AddCap() options! eg: services.AddCap(options=>{ options.UseKafka(...) })"); + } var databaseMarker = app.ApplicationServices.GetService(); if (databaseMarker == null) + { throw new InvalidOperationException( "You must be config used database provider at AddCap() options! eg: services.AddCap(options=>{ options.UseSqlServer(...) })"); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/CAP.Builder.cs b/src/DotNetCore.CAP/CAP.Builder.cs index e59270d..9722461 100644 --- a/src/DotNetCore.CAP/CAP.Builder.cs +++ b/src/DotNetCore.CAP/CAP.Builder.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Abstractions; using Microsoft.Extensions.DependencyInjection; diff --git a/src/DotNetCore.CAP/CAP.Options.cs b/src/DotNetCore.CAP/CAP.Options.cs index a7f6198..05c0294 100644 --- a/src/DotNetCore.CAP/CAP.Options.cs +++ b/src/DotNetCore.CAP/CAP.Options.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Reflection; using DotNetCore.CAP.Models; @@ -10,16 +13,6 @@ namespace DotNetCore.CAP /// public class CapOptions { - /// - /// Default value for polling delay timeout, in seconds. - /// - public const int DefaultPollingDelay = 15; - - /// - /// Default processor count to process messages of cap.queue. - /// - public const int DefaultQueueProcessorCount = 2; - /// /// Default succeeded message expiration time span, in seconds. /// @@ -28,18 +21,16 @@ namespace DotNetCore.CAP /// /// Failed message retry waiting interval. /// - public const int DefaultFailedMessageWaitingInterval = 600; + public const int DefaultFailedMessageWaitingInterval = 60; /// /// Failed message retry count. /// - public const int DefaultFailedRetryCount = 100; + public const int DefaultFailedRetryCount = 50; public CapOptions() { - PollingDelay = DefaultPollingDelay; - QueueProcessorCount = DefaultQueueProcessorCount; SucceedMessageExpiredAfter = DefaultSucceedMessageExpirationAfter; FailedRetryInterval = DefaultFailedMessageWaitingInterval; FailedRetryCount = DefaultFailedRetryCount; @@ -54,18 +45,6 @@ namespace DotNetCore.CAP /// public string DefaultGroup { get; set; } - /// - /// Producer job polling delay time. - /// Default is 15 sec. - /// - public int PollingDelay { get; set; } - - /// - /// Gets or sets the messages queue (Cap.Queue table) processor count. - /// Default is 2 processor. - /// - public int QueueProcessorCount { get; set; } - /// /// Sent or received succeed message after time span of due, then the message will be deleted at due time. /// Default is 24*3600 seconds. @@ -74,18 +53,18 @@ namespace DotNetCore.CAP /// /// Failed messages polling delay time. - /// Default is 600 seconds. + /// Default is 60 seconds. /// public int FailedRetryInterval { get; set; } /// - /// We’ll invoke this call-back with message type,name,content when requeue failed message. + /// We’ll invoke this call-back with message type,name,content when retry failed (send or executed) messages equals times. /// - public Action FailedCallback { get; set; } + public Action FailedThresholdCallback { get; set; } /// /// The number of message retries, the retry will stop when the threshold is reached. - /// Default is 100 times. + /// Default is 50 times. /// public int FailedRetryCount { get; set; } @@ -96,7 +75,9 @@ namespace DotNetCore.CAP public void RegisterExtension(ICapOptionsExtension extension) { if (extension == null) + { throw new ArgumentNullException(nameof(extension)); + } Extensions.Add(extension); } diff --git a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs index cfb1fad..23b585b 100644 --- a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs +++ b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using DotNetCore.CAP; using DotNetCore.CAP.Abstractions; @@ -25,7 +28,10 @@ namespace Microsoft.Extensions.DependencyInjection this IServiceCollection services, Action setupAction) { - if (setupAction == null) throw new ArgumentNullException(nameof(setupAction)); + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } services.TryAddSingleton(); services.Configure(setupAction); @@ -49,21 +55,21 @@ namespace Microsoft.Extensions.DependencyInjection services.AddSingleton(); //Queue's message processor - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); - //Executors - services.AddSingleton(); - services.AddSingleton(); - services.TryAddSingleton(); + //Sender and Executors + services.AddSingleton(); + // Warning: IPublishMessageSender need to inject at extension project. + services.AddSingleton(); //Options and extension service var options = new CapOptions(); setupAction(options); foreach (var serviceExtension in options.Extensions) + { serviceExtension.AddServices(services); + } + services.AddSingleton(options); return new CapBuilder(services); @@ -73,13 +79,19 @@ namespace Microsoft.Extensions.DependencyInjection { var consumerListenerServices = new List>(); foreach (var rejectedServices in services) + { if (rejectedServices.ImplementationType != null && typeof(ICapSubscribe).IsAssignableFrom(rejectedServices.ImplementationType)) + { consumerListenerServices.Add(new KeyValuePair(typeof(ICapSubscribe), rejectedServices.ImplementationType)); + } + } foreach (var service in consumerListenerServices) + { services.AddTransient(service.Key, service.Value); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs b/src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs index ca707ee..d9df0bd 100644 --- a/src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs +++ b/src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Net; using System.Threading.Tasks; diff --git a/src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs b/src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs index 9997280..70d85be 100644 --- a/src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs +++ b/src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Linq; using System.Net; using System.Threading.Tasks; @@ -27,7 +30,10 @@ namespace DotNetCore.CAP public Task Invoke(HttpContext context) { if (!context.Request.Path.StartsWithSegments(_options.PathMatch, - out var matchedPath, out var remainingPath)) return _next(context); + out var matchedPath, out var remainingPath)) + { + return _next(context); + } // Update the path var path = context.Request.Path; @@ -41,7 +47,9 @@ namespace DotNetCore.CAP var findResult = _routes.FindDispatcher(context.Request.Path.Value); if (findResult == null) + { return _next.Invoke(context); + } if (_options.Authorization.Any(filter => !filter.Authorize(dashboardContext))) { diff --git a/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs b/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs index b144996..e04ab24 100644 --- a/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs +++ b/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; using DotNetCore.CAP.Dashboard; // ReSharper disable once CheckNamespace diff --git a/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs b/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs index c2ef243..cfc1bf5 100644 --- a/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs +++ b/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP; using DotNetCore.CAP.Dashboard; using DotNetCore.CAP.Dashboard.GatewayProxy; @@ -40,7 +43,10 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseDashboard(this CapOptions capOptions, Action options) { - if (options == null) throw new ArgumentNullException(nameof(options)); + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } capOptions.RegisterExtension(new DashboardOptionsExtension(options)); diff --git a/src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs b/src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs index 94c0d99..65875c4 100644 --- a/src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs +++ b/src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs @@ -1,4 +1,7 @@ -using System.Reflection; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; namespace DotNetCore.CAP.Dashboard { @@ -22,10 +25,12 @@ namespace DotNetCore.CAP.Dashboard protected override void WriteResponse(DashboardResponse response) { foreach (var resourceName in _resourceNames) + { WriteResource( response, _assembly, $"{_baseNamespace}.{resourceName}"); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs b/src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs index 75ac2b4..2959673 100644 --- a/src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs +++ b/src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Net; using System.Threading.Tasks; @@ -25,9 +28,13 @@ namespace DotNetCore.CAP.Dashboard } if (_command(context)) + { response.StatusCode = (int) HttpStatusCode.NoContent; + } else + { response.StatusCode = 422; + } return Task.FromResult(true); } diff --git a/src/DotNetCore.CAP/Dashboard/Content/js/cap.js b/src/DotNetCore.CAP/Dashboard/Content/js/cap.js index be6a83d..9b5a98c 100644 --- a/src/DotNetCore.CAP/Dashboard/Content/js/cap.js +++ b/src/DotNetCore.CAP/Dashboard/Content/js/cap.js @@ -371,7 +371,7 @@ receivedSucceeded, receivedFailed, receivedSucceededStr, - receivedFailedStr, + receivedFailedStr ); $(window).resize(function() { diff --git a/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.Designer.cs b/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.Designer.cs index a9be4fa..c08a9ab 100644 --- a/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.Designer.cs +++ b/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.Designer.cs @@ -879,15 +879,6 @@ namespace DotNetCore.CAP.Dashboard.Resources { } } - /// - /// Looks up a localized string similar to Processing. - /// - public static string SidebarMenu_Processing { - get { - return ResourceManager.GetString("SidebarMenu_Processing", resourceCulture); - } - } - /// /// Looks up a localized string similar to Succeeded. /// diff --git a/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.resx b/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.resx index b8aa259..57ed89d 100644 --- a/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.resx +++ b/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.resx @@ -240,9 +240,6 @@ Failed - - Processing - Succeeded diff --git a/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.zh.resx b/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.zh.resx index a75494d..90ed01e 100644 --- a/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.zh.resx +++ b/src/DotNetCore.CAP/Dashboard/Content/resx/Strings.zh.resx @@ -234,9 +234,6 @@ 失败 - - 执行中 - 完成 diff --git a/src/DotNetCore.CAP/Dashboard/DashboardContext.cs b/src/DotNetCore.CAP/Dashboard/DashboardContext.cs index de6dd43..c97a308 100644 --- a/src/DotNetCore.CAP/Dashboard/DashboardContext.cs +++ b/src/DotNetCore.CAP/Dashboard/DashboardContext.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; @@ -8,8 +11,15 @@ namespace DotNetCore.CAP.Dashboard { protected DashboardContext(IStorage storage, DashboardOptions options) { - if (storage == null) throw new ArgumentNullException(nameof(storage)); - if (options == null) throw new ArgumentNullException(nameof(options)); + if (storage == null) + { + throw new ArgumentNullException(nameof(storage)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } Storage = storage; Options = options; @@ -36,7 +46,10 @@ namespace DotNetCore.CAP.Dashboard HttpContext httpContext) : base(storage, options) { - if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } HttpContext = httpContext; Request = new CapDashboardRequest(httpContext); diff --git a/src/DotNetCore.CAP/Dashboard/DashboardMetric.cs b/src/DotNetCore.CAP/Dashboard/DashboardMetric.cs index 8bda1b1..6d0209b 100644 --- a/src/DotNetCore.CAP/Dashboard/DashboardMetric.cs +++ b/src/DotNetCore.CAP/Dashboard/DashboardMetric.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; namespace DotNetCore.CAP.Dashboard diff --git a/src/DotNetCore.CAP/Dashboard/DashboardMetrics.cs b/src/DotNetCore.CAP/Dashboard/DashboardMetrics.cs index c6c4ba9..2fcd370 100644 --- a/src/DotNetCore.CAP/Dashboard/DashboardMetrics.cs +++ b/src/DotNetCore.CAP/Dashboard/DashboardMetrics.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Linq; using DotNetCore.CAP.Dashboard.Resources; @@ -64,24 +67,6 @@ namespace DotNetCore.CAP.Dashboard } : null); - //---------------------------------------------------- - - public static readonly DashboardMetric PublishedProcessingCount = new DashboardMetric( - "published_processing:count", - "Metrics_ProcessingJobs", - page => new Metric(page.Statistics.PublishedProcessing.ToString("N0")) - { - Style = page.Statistics.PublishedProcessing > 0 ? MetricStyle.Warning : MetricStyle.Default - }); - - public static readonly DashboardMetric ReceivedProcessingCount = new DashboardMetric( - "received_processing:count", - "Metrics_ProcessingJobs", - page => new Metric(page.Statistics.ReceivedProcessing.ToString("N0")) - { - Style = page.Statistics.ReceivedProcessing > 0 ? MetricStyle.Warning : MetricStyle.Default - }); - //---------------------------------------------------- public static readonly DashboardMetric PublishedSucceededCount = new DashboardMetric( "published_succeeded:count", @@ -129,9 +114,6 @@ namespace DotNetCore.CAP.Dashboard AddMetric(PublishedFailedCountOrNull); AddMetric(ReceivedFailedCountOrNull); - AddMetric(PublishedProcessingCount); - AddMetric(ReceivedProcessingCount); - AddMetric(PublishedSucceededCount); AddMetric(ReceivedSucceededCount); @@ -141,7 +123,10 @@ namespace DotNetCore.CAP.Dashboard public static void AddMetric(DashboardMetric metric) { - if (metric == null) throw new ArgumentNullException(nameof(metric)); + if (metric == null) + { + throw new ArgumentNullException(nameof(metric)); + } lock (Metrics) { diff --git a/src/DotNetCore.CAP/Dashboard/DashboardRequest.cs b/src/DotNetCore.CAP/Dashboard/DashboardRequest.cs index 2499206..5fdb054 100644 --- a/src/DotNetCore.CAP/Dashboard/DashboardRequest.cs +++ b/src/DotNetCore.CAP/Dashboard/DashboardRequest.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -25,7 +28,11 @@ namespace DotNetCore.CAP.Dashboard public CapDashboardRequest(HttpContext context) { - if (context == null) throw new ArgumentNullException(nameof(context)); + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + _context = context; } diff --git a/src/DotNetCore.CAP/Dashboard/DashboardResponse.cs b/src/DotNetCore.CAP/Dashboard/DashboardResponse.cs index d435f2a..ceb0d52 100644 --- a/src/DotNetCore.CAP/Dashboard/DashboardResponse.cs +++ b/src/DotNetCore.CAP/Dashboard/DashboardResponse.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Globalization; using System.IO; using System.Threading.Tasks; diff --git a/src/DotNetCore.CAP/Dashboard/DashboardRoutes.cs b/src/DotNetCore.CAP/Dashboard/DashboardRoutes.cs index 30e2689..312da57 100644 --- a/src/DotNetCore.CAP/Dashboard/DashboardRoutes.cs +++ b/src/DotNetCore.CAP/Dashboard/DashboardRoutes.cs @@ -1,4 +1,7 @@ -using System.Reflection; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; using DotNetCore.CAP.Dashboard.Pages; using DotNetCore.CAP.Infrastructure; diff --git a/src/DotNetCore.CAP/Dashboard/EmbeddedResourceDispatcher.cs b/src/DotNetCore.CAP/Dashboard/EmbeddedResourceDispatcher.cs index ce19de3..c48257c 100644 --- a/src/DotNetCore.CAP/Dashboard/EmbeddedResourceDispatcher.cs +++ b/src/DotNetCore.CAP/Dashboard/EmbeddedResourceDispatcher.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Reflection; using System.Threading.Tasks; @@ -47,8 +50,10 @@ namespace DotNetCore.CAP.Dashboard using (var inputStream = assembly.GetManifestResourceStream(resourceName)) { if (inputStream == null) + { throw new ArgumentException( $@"Resource with name {resourceName} not found in assembly {assembly}."); + } inputStream.CopyTo(response.Body); } diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/DownstreamUrl.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/DownstreamUrl.cs index b4ba407..61ca4b1 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/DownstreamUrl.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/DownstreamUrl.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard.GatewayProxy +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard.GatewayProxy { public class DownstreamUrl { diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/GatewayProxyMiddleware.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/GatewayProxyMiddleware.cs index b166acd..596b32b 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/GatewayProxyMiddleware.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/GatewayProxyMiddleware.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -94,7 +97,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) { foreach (var httpResponseHeader in response.Content.Headers) + { AddHeaderIfDoesntExist(context, httpResponseHeader); + } var content = await response.Content.ReadAsByteArrayAsync(); @@ -113,7 +118,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy using (Stream stream = new MemoryStream(content)) { if (response.StatusCode != HttpStatusCode.NotModified) + { await stream.CopyToAsync(context.Response.Body); + } } } @@ -134,8 +141,10 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy KeyValuePair> httpResponseHeader) { if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) + { context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Value.ToArray())); + } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.Default.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.Default.cs index 90d80a9..4548ce5 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.Default.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.Default.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -45,7 +48,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy FragmentString fragment = new FragmentString()) { if (scheme == null) + { throw new ArgumentNullException(nameof(scheme)); + } var combinedPath = pathBase.HasValue || path.HasValue ? (pathBase + path).ToString() : "/"; @@ -75,7 +80,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy private async Task MapContent(HttpRequest request) { if (request.Body == null) + { return null; + } var content = new ByteArrayContent(await ToByteArray(request.Body)); @@ -97,8 +104,12 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) { foreach (var header in request.Headers) + { if (IsSupportedHeader(header)) + { requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); + } + } } private async Task ToByteArray(Stream stream) diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.cs index ee1f994..e4f3d52 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.cs @@ -1,4 +1,7 @@ -using System.Net.Http; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientBuilder.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientBuilder.cs index df913b6..843f9df 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientBuilder.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientBuilder.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientHttpRequester.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientHttpRequester.cs index 807e452..a608690 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientHttpRequester.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientHttpRequester.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -44,7 +47,10 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester var httpClient = _cacheHandlers.Get(cacheKey); if (httpClient == null) + { httpClient = builder.Create(); + } + return httpClient; } diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClient.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClient.cs index 8a8d12f..6fb0bb8 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClient.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClient.cs @@ -1,4 +1,7 @@ -using System.Net.Http; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; using System.Threading.Tasks; namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientBuilder.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientBuilder.cs index 3c24f6e..ed7f46e 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientBuilder.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientBuilder.cs @@ -1,4 +1,7 @@ -using System.Net.Http; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester { diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientCache.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientCache.cs index d1b5ce3..088a64d 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientCache.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientCache.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester { diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpRequester.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpRequester.cs index 3b87f07..b1c7fec 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpRequester.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpRequester.cs @@ -1,4 +1,7 @@ -using System.Net.Http; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; using System.Threading.Tasks; namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester diff --git a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/MemoryHttpClientCache.cs b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/MemoryHttpClientCache.cs index 20498ec..21aa033 100644 --- a/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/MemoryHttpClientCache.cs +++ b/src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/MemoryHttpClientCache.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Concurrent; namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester @@ -31,7 +34,10 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester { IHttpClient client = null; if (_httpClientsCache.TryGetValue(id, out var connectionQueue)) + { connectionQueue.TryDequeue(out client); + } + return client; } diff --git a/src/DotNetCore.CAP/Dashboard/HtmlHelper.cs b/src/DotNetCore.CAP/Dashboard/HtmlHelper.cs index 75d09c1..a685fa7 100644 --- a/src/DotNetCore.CAP/Dashboard/HtmlHelper.cs +++ b/src/DotNetCore.CAP/Dashboard/HtmlHelper.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -24,44 +27,71 @@ namespace DotNetCore.CAP.Dashboard public NonEscapedString Breadcrumbs(string title, IDictionary items) { - if (items == null) throw new ArgumentNullException(nameof(items)); + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + return RenderPartial(new Breadcrumbs(title, items)); } public NonEscapedString MessagesSidebar(MessageType type) { if (type == MessageType.Publish) + { return SidebarMenu(MessagesSidebarMenu.PublishedItems); + } + return SidebarMenu(MessagesSidebarMenu.ReceivedItems); } public NonEscapedString SidebarMenu(IEnumerable> items) { - if (items == null) throw new ArgumentNullException(nameof(items)); + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + return RenderPartial(new SidebarMenu(items)); } public NonEscapedString BlockMetric(DashboardMetric metric) { - if (metric == null) throw new ArgumentNullException(nameof(metric)); + if (metric == null) + { + throw new ArgumentNullException(nameof(metric)); + } + return RenderPartial(new BlockMetric(metric)); } public NonEscapedString InlineMetric(DashboardMetric metric) { - if (metric == null) throw new ArgumentNullException(nameof(metric)); + if (metric == null) + { + throw new ArgumentNullException(nameof(metric)); + } + return RenderPartial(new InlineMetric(metric)); } public NonEscapedString Paginator(Pager pager) { - if (pager == null) throw new ArgumentNullException(nameof(pager)); + if (pager == null) + { + throw new ArgumentNullException(nameof(pager)); + } + return RenderPartial(new Paginator(pager)); } public NonEscapedString PerPageSelector(Pager pager) { - if (pager == null) throw new ArgumentNullException(nameof(pager)); + if (pager == null) + { + throw new ArgumentNullException(nameof(pager)); + } + return RenderPartial(new PerPageSelector(pager)); } @@ -79,7 +109,9 @@ namespace DotNetCore.CAP.Dashboard public NonEscapedString StateLabel(string stateName) { if (string.IsNullOrWhiteSpace(stateName)) + { return Raw($"{Strings.Common_NoState}"); + } return Raw( $"{stateName}"); @@ -102,40 +134,59 @@ namespace DotNetCore.CAP.Dashboard public string ToHumanDuration(TimeSpan? duration, bool displaySign = true) { - if (duration == null) return null; + if (duration == null) + { + return null; + } var builder = new StringBuilder(); if (displaySign) + { builder.Append(duration.Value.TotalMilliseconds < 0 ? "-" : "+"); + } duration = duration.Value.Duration(); if (duration.Value.Days > 0) + { builder.Append($"{duration.Value.Days}d "); + } if (duration.Value.Hours > 0) + { builder.Append($"{duration.Value.Hours}h "); + } if (duration.Value.Minutes > 0) + { builder.Append($"{duration.Value.Minutes}m "); + } if (duration.Value.TotalHours < 1) + { if (duration.Value.Seconds > 0) { builder.Append(duration.Value.Seconds); if (duration.Value.Milliseconds > 0) + { builder.Append($".{duration.Value.Milliseconds.ToString().PadLeft(3, '0')}"); + } builder.Append("s "); } else { if (duration.Value.Milliseconds > 0) + { builder.Append($"{duration.Value.Milliseconds}ms "); + } } + } if (builder.Length <= 1) + { builder.Append(" <1ms "); + } builder.Remove(builder.Length - 1, 1); @@ -237,14 +288,25 @@ namespace DotNetCore.CAP.Dashboard private string WrapType(Type type) { if (type == null) + { return string.Empty; + } if (type.Name == "Void") + { return WrapKeyword(type.Name.ToLower()); + } + if (Helper.IsComplexType(type)) + { return WrapType(type.Name); + } + if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal)) + { return WrapKeyword(type.Name.ToLower()); + } + return WrapType(type.Name); } diff --git a/src/DotNetCore.CAP/Dashboard/IDashboardAuthorizationFilter.cs b/src/DotNetCore.CAP/Dashboard/IDashboardAuthorizationFilter.cs index f3833a8..0e984ba 100644 --- a/src/DotNetCore.CAP/Dashboard/IDashboardAuthorizationFilter.cs +++ b/src/DotNetCore.CAP/Dashboard/IDashboardAuthorizationFilter.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard { public interface IDashboardAuthorizationFilter { diff --git a/src/DotNetCore.CAP/Dashboard/IDashboardDispatcher.cs b/src/DotNetCore.CAP/Dashboard/IDashboardDispatcher.cs index ce9cf3e..9204b6e 100644 --- a/src/DotNetCore.CAP/Dashboard/IDashboardDispatcher.cs +++ b/src/DotNetCore.CAP/Dashboard/IDashboardDispatcher.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; namespace DotNetCore.CAP.Dashboard { diff --git a/src/DotNetCore.CAP/Dashboard/IMonitoringApi.cs b/src/DotNetCore.CAP/Dashboard/IMonitoringApi.cs index 4e9f60b..e5ecd71 100644 --- a/src/DotNetCore.CAP/Dashboard/IMonitoringApi.cs +++ b/src/DotNetCore.CAP/Dashboard/IMonitoringApi.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using DotNetCore.CAP.Dashboard.Monitoring; @@ -13,14 +16,10 @@ namespace DotNetCore.CAP.Dashboard int PublishedFailedCount(); - int PublishedProcessingCount(); - int PublishedSucceededCount(); int ReceivedFailedCount(); - int ReceivedProcessingCount(); - int ReceivedSucceededCount(); IDictionary HourlySucceededJobs(MessageType type); diff --git a/src/DotNetCore.CAP/Dashboard/JsonDispatcher.cs b/src/DotNetCore.CAP/Dashboard/JsonDispatcher.cs index c50109f..89eadea 100644 --- a/src/DotNetCore.CAP/Dashboard/JsonDispatcher.cs +++ b/src/DotNetCore.CAP/Dashboard/JsonDispatcher.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -37,7 +40,9 @@ namespace DotNetCore.CAP.Dashboard } if (_jsonCommand != null) + { serialized = _jsonCommand(context); + } context.Response.ContentType = "application/json"; await context.Response.WriteAsync(serialized ?? string.Empty); diff --git a/src/DotNetCore.CAP/Dashboard/JsonStats.cs b/src/DotNetCore.CAP/Dashboard/JsonStats.cs index fbc9f2c..4b3db7b 100644 --- a/src/DotNetCore.CAP/Dashboard/JsonStats.cs +++ b/src/DotNetCore.CAP/Dashboard/JsonStats.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; diff --git a/src/DotNetCore.CAP/Dashboard/LocalRequestsOnlyAuthorizationFilter.cs b/src/DotNetCore.CAP/Dashboard/LocalRequestsOnlyAuthorizationFilter.cs index 145cdff..42746d7 100644 --- a/src/DotNetCore.CAP/Dashboard/LocalRequestsOnlyAuthorizationFilter.cs +++ b/src/DotNetCore.CAP/Dashboard/LocalRequestsOnlyAuthorizationFilter.cs @@ -1,4 +1,7 @@ -using DotNetCore.CAP.Infrastructure; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using DotNetCore.CAP.Infrastructure; namespace DotNetCore.CAP.Dashboard { @@ -8,19 +11,27 @@ namespace DotNetCore.CAP.Dashboard { // if unknown, assume not local if (string.IsNullOrEmpty(context.Request.RemoteIpAddress)) + { return false; + } // check if localhost if (context.Request.RemoteIpAddress == "127.0.0.1" || context.Request.RemoteIpAddress == "::1") + { return true; + } // compare with local address if (context.Request.RemoteIpAddress == context.Request.LocalIpAddress) + { return true; + } // check if private ip if (Helper.IsInnerIP(context.Request.RemoteIpAddress)) + { return true; + } return false; } diff --git a/src/DotNetCore.CAP/Dashboard/MenuItem.cs b/src/DotNetCore.CAP/Dashboard/MenuItem.cs index 3f0ea1c..d6aac72 100644 --- a/src/DotNetCore.CAP/Dashboard/MenuItem.cs +++ b/src/DotNetCore.CAP/Dashboard/MenuItem.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System.Collections.Generic; using System.Linq; @@ -23,7 +26,9 @@ namespace DotNetCore.CAP.Dashboard var metrics = new List {Metric}; if (Metrics != null) + { metrics.AddRange(Metrics); + } return metrics.Where(x => x != null).ToList(); } diff --git a/src/DotNetCore.CAP/Dashboard/MessageHistoryRenderer.cs b/src/DotNetCore.CAP/Dashboard/MessageHistoryRenderer.cs index 990aee7..2df3b98 100644 --- a/src/DotNetCore.CAP/Dashboard/MessageHistoryRenderer.cs +++ b/src/DotNetCore.CAP/Dashboard/MessageHistoryRenderer.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Net; @@ -23,18 +26,13 @@ namespace DotNetCore.CAP.Dashboard { Register(StatusName.Succeeded, SucceededRenderer); Register(StatusName.Failed, FailedRenderer); - Register(StatusName.Processing, ProcessingRenderer); - BackgroundStateColors.Add(StatusName.Enqueued, "#F5F5F5"); BackgroundStateColors.Add(StatusName.Succeeded, "#EDF7ED"); BackgroundStateColors.Add(StatusName.Failed, "#FAEBEA"); - BackgroundStateColors.Add(StatusName.Processing, "#FCEFDC"); BackgroundStateColors.Add(StatusName.Scheduled, "#E0F3F8"); - ForegroundStateColors.Add(StatusName.Enqueued, "#999"); ForegroundStateColors.Add(StatusName.Succeeded, "#5cb85c"); ForegroundStateColors.Add(StatusName.Failed, "#d9534f"); - ForegroundStateColors.Add(StatusName.Processing, "#f0ad4e"); ForegroundStateColors.Add(StatusName.Scheduled, "#5bc0de"); } @@ -46,7 +44,9 @@ namespace DotNetCore.CAP.Dashboard public static string GetBackgroundStateColor(string stateName) { if (stateName == null || !BackgroundStateColors.ContainsKey(stateName)) + { return "inherit"; + } return BackgroundStateColors[stateName]; } @@ -59,7 +59,9 @@ namespace DotNetCore.CAP.Dashboard public static string GetForegroundStateColor(string stateName) { if (stateName == null || !ForegroundStateColors.ContainsKey(stateName)) + { return "inherit"; + } return ForegroundStateColors[stateName]; } @@ -68,9 +70,13 @@ namespace DotNetCore.CAP.Dashboard Func, NonEscapedString> renderer) { if (!Renderers.ContainsKey(state)) + { Renderers.Add(state, renderer); + } else + { Renderers[state] = renderer; + } } public static bool Exists(string state) @@ -96,7 +102,10 @@ namespace DotNetCore.CAP.Dashboard public static NonEscapedString DefaultRenderer(HtmlHelper helper, IDictionary stateData) { - if (stateData == null || stateData.Count == 0) return null; + if (stateData == null || stateData.Count == 0) + { + return null; + } var builder = new StringBuilder(); builder.Append("
"); @@ -146,7 +155,10 @@ namespace DotNetCore.CAP.Dashboard builder.Append("
"); - if (!itemsAdded) return null; + if (!itemsAdded) + { + return null; + } return new NonEscapedString(builder.ToString()); } @@ -166,9 +178,13 @@ namespace DotNetCore.CAP.Dashboard string serverId = null; if (stateData.ContainsKey("ServerId")) + { serverId = stateData["ServerId"]; + } else if (stateData.ContainsKey("ServerName")) + { serverId = stateData["ServerName"]; + } if (serverId != null) { diff --git a/src/DotNetCore.CAP/Dashboard/MessagesSidebarMenu.cs b/src/DotNetCore.CAP/Dashboard/MessagesSidebarMenu.cs index 0d68f5d..35a4dfd 100644 --- a/src/DotNetCore.CAP/Dashboard/MessagesSidebarMenu.cs +++ b/src/DotNetCore.CAP/Dashboard/MessagesSidebarMenu.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using DotNetCore.CAP.Dashboard.Resources; @@ -20,13 +23,6 @@ namespace DotNetCore.CAP.Dashboard Metric = DashboardMetrics.PublishedSucceededCount }); - PublishedItems.Add(page => - new MenuItem(Strings.SidebarMenu_Processing, page.Url.To("/published/processing")) - { - Active = page.RequestPath.StartsWith("/published/processing"), - Metric = DashboardMetrics.PublishedProcessingCount - }); - PublishedItems.Add(page => new MenuItem(Strings.SidebarMenu_Failed, page.Url.To("/published/failed")) { Active = page.RequestPath.StartsWith("/published/failed"), @@ -41,12 +37,6 @@ namespace DotNetCore.CAP.Dashboard Metric = DashboardMetrics.ReceivedSucceededCount }); - ReceivedItems.Add(page => new MenuItem(Strings.SidebarMenu_Processing, page.Url.To("/received/processing")) - { - Active = page.RequestPath.StartsWith("/received/processing"), - Metric = DashboardMetrics.ReceivedProcessingCount - }); - ReceivedItems.Add(page => new MenuItem(Strings.SidebarMenu_Failed, page.Url.To("/received/failed")) { Active = page.RequestPath.StartsWith("/received/failed"), diff --git a/src/DotNetCore.CAP/Dashboard/Metric.cs b/src/DotNetCore.CAP/Dashboard/Metric.cs index fafa537..661511e 100644 --- a/src/DotNetCore.CAP/Dashboard/Metric.cs +++ b/src/DotNetCore.CAP/Dashboard/Metric.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard { public class Metric { diff --git a/src/DotNetCore.CAP/Dashboard/Monitoring/MessageDto.cs b/src/DotNetCore.CAP/Dashboard/Monitoring/MessageDto.cs index 5c2b1f0..1a256ba 100644 --- a/src/DotNetCore.CAP/Dashboard/Monitoring/MessageDto.cs +++ b/src/DotNetCore.CAP/Dashboard/Monitoring/MessageDto.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Dashboard.Monitoring { diff --git a/src/DotNetCore.CAP/Dashboard/Monitoring/MessageQueryDto.cs b/src/DotNetCore.CAP/Dashboard/Monitoring/MessageQueryDto.cs index 98f3e97..02da29b 100644 --- a/src/DotNetCore.CAP/Dashboard/Monitoring/MessageQueryDto.cs +++ b/src/DotNetCore.CAP/Dashboard/Monitoring/MessageQueryDto.cs @@ -1,4 +1,7 @@ -using DotNetCore.CAP.Models; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Dashboard.Monitoring { diff --git a/src/DotNetCore.CAP/Dashboard/Monitoring/ServerDto.cs b/src/DotNetCore.CAP/Dashboard/Monitoring/ServerDto.cs index 8564205..f332b5e 100644 --- a/src/DotNetCore.CAP/Dashboard/Monitoring/ServerDto.cs +++ b/src/DotNetCore.CAP/Dashboard/Monitoring/ServerDto.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard.Monitoring +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard.Monitoring { public class SubscriberDto { diff --git a/src/DotNetCore.CAP/Dashboard/Monitoring/StatisticsDto.cs b/src/DotNetCore.CAP/Dashboard/Monitoring/StatisticsDto.cs index 38a8355..7c7a529 100644 --- a/src/DotNetCore.CAP/Dashboard/Monitoring/StatisticsDto.cs +++ b/src/DotNetCore.CAP/Dashboard/Monitoring/StatisticsDto.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard.Monitoring +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard.Monitoring { public class StatisticsDto { @@ -9,8 +12,5 @@ public int PublishedFailed { get; set; } public int ReceivedFailed { get; set; } - - public int PublishedProcessing { get; set; } - public int ReceivedProcessing { get; set; } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/NavigationMenu.cs b/src/DotNetCore.CAP/Dashboard/NavigationMenu.cs index a0f94e7..4d8babd 100644 --- a/src/DotNetCore.CAP/Dashboard/NavigationMenu.cs +++ b/src/DotNetCore.CAP/Dashboard/NavigationMenu.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using DotNetCore.CAP.Dashboard.Resources; diff --git a/src/DotNetCore.CAP/Dashboard/NonEscapedString.cs b/src/DotNetCore.CAP/Dashboard/NonEscapedString.cs index 60b60b9..dcc235c 100644 --- a/src/DotNetCore.CAP/Dashboard/NonEscapedString.cs +++ b/src/DotNetCore.CAP/Dashboard/NonEscapedString.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard { public class NonEscapedString { diff --git a/src/DotNetCore.CAP/Dashboard/Pager.cs b/src/DotNetCore.CAP/Dashboard/Pager.cs index 8962ff0..55b7267 100644 --- a/src/DotNetCore.CAP/Dashboard/Pager.cs +++ b/src/DotNetCore.CAP/Dashboard/Pager.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; namespace DotNetCore.CAP.Dashboard @@ -35,14 +38,21 @@ namespace DotNetCore.CAP.Dashboard public string PageUrl(int page) { - if (page < 1 || page > TotalPageCount) return "#"; + if (page < 1 || page > TotalPageCount) + { + return "#"; + } return BasePageUrl + "?from=" + (page - 1) * RecordsPerPage + "&count=" + RecordsPerPage; } public string RecordsPerPageUrl(int perPage) { - if (perPage <= 0) return "#"; + if (perPage <= 0) + { + return "#"; + } + return BasePageUrl + "?from=0&count=" + perPage; } @@ -51,23 +61,35 @@ namespace DotNetCore.CAP.Dashboard // start page index _startPageIndex = CurrentPage - PageItemsCount / 2; if (_startPageIndex + PageItemsCount > TotalPageCount) + { _startPageIndex = TotalPageCount + 1 - PageItemsCount; + } + if (_startPageIndex < 1) + { _startPageIndex = 1; + } // end page index _endPageIndex = _startPageIndex + PageItemsCount - 1; if (_endPageIndex > TotalPageCount) + { _endPageIndex = TotalPageCount; + } var pagerItems = new List(); - if (TotalPageCount == 0) return pagerItems; + if (TotalPageCount == 0) + { + return pagerItems; + } AddPrevious(pagerItems); // first page if (_startPageIndex > 1) + { pagerItems.Add(new Item(1, false, ItemType.Page)); + } // more page before numeric page buttons AddMoreBefore(pagerItems); @@ -80,7 +102,9 @@ namespace DotNetCore.CAP.Dashboard // last page if (_endPageIndex < TotalPageCount) + { pagerItems.Add(new Item(TotalPageCount, false, ItemType.Page)); + } // Next page AddNext(pagerItems); @@ -99,7 +123,11 @@ namespace DotNetCore.CAP.Dashboard if (_startPageIndex > 2) { var index = _startPageIndex - 1; - if (index < 1) index = 1; + if (index < 1) + { + index = 1; + } + var item = new Item(index, false, ItemType.MorePage); results.Add(item); } @@ -110,7 +138,11 @@ namespace DotNetCore.CAP.Dashboard if (_endPageIndex < TotalPageCount - 1) { var index = _startPageIndex + PageItemsCount; - if (index > TotalPageCount) index = TotalPageCount; + if (index > TotalPageCount) + { + index = TotalPageCount; + } + var item = new Item(index, false, ItemType.MorePage); results.Add(item); } diff --git a/src/DotNetCore.CAP/Dashboard/Pages/BlockMetric.cs b/src/DotNetCore.CAP/Dashboard/Pages/BlockMetric.cs index 4687d2d..faa0c84 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/BlockMetric.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/BlockMetric.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard.Pages +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard.Pages { internal partial class BlockMetric { diff --git a/src/DotNetCore.CAP/Dashboard/Pages/Breadcrumbs.cs b/src/DotNetCore.CAP/Dashboard/Pages/Breadcrumbs.cs index 20c2339..2295890 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/Breadcrumbs.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/Breadcrumbs.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; namespace DotNetCore.CAP.Dashboard.Pages { diff --git a/src/DotNetCore.CAP/Dashboard/Pages/HomePage.cs b/src/DotNetCore.CAP/Dashboard/Pages/HomePage.cs index 2e1b0ce..8773831 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/HomePage.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/HomePage.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; namespace DotNetCore.CAP.Dashboard.Pages { diff --git a/src/DotNetCore.CAP/Dashboard/Pages/InlineMetric.cs b/src/DotNetCore.CAP/Dashboard/Pages/InlineMetric.cs index f24c19a..26b9ba1 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/InlineMetric.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/InlineMetric.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard.Pages +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard.Pages { internal partial class InlineMetric { diff --git a/src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cs b/src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cs index 820402b..720e552 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard.Pages +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard.Pages { partial class LayoutPage { diff --git a/src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cshtml b/src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cshtml index 28ef474..e73ee90 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cshtml +++ b/src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cshtml @@ -16,68 +16,68 @@ - -
+ +
- - - - -
- @RenderBody() -
-
- - -
+ +
+ @RenderBody()
+
+ + + +
+
- + \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/Pages/NodePage.cs b/src/DotNetCore.CAP/Dashboard/Pages/NodePage.cs index 2728dc4..9f677b8 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/NodePage.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/NodePage.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; using DotNetCore.CAP.NodeDiscovery; using Microsoft.Extensions.DependencyInjection; @@ -31,8 +34,10 @@ namespace DotNetCore.CAP.Dashboard.Pages { return new List(); } + _nodes = _discoveryProvider.GetNodes().GetAwaiter().GetResult(); } + return _nodes; } } diff --git a/src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cs b/src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cs index 469ef0a..2ccece2 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Dashboard.Pages { @@ -15,10 +18,10 @@ namespace DotNetCore.CAP.Dashboard.Pages { if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, StringComparison.CurrentCultureIgnoreCase)) + { return api.PublishedSucceededCount(); - if (string.Equals(StatusName, Infrastructure.StatusName.Processing, - StringComparison.CurrentCultureIgnoreCase)) - return api.PublishedProcessingCount(); + } + return api.PublishedFailedCount(); } } diff --git a/src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cs b/src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cs index ddf2caa..9090876 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Dashboard.Pages { @@ -15,10 +18,10 @@ namespace DotNetCore.CAP.Dashboard.Pages { if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, StringComparison.CurrentCultureIgnoreCase)) + { return api.ReceivedSucceededCount(); - if (string.Equals(StatusName, Infrastructure.StatusName.Processing, - StringComparison.CurrentCultureIgnoreCase)) - return api.ReceivedProcessingCount(); + } + return api.ReceivedFailedCount(); } } diff --git a/src/DotNetCore.CAP/Dashboard/Pages/SidebarMenu.cs b/src/DotNetCore.CAP/Dashboard/Pages/SidebarMenu.cs index 8f79ae3..a2fcd41 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/SidebarMenu.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/SidebarMenu.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; namespace DotNetCore.CAP.Dashboard.Pages diff --git a/src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cs b/src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cs index e20da01..0794cf7 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard.Pages +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard.Pages { internal partial class Paginator { diff --git a/src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cshtml b/src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cshtml index 4c2b640..b377a57 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cshtml +++ b/src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cshtml @@ -1,5 +1,4 @@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ - @using DotNetCore.CAP.Dashboard @using DotNetCore.CAP.Dashboard.Resources @inherits DotNetCore.CAP.Dashboard.RazorPage diff --git a/src/DotNetCore.CAP/Dashboard/Pages/_PerPageSelector.cs b/src/DotNetCore.CAP/Dashboard/Pages/_PerPageSelector.cs index cbe86fb..6f88462 100644 --- a/src/DotNetCore.CAP/Dashboard/Pages/_PerPageSelector.cs +++ b/src/DotNetCore.CAP/Dashboard/Pages/_PerPageSelector.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Dashboard.Pages +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Dashboard.Pages { internal partial class PerPageSelector { diff --git a/src/DotNetCore.CAP/Dashboard/RazorPage.cs b/src/DotNetCore.CAP/Dashboard/RazorPage.cs index 93b3425..616441d 100644 --- a/src/DotNetCore.CAP/Dashboard/RazorPage.cs +++ b/src/DotNetCore.CAP/Dashboard/RazorPage.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Diagnostics; using System.Net; using System.Text; @@ -36,7 +39,11 @@ namespace DotNetCore.CAP.Dashboard { get { - if (_statisticsLazy == null) throw new InvalidOperationException("Page is not initialized."); + if (_statisticsLazy == null) + { + throw new InvalidOperationException("Page is not initialized."); + } + return _statisticsLazy.Value; } } @@ -104,6 +111,7 @@ namespace DotNetCore.CAP.Dashboard { return $"{discoveryOptions.NodeName}({discoveryOptions.NodeId})"; } + return null; } @@ -111,7 +119,7 @@ namespace DotNetCore.CAP.Dashboard { if (CapCache.Global.TryGet("cap.nodes.count", out var count)) { - dto.Servers = (int)count; + dto.Servers = (int) count; } else { @@ -128,7 +136,10 @@ namespace DotNetCore.CAP.Dashboard protected void WriteLiteral(string textToAppend) { if (string.IsNullOrEmpty(textToAppend)) + { return; + } + _content.Append(textToAppend); } @@ -136,7 +147,10 @@ namespace DotNetCore.CAP.Dashboard protected virtual void Write(object value) { if (value == null) + { return; + } + var html = value as NonEscapedString; WriteLiteral(html?.ToString() ?? Encode(value.ToString())); } diff --git a/src/DotNetCore.CAP/Dashboard/RazorPageDispatcher.cs b/src/DotNetCore.CAP/Dashboard/RazorPageDispatcher.cs index ade0988..f707a8d 100644 --- a/src/DotNetCore.CAP/Dashboard/RazorPageDispatcher.cs +++ b/src/DotNetCore.CAP/Dashboard/RazorPageDispatcher.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Text.RegularExpressions; using System.Threading.Tasks; diff --git a/src/DotNetCore.CAP/Dashboard/RouteCollection.cs b/src/DotNetCore.CAP/Dashboard/RouteCollection.cs index 3831ad6..b093e9f 100644 --- a/src/DotNetCore.CAP/Dashboard/RouteCollection.cs +++ b/src/DotNetCore.CAP/Dashboard/RouteCollection.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Text.RegularExpressions; @@ -11,24 +14,39 @@ namespace DotNetCore.CAP.Dashboard public void Add(string pathTemplate, IDashboardDispatcher dispatcher) { - if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); - if (dispatcher == null) throw new ArgumentNullException(nameof(dispatcher)); + if (pathTemplate == null) + { + throw new ArgumentNullException(nameof(pathTemplate)); + } + + if (dispatcher == null) + { + throw new ArgumentNullException(nameof(dispatcher)); + } _dispatchers.Add(new Tuple(pathTemplate, dispatcher)); } public Tuple FindDispatcher(string path) { - if (path.Length == 0) path = "/"; + if (path.Length == 0) + { + path = "/"; + } foreach (var dispatcher in _dispatchers) { var pattern = dispatcher.Item1; if (!pattern.StartsWith("^", StringComparison.OrdinalIgnoreCase)) + { pattern = "^" + pattern; + } + if (!pattern.EndsWith("$", StringComparison.OrdinalIgnoreCase)) + { pattern += "$"; + } var match = Regex.Match( path, @@ -36,7 +54,9 @@ namespace DotNetCore.CAP.Dashboard RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline); if (match.Success) + { return new Tuple(dispatcher.Item2, match); + } } return null; diff --git a/src/DotNetCore.CAP/Dashboard/RouteCollectionExtensions.cs b/src/DotNetCore.CAP/Dashboard/RouteCollectionExtensions.cs index 393335c..dec40eb 100644 --- a/src/DotNetCore.CAP/Dashboard/RouteCollectionExtensions.cs +++ b/src/DotNetCore.CAP/Dashboard/RouteCollectionExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Text.RegularExpressions; namespace DotNetCore.CAP.Dashboard @@ -10,9 +13,20 @@ namespace DotNetCore.CAP.Dashboard string pathTemplate, Func pageFunc) { - if (routes == null) throw new ArgumentNullException(nameof(routes)); - if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); - if (pageFunc == null) throw new ArgumentNullException(nameof(pageFunc)); + if (routes == null) + { + throw new ArgumentNullException(nameof(routes)); + } + + if (pathTemplate == null) + { + throw new ArgumentNullException(nameof(pathTemplate)); + } + + if (pageFunc == null) + { + throw new ArgumentNullException(nameof(pageFunc)); + } routes.Add(pathTemplate, new RazorPageDispatcher(pageFunc)); } @@ -22,9 +36,20 @@ namespace DotNetCore.CAP.Dashboard string pathTemplate, Func command) { - if (routes == null) throw new ArgumentNullException(nameof(routes)); - if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); - if (command == null) throw new ArgumentNullException(nameof(command)); + if (routes == null) + { + throw new ArgumentNullException(nameof(routes)); + } + + if (pathTemplate == null) + { + throw new ArgumentNullException(nameof(pathTemplate)); + } + + if (command == null) + { + throw new ArgumentNullException(nameof(command)); + } routes.Add(pathTemplate, new CommandDispatcher(command)); } @@ -34,9 +59,20 @@ namespace DotNetCore.CAP.Dashboard string pathTemplate, Func func) { - if (routes == null) throw new ArgumentNullException(nameof(routes)); - if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); - if (func == null) throw new ArgumentNullException(nameof(func)); + if (routes == null) + { + throw new ArgumentNullException(nameof(routes)); + } + + if (pathTemplate == null) + { + throw new ArgumentNullException(nameof(pathTemplate)); + } + + if (func == null) + { + throw new ArgumentNullException(nameof(func)); + } routes.Add(pathTemplate, new JsonDispatcher(func)); } @@ -46,9 +82,20 @@ namespace DotNetCore.CAP.Dashboard string pathTemplate, Func jsonfunc) { - if (routes == null) throw new ArgumentNullException(nameof(routes)); - if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); - if (jsonfunc == null) throw new ArgumentNullException(nameof(jsonfunc)); + if (routes == null) + { + throw new ArgumentNullException(nameof(routes)); + } + + if (pathTemplate == null) + { + throw new ArgumentNullException(nameof(pathTemplate)); + } + + if (jsonfunc == null) + { + throw new ArgumentNullException(nameof(jsonfunc)); + } routes.Add(pathTemplate, new JsonDispatcher(jsonfunc)); } @@ -58,9 +105,20 @@ namespace DotNetCore.CAP.Dashboard string pathTemplate, Action command) { - if (routes == null) throw new ArgumentNullException(nameof(routes)); - if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); - if (command == null) throw new ArgumentNullException(nameof(command)); + if (routes == null) + { + throw new ArgumentNullException(nameof(routes)); + } + + if (pathTemplate == null) + { + throw new ArgumentNullException(nameof(pathTemplate)); + } + + if (command == null) + { + throw new ArgumentNullException(nameof(command)); + } routes.Add(pathTemplate, new BatchCommandDispatcher(command)); } diff --git a/src/DotNetCore.CAP/Dashboard/UrlHelper.cs b/src/DotNetCore.CAP/Dashboard/UrlHelper.cs index 9eb89d1..b3acbfa 100644 --- a/src/DotNetCore.CAP/Dashboard/UrlHelper.cs +++ b/src/DotNetCore.CAP/Dashboard/UrlHelper.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Net; namespace DotNetCore.CAP.Dashboard diff --git a/src/DotNetCore.CAP/Diagnostics/DiagnosticListenerExtensions.cs b/src/DotNetCore.CAP/Diagnostics/DiagnosticListenerExtensions.cs new file mode 100644 index 0000000..da0cc0c --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/DiagnosticListenerExtensions.cs @@ -0,0 +1,234 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using DotNetCore.CAP.Internal; +using DotNetCore.CAP.Models; + +namespace DotNetCore.CAP.Diagnostics +{ + /// + /// Extension methods on the DiagnosticListener class to log CAP data + /// + public static class CapDiagnosticListenerExtensions + { + public const string DiagnosticListenerName = "CapDiagnosticListener"; + + private const string CapPrefix = "DotNetCore.CAP."; + + public const string CapBeforePublishMessageStore = CapPrefix + nameof(WritePublishMessageStoreBefore); + public const string CapAfterPublishMessageStore = CapPrefix + nameof(WritePublishMessageStoreAfter); + public const string CapErrorPublishMessageStore = CapPrefix + nameof(WritePublishMessageStoreError); + + public const string CapBeforePublish = CapPrefix + nameof(WritePublishBefore); + public const string CapAfterPublish = CapPrefix + nameof(WritePublishAfter); + public const string CapErrorPublish = CapPrefix + nameof(WritePublishError); + + public const string CapBeforeConsume = CapPrefix + nameof(WriteConsumeBefore); + public const string CapAfterConsume = CapPrefix + nameof(WriteConsumeAfter); + public const string CapErrorConsume = CapPrefix + nameof(WriteConsumeError); + + public const string CapBeforeSubscriberInvoke = CapPrefix + nameof(WriteSubscriberInvokeBefore); + public const string CapAfterSubscriberInvoke = CapPrefix + nameof(WriteSubscriberInvokeAfter); + public const string CapErrorSubscriberInvoke = CapPrefix + nameof(WriteSubscriberInvokeError); + + + //============================================================================ + //==================== Before publish store message ==================== + //============================================================================ + public static Guid WritePublishMessageStoreBefore(this DiagnosticListener @this, + CapPublishedMessage message, + [CallerMemberName] string operation = "") + { + if (@this.IsEnabled(CapBeforePublishMessageStore)) + { + var operationId = Guid.NewGuid(); + + @this.Write(CapBeforePublishMessageStore, new + { + OperationId = operationId, + Operation = operation, + MessageName = message.Name, + MessageContent = message.Content + }); + + return operationId; + } + + return Guid.Empty; + } + + public static void WritePublishMessageStoreAfter(this DiagnosticListener @this, + Guid operationId, + CapPublishedMessage message, + [CallerMemberName] string operation = "") + { + if (@this.IsEnabled(CapAfterPublishMessageStore)) + { + @this.Write(CapAfterPublishMessageStore, new + { + OperationId = operationId, + Operation = operation, + MessageId = message.Id, + MessageName = message.Name, + MessageContent = message.Content, + Timestamp = Stopwatch.GetTimestamp() + }); + } + } + + public static void WritePublishMessageStoreError(this DiagnosticListener @this, + Guid operationId, + CapPublishedMessage message, + Exception ex, + [CallerMemberName] string operation = "") + { + if (@this.IsEnabled(CapErrorPublishMessageStore)) + { + @this.Write(CapErrorPublishMessageStore, new + { + OperationId = operationId, + Operation = operation, + MessageName = message.Name, + MessageContent = message.Content, + Exception = ex, + Timestamp = Stopwatch.GetTimestamp() + }); + } + } + + + //============================================================================ + //==================== Publish ==================== + //============================================================================ + public static void WritePublishBefore(this DiagnosticListener @this, BrokerPublishEventData eventData) + { + if (@this.IsEnabled(CapBeforePublish)) + { + eventData.Headers = new TracingHeaders(); + @this.Write(CapBeforePublish, eventData); + } + } + + public static void WritePublishAfter(this DiagnosticListener @this, BrokerPublishEndEventData eventData) + { + if (@this.IsEnabled(CapAfterPublish)) + { + eventData.Headers = new TracingHeaders(); + @this.Write(CapAfterPublish, eventData); + } + } + + public static void WritePublishError(this DiagnosticListener @this, BrokerPublishErrorEventData eventData) + { + if (@this.IsEnabled(CapErrorPublish)) + { + eventData.Headers = new TracingHeaders(); + @this.Write(CapErrorPublish, eventData); + } + } + + + //============================================================================ + //==================== Consume ==================== + //============================================================================ + public static Guid WriteConsumeBefore(this DiagnosticListener @this, BrokerConsumeEventData eventData) + { + if (@this.IsEnabled(CapBeforeConsume)) + { + eventData.Headers = new TracingHeaders(); + @this.Write(CapBeforeConsume, eventData); + } + + return Guid.Empty; + } + + public static void WriteConsumeAfter(this DiagnosticListener @this, BrokerConsumeEndEventData eventData) + { + if (@this.IsEnabled(CapAfterConsume)) + { + eventData.Headers = new TracingHeaders(); + @this.Write(CapAfterConsume, eventData); + } + } + + public static void WriteConsumeError(this DiagnosticListener @this, BrokerConsumeErrorEventData eventData) + { + if (@this.IsEnabled(CapErrorConsume)) + { + eventData.Headers = new TracingHeaders(); + @this.Write(CapErrorConsume, eventData); + } + } + + + //============================================================================ + //==================== SubscriberInvoke ==================== + //============================================================================ + public static Guid WriteSubscriberInvokeBefore(this DiagnosticListener @this, + ConsumerContext context, + [CallerMemberName] string operation = "") + { + if (@this.IsEnabled(CapBeforeSubscriberInvoke)) + { + var operationId = Guid.NewGuid(); + + var methodName = context.ConsumerDescriptor.MethodInfo.Name; + var subscribeName = context.ConsumerDescriptor.Attribute.Name; + var subscribeGroup = context.ConsumerDescriptor.Attribute.Group; + var parameterValues = context.DeliverMessage.Content; + + @this.Write(CapBeforeSubscriberInvoke, new SubscriberInvokeEventData(operationId, operation, methodName, + subscribeName, + subscribeGroup, parameterValues, DateTimeOffset.UtcNow)); + + return operationId; + } + + return Guid.Empty; + } + + public static void WriteSubscriberInvokeAfter(this DiagnosticListener @this, + Guid operationId, + ConsumerContext context, + DateTimeOffset startTime, + TimeSpan duration, + [CallerMemberName] string operation = "") + { + if (@this.IsEnabled(CapAfterSubscriberInvoke)) + { + var methodName = context.ConsumerDescriptor.MethodInfo.Name; + var subscribeName = context.ConsumerDescriptor.Attribute.Name; + var subscribeGroup = context.ConsumerDescriptor.Attribute.Group; + var parameterValues = context.DeliverMessage.Content; + + @this.Write(CapAfterSubscriberInvoke, new SubscriberInvokeEndEventData(operationId, operation, methodName, + subscribeName, + subscribeGroup, parameterValues, startTime, duration)); + } + } + + public static void WriteSubscriberInvokeError(this DiagnosticListener @this, + Guid operationId, + ConsumerContext context, + Exception ex, + DateTimeOffset startTime, + TimeSpan duration, + [CallerMemberName] string operation = "") + { + if (@this.IsEnabled(CapErrorSubscriberInvoke)) + { + var methodName = context.ConsumerDescriptor.MethodInfo.Name; + var subscribeName = context.ConsumerDescriptor.Attribute.Name; + var subscribeGroup = context.ConsumerDescriptor.Attribute.Group; + var parameterValues = context.DeliverMessage.Content; + + @this.Write(CapErrorSubscriberInvoke, new SubscriberInvokeErrorEventData(operationId, operation, methodName, + subscribeName, + subscribeGroup, parameterValues, ex, startTime, duration)); + } + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.Broker.Consume.cs b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.Consume.cs new file mode 100644 index 0000000..b24b01a --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.Consume.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class BrokerConsumeEventData : BrokerEventData + { + public BrokerConsumeEventData(Guid operationId, string operation, string brokerAddress, + string brokerTopicName, string brokerTopicBody, DateTimeOffset startTime) + : base(operationId, operation, brokerAddress, brokerTopicName, brokerTopicBody) + { + StartTime = startTime; + } + + public DateTimeOffset StartTime { get; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.Broker.ConsumeEnd.cs b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.ConsumeEnd.cs new file mode 100644 index 0000000..013eace --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.ConsumeEnd.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class BrokerConsumeEndEventData : BrokerConsumeEventData + { + public BrokerConsumeEndEventData(Guid operationId, string operation, string brokerAddress, + string brokerTopicName, + string brokerTopicBody, DateTimeOffset startTime, TimeSpan duration) + : base(operationId, operation, brokerAddress, brokerTopicName, brokerTopicBody, startTime) + { + Duration = duration; + } + + public TimeSpan Duration { get; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.Broker.ConsumeError.cs b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.ConsumeError.cs new file mode 100644 index 0000000..36af350 --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.ConsumeError.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class BrokerConsumeErrorEventData : BrokerConsumeEndEventData, IErrorEventData + { + public BrokerConsumeErrorEventData(Guid operationId, string operation, string brokerAddress, + string brokerTopicName, string brokerTopicBody, Exception exception, DateTimeOffset startTime, + TimeSpan duration) + : base(operationId, operation, brokerAddress, brokerTopicName, brokerTopicBody, startTime, duration) + { + Exception = exception; + } + + public Exception Exception { get; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.Broker.Publish.cs b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.Publish.cs new file mode 100644 index 0000000..320f956 --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.Publish.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class BrokerPublishEventData : BrokerEventData + { + public BrokerPublishEventData(Guid operationId, string operation, string brokerAddress, + string brokerTopicName, string brokerTopicBody, DateTimeOffset startTime) + : base(operationId, operation, brokerAddress, brokerTopicName, brokerTopicBody) + { + StartTime = startTime; + } + + public DateTimeOffset StartTime { get; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.Broker.PublishEnd.cs b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.PublishEnd.cs new file mode 100644 index 0000000..bc3d0cb --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.PublishEnd.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class BrokerPublishEndEventData : BrokerPublishEventData + { + public BrokerPublishEndEventData(Guid operationId, string operation, string brokerAddress, + string brokerTopicName, + string brokerTopicBody, DateTimeOffset startTime, TimeSpan duration) + : base(operationId, operation, brokerAddress, brokerTopicName, brokerTopicBody, startTime) + { + Duration = duration; + } + + public TimeSpan Duration { get; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.Broker.PublishError.cs b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.PublishError.cs new file mode 100644 index 0000000..ec44e3b --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.PublishError.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class BrokerPublishErrorEventData : BrokerPublishEndEventData, IErrorEventData + { + public BrokerPublishErrorEventData(Guid operationId, string operation, string brokerAddress, + string brokerTopicName, string brokerTopicBody, Exception exception, DateTimeOffset startTime, + TimeSpan duration) + : base(operationId, operation, brokerAddress, brokerTopicName, brokerTopicBody, startTime, duration) + { + Exception = exception; + } + + public Exception Exception { get; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.Broker.cs b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.cs new file mode 100644 index 0000000..2db1794 --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.Broker.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class BrokerEventData : EventData + { + public BrokerEventData(Guid operationId, string operation, string brokerAddress, + string brokerTopicName, string brokerTopicBody) + : base(operationId, operation) + { + BrokerAddress = brokerAddress; + BrokerTopicName = brokerTopicName; + BrokerTopicBody = brokerTopicBody; + } + + public TracingHeaders Headers { get; set; } + + public string BrokerAddress { get; set; } + + public string BrokerTopicBody { get; set; } + + public string BrokerTopicName { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.SubscriberInvoke.cs b/src/DotNetCore.CAP/Diagnostics/EventData.SubscriberInvoke.cs new file mode 100644 index 0000000..2302eec --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.SubscriberInvoke.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class SubscriberInvokeEventData : EventData + { + public SubscriberInvokeEventData(Guid operationId, + string operation, + string methodName, + string subscribeName, + string subscribeGroup, + string parameterValues, + DateTimeOffset startTime) + : base(operationId, operation) + { + MethodName = methodName; + SubscribeName = subscribeName; + SubscribeGroup = subscribeGroup; + ParameterValues = parameterValues; + StartTime = startTime; + } + + public DateTimeOffset StartTime { get; } + + public string MethodName { get; set; } + + public string SubscribeName { get; set; } + + public string SubscribeGroup { get; set; } + + public string ParameterValues { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.SubscriberInvokeEnd.cs b/src/DotNetCore.CAP/Diagnostics/EventData.SubscriberInvokeEnd.cs new file mode 100644 index 0000000..2a24ca1 --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.SubscriberInvokeEnd.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class SubscriberInvokeEndEventData : SubscriberInvokeEventData + { + public SubscriberInvokeEndEventData(Guid operationId, string operation, + string methodName, string subscribeName, string subscribeGroup, + string parameterValues, DateTimeOffset startTime, TimeSpan duration) + : base(operationId, operation, methodName, subscribeName, subscribeGroup, parameterValues, startTime) + { + Duration = duration; + } + + public TimeSpan Duration { get; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.SubscriberInvokeError.cs b/src/DotNetCore.CAP/Diagnostics/EventData.SubscriberInvokeError.cs new file mode 100644 index 0000000..005f70f --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.SubscriberInvokeError.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class SubscriberInvokeErrorEventData : SubscriberInvokeEndEventData, IErrorEventData + { + public SubscriberInvokeErrorEventData(Guid operationId, string operation, string methodName, + string subscribeName, string subscribeGroup, string parameterValues, Exception exception, + DateTimeOffset startTime, TimeSpan duration) : base(operationId, operation, methodName, subscribeName, + subscribeGroup, parameterValues, startTime, duration) + { + Exception = exception; + } + + public Exception Exception { get; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/EventData.cs b/src/DotNetCore.CAP/Diagnostics/EventData.cs new file mode 100644 index 0000000..b773780 --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/EventData.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public class EventData + { + public EventData(Guid operationId, string operation) + { + OperationId = operationId; + Operation = operation; + } + + public Guid OperationId { get; set; } + + public string Operation { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/IErrorEventData.cs b/src/DotNetCore.CAP/Diagnostics/IErrorEventData.cs new file mode 100644 index 0000000..e0217c6 --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/IErrorEventData.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Diagnostics +{ + public interface IErrorEventData + { + Exception Exception { get; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Diagnostics/TracingHeaders.cs b/src/DotNetCore.CAP/Diagnostics/TracingHeaders.cs new file mode 100644 index 0000000..11093d4 --- /dev/null +++ b/src/DotNetCore.CAP/Diagnostics/TracingHeaders.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace DotNetCore.CAP.Diagnostics +{ + public class TracingHeaders : IEnumerable> + { + private List> _dataStore; + + public IEnumerator> GetEnumerator() + { + return _dataStore.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(string name, string value) + { + if (_dataStore == null) + { + _dataStore = new List>(); + } + + _dataStore.Add(new KeyValuePair(name, value)); + } + + public bool Contains(string name) + { + return _dataStore != null && _dataStore.Any(x => x.Key == name); + } + + public void Remove(string name) + { + _dataStore?.RemoveAll(x => x.Key == name); + } + + public void Cleaar() + { + _dataStore?.Clear(); + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/DotNetCore.CAP.csproj b/src/DotNetCore.CAP/DotNetCore.CAP.csproj index c3f3786..464a2a5 100644 --- a/src/DotNetCore.CAP/DotNetCore.CAP.csproj +++ b/src/DotNetCore.CAP/DotNetCore.CAP.csproj @@ -5,8 +5,8 @@ DotNetCore.CAP $(PackageTags); - - bin\Debug\netstandard2.0\DotNetCore.CAP.xml + + bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.xml 1701;1702;1705;CS1591 @@ -48,13 +48,14 @@ - - - - - - + + + + + + + @@ -142,4 +143,9 @@ Strings.Designer.cs + + + RazorGenerator + + \ No newline at end of file diff --git a/src/DotNetCore.CAP/IBootstrapper.Default.cs b/src/DotNetCore.CAP/IBootstrapper.Default.cs index 64f298c..9af8f2e 100644 --- a/src/DotNetCore.CAP/IBootstrapper.Default.cs +++ b/src/DotNetCore.CAP/IBootstrapper.Default.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -19,10 +22,6 @@ namespace DotNetCore.CAP private readonly ILogger _logger; private Task _bootstrappingTask; - private IStorage Storage { get; } - - private IEnumerable Processors { get; } - public DefaultBootstrapper( ILogger logger, IStorage storage, @@ -49,6 +48,10 @@ namespace DotNetCore.CAP }); } + private IStorage Storage { get; } + + private IEnumerable Processors { get; } + public Task BootstrapAsync() { return _bootstrappingTask = BootstrapTaskAsync(); @@ -56,27 +59,40 @@ namespace DotNetCore.CAP private async Task BootstrapTaskAsync() { + _logger.LogInformation("### CAP starting..."); + await Storage.InitializeAsync(_cts.Token); - if (_cts.IsCancellationRequested) return; + if (_cts.IsCancellationRequested) + { + return; + } _appLifetime.ApplicationStopping.Register(() => { foreach (var item in Processors) + { item.Dispose(); + } }); - if (_cts.IsCancellationRequested) return; + if (_cts.IsCancellationRequested) + { + return; + } await BootstrapCoreAsync(); _ctsRegistration.Dispose(); _cts.Dispose(); + + _logger.LogInformation("### CAP started!"); } protected virtual Task BootstrapCoreAsync() { foreach (var item in Processors) + { try { item.Start(); @@ -85,6 +101,8 @@ namespace DotNetCore.CAP { _logger.ProcessorsStartedError(ex); } + } + return Task.CompletedTask; } } diff --git a/src/DotNetCore.CAP/IBootstrapper.cs b/src/DotNetCore.CAP/IBootstrapper.cs index aacd1dc..9da1627 100644 --- a/src/DotNetCore.CAP/IBootstrapper.cs +++ b/src/DotNetCore.CAP/IBootstrapper.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; namespace DotNetCore.CAP { diff --git a/src/DotNetCore.CAP/ICallbackPublisher.cs b/src/DotNetCore.CAP/ICallbackPublisher.cs index 37170b9..0046e94 100644 --- a/src/DotNetCore.CAP/ICallbackPublisher.cs +++ b/src/DotNetCore.CAP/ICallbackPublisher.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; using DotNetCore.CAP.Models; namespace DotNetCore.CAP @@ -11,6 +14,6 @@ namespace DotNetCore.CAP /// /// Publish a callback message /// - Task PublishAsync(CapPublishedMessage obj); + Task PublishCallbackAsync(CapPublishedMessage obj); } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/ICapOptionsExtension.cs b/src/DotNetCore.CAP/ICapOptionsExtension.cs index cc467b8..f6c2fc4 100644 --- a/src/DotNetCore.CAP/ICapOptionsExtension.cs +++ b/src/DotNetCore.CAP/ICapOptionsExtension.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Extensions.DependencyInjection; namespace DotNetCore.CAP { @@ -10,7 +13,7 @@ namespace DotNetCore.CAP /// /// Registered child service. /// - /// add service to the + /// add service to the void AddServices(IServiceCollection services); } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/ICapPublisher.cs b/src/DotNetCore.CAP/ICapPublisher.cs index 9d0fd2f..0688c02 100644 --- a/src/DotNetCore.CAP/ICapPublisher.cs +++ b/src/DotNetCore.CAP/ICapPublisher.cs @@ -1,4 +1,7 @@ -using System.Data; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Data; using System.Threading.Tasks; namespace DotNetCore.CAP @@ -12,7 +15,7 @@ namespace DotNetCore.CAP /// (EntityFramework) Asynchronous publish a object message. /// /// If you are using the EntityFramework, you need to configure the DbContextType first. - /// otherwise you need to use overloaded method with IDbConnection and IDbTransaction. + /// otherwise you need to use overloaded method with IDbTransaction. /// ///
/// The type of content object. @@ -25,7 +28,7 @@ namespace DotNetCore.CAP /// (EntityFramework) Publish a object message. /// /// If you are using the EntityFramework, you need to configure the DbContextType first. - /// otherwise you need to use overloaded method with IDbConnection and IDbTransaction. + /// otherwise you need to use overloaded method with IDbTransaction. /// /// /// The type of content object. diff --git a/src/DotNetCore.CAP/ICapSubscribe.cs b/src/DotNetCore.CAP/ICapSubscribe.cs index b358609..e223b84 100644 --- a/src/DotNetCore.CAP/ICapSubscribe.cs +++ b/src/DotNetCore.CAP/ICapSubscribe.cs @@ -1,7 +1,10 @@ -namespace DotNetCore.CAP +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP { /// - /// An empty interface, which is used to mark the current class have a CAP methods. + /// An empty interface, which is used to mark the current class have a CAP subscriber methods. /// public interface ICapSubscribe { diff --git a/src/DotNetCore.CAP/IConsumerClient.cs b/src/DotNetCore.CAP/IConsumerClient.cs index 4dd3143..150bc4c 100644 --- a/src/DotNetCore.CAP/IConsumerClient.cs +++ b/src/DotNetCore.CAP/IConsumerClient.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Threading; @@ -10,6 +13,8 @@ namespace DotNetCore.CAP /// public interface IConsumerClient : IDisposable { + string ServersAddress { get; } + /// /// Subscribe to a set of topics to the message queue /// diff --git a/src/DotNetCore.CAP/IConsumerClientFactory.cs b/src/DotNetCore.CAP/IConsumerClientFactory.cs index 94b9cae..a5ce33f 100644 --- a/src/DotNetCore.CAP/IConsumerClientFactory.cs +++ b/src/DotNetCore.CAP/IConsumerClientFactory.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP { /// /// Consumer client factory to create consumer client instance. diff --git a/src/DotNetCore.CAP/IConsumerHandler.Default.cs b/src/DotNetCore.CAP/IConsumerHandler.Default.cs index 645a121..e2179ff 100644 --- a/src/DotNetCore.CAP/IConsumerHandler.Default.cs +++ b/src/DotNetCore.CAP/IConsumerHandler.Default.cs @@ -1,41 +1,49 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using DotNetCore.CAP.Diagnostics; using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Internal; using DotNetCore.CAP.Models; -using DotNetCore.CAP.Processor; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace DotNetCore.CAP { internal class ConsumerHandler : IConsumerHandler { + private readonly IStorageConnection _connection; private readonly IConsumerClientFactory _consumerClientFactory; - private readonly CancellationTokenSource _cts; + private readonly IDispatcher _dispatcher; private readonly ILogger _logger; - private readonly TimeSpan _pollingDelay = TimeSpan.FromSeconds(1); private readonly MethodMatcherCache _selector; - private readonly IServiceProvider _serviceProvider; + private string _serverAddress; private Task _compositeTask; - private bool _disposed; - public ConsumerHandler( - IServiceProvider serviceProvider, - IConsumerClientFactory consumerClientFactory, + // diagnostics listener + // ReSharper disable once InconsistentNaming + private static readonly DiagnosticListener s_diagnosticListener = + new DiagnosticListener(CapDiagnosticListenerExtensions.DiagnosticListenerName); + + public ConsumerHandler(IConsumerClientFactory consumerClientFactory, + IDispatcher dispatcher, + IStorageConnection connection, ILogger logger, MethodMatcherCache selector) { _selector = selector; _logger = logger; - _serviceProvider = serviceProvider; _consumerClientFactory = consumerClientFactory; + _dispatcher = dispatcher; + _connection = connection; _cts = new CancellationTokenSource(); } @@ -44,25 +52,32 @@ namespace DotNetCore.CAP var groupingMatches = _selector.GetCandidatesMethodsOfGroupNameGrouped(); foreach (var matchGroup in groupingMatches) + { Task.Factory.StartNew(() => - { - using (var client = _consumerClientFactory.Create(matchGroup.Key)) - { - RegisterMessageProcessor(client); + { + using (var client = _consumerClientFactory.Create(matchGroup.Key)) + { + _serverAddress = client.ServersAddress; + + RegisterMessageProcessor(client); - client.Subscribe(matchGroup.Value.Select(x => x.Attribute.Name)); + client.Subscribe(matchGroup.Value.Select(x => x.Attribute.Name)); - client.Listening(_pollingDelay, _cts.Token); - } - }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + client.Listening(_pollingDelay, _cts.Token); + } + }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } - _compositeTask = Task.CompletedTask; + _compositeTask = Task.CompletedTask; } public void Dispose() { if (_disposed) + { return; + } + _disposed = true; _cts.Cancel(); try @@ -73,35 +88,54 @@ namespace DotNetCore.CAP { var innerEx = ex.InnerExceptions[0]; if (!(innerEx is OperationCanceledException)) + { _logger.ExpectedOperationCanceledException(innerEx); + } } } public void Pulse() { - SubscribeQueuer.PulseEvent.Set(); + //ignore } private void RegisterMessageProcessor(IConsumerClient client) { - client.OnMessageReceived += (sender, message) => + client.OnMessageReceived += (sender, messageContext) => { - _logger.EnqueuingReceivedMessage(message.Name, message.Content); + var startTime = DateTimeOffset.UtcNow; + var stopwatch = Stopwatch.StartNew(); + + var tracingResult = TracingBefore(messageContext.Name, messageContext.Content); + var operationId = tracingResult.Item1; + var messageBody = tracingResult.Item2; - using (var scope = _serviceProvider.CreateScope()) + var receivedMessage = new CapReceivedMessage(messageContext) { - try - { - StoreMessage(scope, message); - client.Commit(); - } - catch (Exception e) - { - _logger.LogError(e, "An exception occurred when storage received message. Message:'{0}'.", message); - client.Reject(); - } + StatusName = StatusName.Scheduled, + Content = messageBody + }; + + try + { + StoreMessage(receivedMessage); + + client.Commit(); + + TracingAfter(operationId, receivedMessage.Name, receivedMessage.Content, startTime, + stopwatch.Elapsed); + + _dispatcher.EnqueueToExecute(receivedMessage); + } + catch (Exception e) + { + _logger.LogError(e, "An exception occurred when storage received message. Message:'{0}'.", messageContext); + + client.Reject(); + + TracingError(operationId, receivedMessage.Name, receivedMessage.Content, e, startTime, + stopwatch.Elapsed); } - Pulse(); }; client.OnLog += WriteLog; @@ -112,37 +146,81 @@ namespace DotNetCore.CAP switch (logmsg.LogType) { case MqLogType.ConsumerCancelled: - _logger.LogWarning("RabbitMQ consumer cancelled. reason: " + logmsg.Reason); + _logger.LogWarning("RabbitMQ consumer cancelled. --> " + logmsg.Reason); break; case MqLogType.ConsumerRegistered: - _logger.LogInformation("RabbitMQ consumer registered. " + logmsg.Reason); + _logger.LogInformation("RabbitMQ consumer registered. --> " + logmsg.Reason); break; case MqLogType.ConsumerUnregistered: - _logger.LogWarning("RabbitMQ consumer unregistered. reason: " + logmsg.Reason); + _logger.LogWarning("RabbitMQ consumer unregistered. --> " + logmsg.Reason); break; case MqLogType.ConsumerShutdown: - _logger.LogWarning("RabbitMQ consumer shutdown. reason:" + logmsg.Reason); + _logger.LogWarning("RabbitMQ consumer shutdown. --> " + logmsg.Reason); break; case MqLogType.ConsumeError: - _logger.LogError("Kakfa client consume error. reason:" + logmsg.Reason); + _logger.LogError("Kakfa client consume error. --> " + logmsg.Reason); break; case MqLogType.ServerConnError: - _logger.LogCritical("Kafka server connection error. reason:" + logmsg.Reason); + _logger.LogCritical("Kafka server connection error. --> " + logmsg.Reason); break; default: throw new ArgumentOutOfRangeException(); } } - private static void StoreMessage(IServiceScope serviceScope, MessageContext messageContext) + private void StoreMessage(CapReceivedMessage receivedMessage) { - var provider = serviceScope.ServiceProvider; - var messageStore = provider.GetRequiredService(); - var receivedMessage = new CapReceivedMessage(messageContext) - { - StatusName = StatusName.Scheduled - }; - messageStore.StoreReceivedMessageAsync(receivedMessage).GetAwaiter().GetResult(); + var id = _connection.StoreReceivedMessageAsync(receivedMessage) + .GetAwaiter().GetResult(); + + receivedMessage.Id = id; + } + + private (Guid, string) TracingBefore(string topic, string values) + { + _logger.LogDebug("CAP received topic message:" + topic); + + Guid operationId = Guid.NewGuid(); + + var eventData = new BrokerConsumeEventData( + operationId, "", + _serverAddress, + topic, + values, + DateTimeOffset.UtcNow); + + s_diagnosticListener.WriteConsumeBefore(eventData); + + return (operationId, eventData.BrokerTopicBody); + } + + private void TracingAfter(Guid operationId, string topic, string values, DateTimeOffset startTime, TimeSpan du) + { + var eventData = new BrokerConsumeEndEventData( + operationId, + "", + _serverAddress, + topic, + values, + startTime, + du); + + s_diagnosticListener.WriteConsumeAfter(eventData); + } + + private void TracingError(Guid operationId, string topic, string values, Exception ex, DateTimeOffset startTime, TimeSpan du) + { + var eventData = new BrokerConsumeErrorEventData( + operationId, + "", + _serverAddress, + topic, + values, + ex, + startTime, + du); + + s_diagnosticListener.WriteConsumeError(eventData); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/IConsumerHandler.cs b/src/DotNetCore.CAP/IConsumerHandler.cs index f9e7c6c..86588e2 100644 --- a/src/DotNetCore.CAP/IConsumerHandler.cs +++ b/src/DotNetCore.CAP/IConsumerHandler.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP { /// /// Handler received message of subscribed. diff --git a/src/DotNetCore.CAP/IDispatcher.cs b/src/DotNetCore.CAP/IDispatcher.cs new file mode 100644 index 0000000..e8553ea --- /dev/null +++ b/src/DotNetCore.CAP/IDispatcher.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using DotNetCore.CAP.Models; + +namespace DotNetCore.CAP +{ + public interface IDispatcher + { + void EnqueueToPublish(CapPublishedMessage message); + + void EnqueueToExecute(CapReceivedMessage message); + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/IFetchedMessage.cs b/src/DotNetCore.CAP/IFetchedMessage.cs deleted file mode 100644 index ca7acee..0000000 --- a/src/DotNetCore.CAP/IFetchedMessage.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP -{ - public interface IFetchedMessage : IDisposable - { - int MessageId { get; } - - MessageType MessageType { get; } - - void RemoveFromQueue(); - - void Requeue(); - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/IProcessingServer.cs b/src/DotNetCore.CAP/IProcessingServer.cs index 5369800..b7baa9a 100644 --- a/src/DotNetCore.CAP/IProcessingServer.cs +++ b/src/DotNetCore.CAP/IProcessingServer.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP { diff --git a/src/DotNetCore.CAP/IPublishExecutor.cs b/src/DotNetCore.CAP/IPublishExecutor.cs index 08245de..3997273 100644 --- a/src/DotNetCore.CAP/IPublishExecutor.cs +++ b/src/DotNetCore.CAP/IPublishExecutor.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; namespace DotNetCore.CAP { @@ -15,4 +18,4 @@ namespace DotNetCore.CAP /// Task PublishAsync(string keyName, string content); } -} +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/IPublishMessageSender.Base.cs b/src/DotNetCore.CAP/IPublishMessageSender.Base.cs new file mode 100644 index 0000000..a597169 --- /dev/null +++ b/src/DotNetCore.CAP/IPublishMessageSender.Base.cs @@ -0,0 +1,177 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using DotNetCore.CAP.Diagnostics; +using DotNetCore.CAP.Infrastructure; +using DotNetCore.CAP.Internal; +using DotNetCore.CAP.Models; +using DotNetCore.CAP.Processor; +using DotNetCore.CAP.Processor.States; +using Microsoft.Extensions.Logging; + +namespace DotNetCore.CAP +{ + public abstract class BasePublishMessageSender : IPublishMessageSender, IPublishExecutor + { + private readonly IStorageConnection _connection; + private readonly ILogger _logger; + private readonly CapOptions _options; + private readonly IStateChanger _stateChanger; + + protected string ServersAddress { get; set; } + + // diagnostics listener + // ReSharper disable once InconsistentNaming + protected static readonly DiagnosticListener s_diagnosticListener = + new DiagnosticListener(CapDiagnosticListenerExtensions.DiagnosticListenerName); + + protected BasePublishMessageSender( + ILogger logger, + CapOptions options, + IStorageConnection connection, + IStateChanger stateChanger) + { + _options = options; + _connection = connection; + _stateChanger = stateChanger; + _logger = logger; + } + + public abstract Task PublishAsync(string keyName, string content); + + public async Task SendAsync(CapPublishedMessage message) + { + var startTime = DateTimeOffset.UtcNow; + var stopwatch = Stopwatch.StartNew(); + + var tracingResult = TracingBefore(message.Name, message.Content); + var operationId = tracingResult.Item1; + + var sendValues = tracingResult.Item2 != null + ? Helper.AddTracingHeaderProperty(message.Content, tracingResult.Item2) + : message.Content; + + var result = await PublishAsync(message.Name, sendValues); + + stopwatch.Stop(); + if (result.Succeeded) + { + await SetSuccessfulState(message); + + TracingAfter(operationId, message.Name, sendValues, startTime, stopwatch.Elapsed); + + return OperateResult.Success; + } + else + { + TracingError(operationId, message, result, startTime, stopwatch.Elapsed); + + await SetFailedState(message, result.Exception, out bool stillRetry); + + if (stillRetry) + { + _logger.SenderRetrying(message.Id, message.Retries); + + await SendAsync(message); + } + return OperateResult.Failed(result.Exception); + } + } + + private static bool UpdateMessageForRetryAsync(CapPublishedMessage message) + { + var retryBehavior = RetryBehavior.DefaultRetry; + + var retries = ++message.Retries; + if (retries >= retryBehavior.RetryCount) + { + return false; + } + + var due = message.Added.AddSeconds(retryBehavior.RetryIn(retries)); + message.ExpiresAt = due; + + return true; + } + + private Task SetSuccessfulState(CapPublishedMessage message) + { + var succeededState = new SucceededState(_options.SucceedMessageExpiredAfter); + + return _stateChanger.ChangeStateAsync(message, succeededState, _connection); + } + + private Task SetFailedState(CapPublishedMessage message, Exception ex, out bool stillRetry) + { + IState newState = new FailedState(); + stillRetry = UpdateMessageForRetryAsync(message); + if (stillRetry) + { + _logger.ConsumerExecutionFailedWillRetry(ex); + return Task.CompletedTask; + } + + AddErrorReasonToContent(message, ex); + + return _stateChanger.ChangeStateAsync(message, newState, _connection); + } + + private static void AddErrorReasonToContent(CapPublishedMessage message, Exception exception) + { + message.Content = Helper.AddExceptionProperty(message.Content, exception); + } + + private (Guid, TracingHeaders) TracingBefore(string topic, string values) + { + Guid operationId = Guid.NewGuid(); + + var eventData = new BrokerPublishEventData( + operationId, "", + ServersAddress, topic, + values, + DateTimeOffset.UtcNow); + + s_diagnosticListener.WritePublishBefore(eventData); + + return (operationId, eventData.Headers); //if not enabled diagnostics ,the header will be null + } + + private void TracingAfter(Guid operationId, string topic, string values, DateTimeOffset startTime, TimeSpan du) + { + var eventData = new BrokerPublishEndEventData( + operationId, + "", + ServersAddress, + topic, + values, + startTime, + du); + + s_diagnosticListener.WritePublishAfter(eventData); + + _logger.MessageHasBeenSent(du.TotalSeconds); + } + + private void TracingError(Guid operationId, CapPublishedMessage message, OperateResult result, DateTimeOffset startTime, TimeSpan du) + { + var ex = new PublisherSentFailedException(result.ToString(), result.Exception); + + _logger.MessagePublishException(message.Id, result.ToString(), ex); + + var eventData = new BrokerPublishErrorEventData( + operationId, + "", + ServersAddress, + message.Name, + message.Content, + ex, + startTime, + du); + + s_diagnosticListener.WritePublishError(eventData); + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/IPublishMessageSender.cs b/src/DotNetCore.CAP/IPublishMessageSender.cs new file mode 100644 index 0000000..c99afd8 --- /dev/null +++ b/src/DotNetCore.CAP/IPublishMessageSender.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using DotNetCore.CAP.Models; + +namespace DotNetCore.CAP +{ + public interface IPublishMessageSender + { + Task SendAsync(CapPublishedMessage message); + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs b/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs deleted file mode 100644 index 194e595..0000000 --- a/src/DotNetCore.CAP/IQueueExecutor.Publish.Base.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using DotNetCore.CAP.Infrastructure; -using DotNetCore.CAP.Models; -using DotNetCore.CAP.Processor; -using DotNetCore.CAP.Processor.States; -using Microsoft.Extensions.Logging; - -namespace DotNetCore.CAP -{ - public abstract class BasePublishQueueExecutor : IQueueExecutor, IPublishExecutor - { - private readonly ILogger _logger; - private readonly CapOptions _options; - private readonly IStateChanger _stateChanger; - - protected BasePublishQueueExecutor( - CapOptions options, - IStateChanger stateChanger, - ILogger logger) - { - _options = options; - _stateChanger = stateChanger; - _logger = logger; - } - - public async Task ExecuteAsync(IStorageConnection connection, IFetchedMessage fetched) - { - var message = await connection.GetPublishedMessageAsync(fetched.MessageId); - try - { - var sp = Stopwatch.StartNew(); - await _stateChanger.ChangeStateAsync(message, new ProcessingState(), connection); - - if (message.Retries > 0) - _logger.JobRetrying(message.Retries); - var result = await PublishAsync(message.Name, message.Content); - sp.Stop(); - - IState newState; - if (!result.Succeeded) - { - var shouldRetry = UpdateMessageForRetryAsync(message); - if (shouldRetry) - { - newState = new ScheduledState(); - _logger.JobFailedWillRetry(result.Exception); - } - else - { - newState = new FailedState(); - _logger.JobFailed(result.Exception); - } - message.Content = Helper.AddExceptionProperty(message.Content, result.Exception); - } - else - { - newState = new SucceededState(_options.SucceedMessageExpiredAfter); - } - await _stateChanger.ChangeStateAsync(message, newState, connection); - - fetched.RemoveFromQueue(); - - if (result.Succeeded) - _logger.JobExecuted(sp.Elapsed.TotalSeconds); - - return OperateResult.Success; - } - catch (Exception ex) - { - fetched.Requeue(); - _logger.ExceptionOccuredWhileExecuting(message?.Name, ex); - return OperateResult.Failed(ex); - } - } - - public abstract Task PublishAsync(string keyName, string content); - - private static bool UpdateMessageForRetryAsync(CapPublishedMessage message) - { - var retryBehavior = RetryBehavior.DefaultRetry; - - var retries = ++message.Retries; - if (retries >= retryBehavior.RetryCount) - return false; - - var due = message.Added.AddSeconds(retryBehavior.RetryIn(retries)); - message.ExpiresAt = due; - - return true; - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/IQueueExecutor.Subscribe.cs b/src/DotNetCore.CAP/IQueueExecutor.Subscribe.cs deleted file mode 100644 index 7435b4b..0000000 --- a/src/DotNetCore.CAP/IQueueExecutor.Subscribe.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using DotNetCore.CAP.Abstractions; -using DotNetCore.CAP.Infrastructure; -using DotNetCore.CAP.Internal; -using DotNetCore.CAP.Models; -using DotNetCore.CAP.Processor; -using DotNetCore.CAP.Processor.States; -using Microsoft.Extensions.Logging; - -namespace DotNetCore.CAP -{ - public class SubscribeQueueExecutor : IQueueExecutor - { - private readonly ILogger _logger; - private readonly CapOptions _options; - private readonly IStateChanger _stateChanger; - private readonly ISubscriberExecutor _subscriberExecutor; - - public SubscribeQueueExecutor( - CapOptions options, - IStateChanger stateChanger, - ISubscriberExecutor subscriberExecutor, - ILogger logger) - { - _options = options; - _subscriberExecutor = subscriberExecutor; - _stateChanger = stateChanger; - _logger = logger; - } - - public async Task ExecuteAsync(IStorageConnection connection, IFetchedMessage fetched) - { - var message = await connection.GetReceivedMessageAsync(fetched.MessageId); - - if (message == null) - { - _logger.LogError($"Can not find mesage at cap received message table, message id:{fetched.MessageId} !!!"); - return OperateResult.Failed(); - } - - try - { - var sp = Stopwatch.StartNew(); - await _stateChanger.ChangeStateAsync(message, new ProcessingState(), connection); - - if (message.Retries > 0) - _logger.JobRetrying(message.Retries); - - var result = await _subscriberExecutor.ExecuteAsync(message); - sp.Stop(); - - var state = GetNewState(result, message); - - await _stateChanger.ChangeStateAsync(message, state, connection); - - fetched.RemoveFromQueue(); - - if (result.Succeeded) - _logger.JobExecuted(sp.Elapsed.TotalSeconds); - - return OperateResult.Success; - } - catch (SubscriberNotFoundException ex) - { - _logger.LogError(ex.Message); - - AddErrorReasonToContent(message, ex); - - await _stateChanger.ChangeStateAsync(message, new FailedState(), connection); - - fetched.RemoveFromQueue(); - - return OperateResult.Failed(ex); - } - catch (Exception ex) - { - _logger.ExceptionOccuredWhileExecuting(message.Name, ex); - - fetched.Requeue(); - - return OperateResult.Failed(ex); - } - } - - private IState GetNewState(OperateResult result, CapReceivedMessage message) - { - IState newState; - if (!result.Succeeded) - { - var shouldRetry = UpdateMessageForRetry(message); - if (shouldRetry) - { - newState = new ScheduledState(); - _logger.JobFailedWillRetry(result.Exception); - } - else - { - newState = new FailedState(); - _logger.JobFailed(result.Exception); - } - AddErrorReasonToContent(message, result.Exception); - } - else - { - newState = new SucceededState(_options.SucceedMessageExpiredAfter); - } - return newState; - } - - private static bool UpdateMessageForRetry(CapReceivedMessage message) - { - var retryBehavior = RetryBehavior.DefaultRetry; - - var retries = ++message.Retries; - if (retries >= retryBehavior.RetryCount) - return false; - - var due = message.Added.AddSeconds(retryBehavior.RetryIn(retries)); - message.ExpiresAt = due; - - return true; - } - - private static void AddErrorReasonToContent(CapReceivedMessage message, Exception exception) - { - message.Content = Helper.AddExceptionProperty(message.Content, exception); - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/IQueueExecutor.cs b/src/DotNetCore.CAP/IQueueExecutor.cs deleted file mode 100644 index ad4a6b1..0000000 --- a/src/DotNetCore.CAP/IQueueExecutor.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace DotNetCore.CAP -{ - public interface IQueueExecutor - { - Task ExecuteAsync(IStorageConnection connection, IFetchedMessage message); - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/IQueueExecutorFactory.cs b/src/DotNetCore.CAP/IQueueExecutorFactory.cs deleted file mode 100644 index 5b46ac6..0000000 --- a/src/DotNetCore.CAP/IQueueExecutorFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP -{ - public interface IQueueExecutorFactory - { - IQueueExecutor GetInstance(MessageType messageType); - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/IStorage.cs b/src/DotNetCore.CAP/IStorage.cs index 1f857ac..a31b351 100644 --- a/src/DotNetCore.CAP/IStorage.cs +++ b/src/DotNetCore.CAP/IStorage.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System.Threading; using System.Threading.Tasks; using DotNetCore.CAP.Dashboard; diff --git a/src/DotNetCore.CAP/IStorageConnection.cs b/src/DotNetCore.CAP/IStorageConnection.cs index a4f7b0b..18d5ff8 100644 --- a/src/DotNetCore.CAP/IStorageConnection.cs +++ b/src/DotNetCore.CAP/IStorageConnection.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -18,20 +21,10 @@ namespace DotNetCore.CAP /// The message's id. Task GetPublishedMessageAsync(int id); - /// - /// Fetches the next message to be executed. - /// - Task FetchNextMessageAsync(); - - /// - /// Returns the next message to be enqueued. - /// - Task GetNextPublishedMessageToBeEnqueuedAsync(); - /// /// Returns executed failed messages. /// - Task> GetFailedPublishedMessages(); + Task> GetPublishedMessagesOfNeedRetry(); // Received messages @@ -39,7 +32,7 @@ namespace DotNetCore.CAP /// Stores the message. /// /// The message to store. - Task StoreReceivedMessageAsync(CapReceivedMessage message); + Task StoreReceivedMessageAsync(CapReceivedMessage message); /// /// Returns the message with the given id. @@ -47,15 +40,10 @@ namespace DotNetCore.CAP /// The message's id. Task GetReceivedMessageAsync(int id); - /// - /// Returns the next message to be enqueued. - /// - Task GetNextReceivedMessageToBeEnqueuedAsync(); - /// /// Returns executed failed message. /// - Task> GetFailedReceivedMessages(); + Task> GetReceivedMessagesOfNeedRetry(); /// /// Creates and returns an . diff --git a/src/DotNetCore.CAP/IStorageTransaction.cs b/src/DotNetCore.CAP/IStorageTransaction.cs index 05fea1b..7ee5185 100644 --- a/src/DotNetCore.CAP/IStorageTransaction.cs +++ b/src/DotNetCore.CAP/IStorageTransaction.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Threading.Tasks; using DotNetCore.CAP.Models; @@ -14,10 +17,6 @@ namespace DotNetCore.CAP void UpdateMessage(CapReceivedMessage message); - void EnqueueMessage(CapPublishedMessage message); - - void EnqueueMessage(CapReceivedMessage message); - Task CommitAsync(); } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/ISubscribeExecutor.Default.cs b/src/DotNetCore.CAP/ISubscribeExecutor.Default.cs new file mode 100644 index 0000000..fe03a15 --- /dev/null +++ b/src/DotNetCore.CAP/ISubscribeExecutor.Default.cs @@ -0,0 +1,175 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using DotNetCore.CAP.Diagnostics; +using DotNetCore.CAP.Infrastructure; +using DotNetCore.CAP.Internal; +using DotNetCore.CAP.Models; +using DotNetCore.CAP.Processor; +using DotNetCore.CAP.Processor.States; +using Microsoft.Extensions.Logging; + +namespace DotNetCore.CAP +{ + internal class DefaultSubscriberExecutor : ISubscriberExecutor + { + private readonly ICallbackMessageSender _callbackMessageSender; + private readonly IStorageConnection _connection; + private readonly ILogger _logger; + private readonly IStateChanger _stateChanger; + private readonly CapOptions _options; + private readonly MethodMatcherCache _selector; + + // diagnostics listener + // ReSharper disable once InconsistentNaming + private static readonly DiagnosticListener s_diagnosticListener = + new DiagnosticListener(CapDiagnosticListenerExtensions.DiagnosticListenerName); + + public DefaultSubscriberExecutor( + ILogger logger, + CapOptions options, + IConsumerInvokerFactory consumerInvokerFactory, + ICallbackMessageSender callbackMessageSender, + IStateChanger stateChanger, + IStorageConnection connection, + MethodMatcherCache selector) + { + _selector = selector; + _callbackMessageSender = callbackMessageSender; + _options = options; + _stateChanger = stateChanger; + _connection = connection; + _logger = logger; + + Invoker = consumerInvokerFactory.CreateInvoker(); + } + + private IConsumerInvoker Invoker { get; } + + public async Task ExecuteAsync(CapReceivedMessage message) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + try + { + var sp = Stopwatch.StartNew(); + + await InvokeConsumerMethodAsync(message); + + sp.Stop(); + + await SetSuccessfulState(message); + + _logger.ConsumerExecuted(sp.Elapsed.TotalSeconds); + + return OperateResult.Success; + } + catch (Exception ex) + { + _logger.LogError(ex, $"An exception occurred while executing the subscription method. Topic:{message.Name}, Id:{message.Id}"); + + await SetFailedState(message, ex, out bool stillRetry); + if (stillRetry) + { + await ExecuteAsync(message); + } + + return OperateResult.Failed(ex); + } + } + + private Task SetSuccessfulState(CapReceivedMessage message) + { + var succeededState = new SucceededState(_options.SucceedMessageExpiredAfter); + + return _stateChanger.ChangeStateAsync(message, succeededState, _connection); + } + + private Task SetFailedState(CapReceivedMessage message, Exception ex, out bool stillRetry) + { + IState newState = new FailedState(); + + if (ex is SubscriberNotFoundException) + { + stillRetry = false; + message.Retries = _options.FailedRetryCount; // not retry if SubscriberNotFoundException + } + else + { + stillRetry = UpdateMessageForRetry(message); + if (stillRetry) + { + _logger.ConsumerExecutionFailedWillRetry(ex); + return Task.CompletedTask; + } + } + + AddErrorReasonToContent(message, ex); + + return _stateChanger.ChangeStateAsync(message, newState, _connection); + } + + private static bool UpdateMessageForRetry(CapReceivedMessage message) + { + var retryBehavior = RetryBehavior.DefaultRetry; + + var retries = ++message.Retries; + if (retries >= retryBehavior.RetryCount) + { + return false; + } + + var due = message.Added.AddSeconds(retryBehavior.RetryIn(retries)); + message.ExpiresAt = due; + + return true; + } + + private static void AddErrorReasonToContent(CapReceivedMessage message, Exception exception) + { + message.Content = Helper.AddExceptionProperty(message.Content, exception); + } + + private async Task InvokeConsumerMethodAsync(CapReceivedMessage receivedMessage) + { + if (!_selector.TryGetTopicExector(receivedMessage.Name, receivedMessage.Group, + out var executor)) + { + var error = $"message can not be found subscriber, Message:{receivedMessage},\r\n see: https://github.com/dotnetcore/CAP/issues/63"; + throw new SubscriberNotFoundException(error); + } + + var startTime = DateTimeOffset.UtcNow; + var stopwatch = Stopwatch.StartNew(); + var operationId = Guid.Empty; + + var consumerContext = new ConsumerContext(executor, receivedMessage.ToMessageContext()); + + try + { + operationId = s_diagnosticListener.WriteSubscriberInvokeBefore(consumerContext); + + var ret = await Invoker.InvokeAsync(consumerContext); + + s_diagnosticListener.WriteSubscriberInvokeAfter(operationId, consumerContext, startTime, stopwatch.Elapsed); + + if (!string.IsNullOrEmpty(ret.CallbackName)) + { + await _callbackMessageSender.SendAsync(ret.MessageId, ret.CallbackName, ret.Result); + } + } + catch (Exception ex) + { + s_diagnosticListener.WriteSubscriberInvokeError(operationId, consumerContext, ex, startTime, stopwatch.Elapsed); + + throw new SubscriberExecutionFailedException(ex.Message, ex); + } + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/ISubscriberExecutor.cs b/src/DotNetCore.CAP/ISubscriberExecutor.cs new file mode 100644 index 0000000..a099b60 --- /dev/null +++ b/src/DotNetCore.CAP/ISubscriberExecutor.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using DotNetCore.CAP.Models; + +namespace DotNetCore.CAP +{ + public interface ISubscriberExecutor + { + Task ExecuteAsync(CapReceivedMessage message); + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Infrastructure/Helper.cs b/src/DotNetCore.CAP/Infrastructure/Helper.cs index b04592e..3f023b9 100644 --- a/src/DotNetCore.CAP/Infrastructure/Helper.cs +++ b/src/DotNetCore.CAP/Infrastructure/Helper.cs @@ -1,6 +1,12 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Reflection; +using DotNetCore.CAP.Diagnostics; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -32,7 +38,10 @@ namespace DotNetCore.CAP.Infrastructure public static object FromJson(string value, Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } return value != null ? JsonConvert.DeserializeObject(value, type, _serializerSettings) @@ -42,7 +51,7 @@ namespace DotNetCore.CAP.Infrastructure public static long ToTimestamp(DateTime value) { var elapsedTime = value - Epoch; - return (long) elapsedTime.TotalSeconds; + return (long)elapsedTime.TotalSeconds; } @@ -54,13 +63,19 @@ namespace DotNetCore.CAP.Infrastructure public static bool IsController(TypeInfo typeInfo) { if (!typeInfo.IsClass) + { return false; + } if (typeInfo.IsAbstract) + { return false; + } if (!typeInfo.IsPublic) + { return false; + } return !typeInfo.ContainsGenericParameters && typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase); @@ -71,13 +86,40 @@ namespace DotNetCore.CAP.Infrastructure return !CanConvertFromString(type); } - public static string AddExceptionProperty(string json, Exception exception) { var jObject = ToJObject(exception); return AddJsonProperty(json, "ExceptionMessage", jObject); } + public static string AddTracingHeaderProperty(string json, TracingHeaders headers) + { + var jObject = ToJObject(headers); + return AddJsonProperty(json, nameof(TracingHeaders), jObject); + } + + public static bool TryExtractTracingHeaders(string json, out TracingHeaders headers, out string removedHeadersJson) + { + var jObj = JObject.Parse(json); + var jToken = jObj[nameof(TracingHeaders)]; + if (jToken != null) + { + headers = new TracingHeaders(); + foreach (var item in jToken.ToObject>()) + { + headers.Add(item.Key, item.Value); + } + + jObj.Remove(nameof(TracingHeaders)); + removedHeadersJson = jObj.ToString(); + return true; + } + + headers = null; + removedHeadersJson = null; + return false; + } + public static bool IsInnerIP(string ipAddress) { bool isInnerIp; @@ -144,14 +186,28 @@ namespace DotNetCore.CAP.Infrastructure }); } + private static JObject ToJObject(TracingHeaders headers) + { + var jobj = new JObject(); + foreach (var keyValuePair in headers) + { + jobj[keyValuePair.Key] = keyValuePair.Value; + } + return jobj; + } + private static string AddJsonProperty(string json, string propertyName, JObject propertyValue) { var jObj = JObject.Parse(json); if (jObj.TryGetValue(propertyName, out var _)) + { jObj[propertyName] = propertyValue; + } else + { jObj.Add(new JProperty(propertyName, propertyValue)); + } return jObj.ToString(Formatting.None); } diff --git a/src/DotNetCore.CAP/Infrastructure/ObjectId.cs b/src/DotNetCore.CAP/Infrastructure/ObjectId.cs index 4a51166..4b93ce6 100644 --- a/src/DotNetCore.CAP/Infrastructure/ObjectId.cs +++ b/src/DotNetCore.CAP/Infrastructure/ObjectId.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; @@ -57,7 +60,10 @@ namespace DotNetCore.CAP.Infrastructure public ObjectId(byte[] bytes) { if (bytes == null) + { throw new ArgumentNullException("bytes"); + } + Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment); } @@ -83,11 +89,16 @@ namespace DotNetCore.CAP.Infrastructure public ObjectId(int timestamp, int machine, short pid, int increment) { if ((machine & 0xff000000) != 0) + { throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + if ((increment & 0xff000000) != 0) + { throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } _timestamp = timestamp; _machine = machine; @@ -102,7 +113,10 @@ namespace DotNetCore.CAP.Infrastructure public ObjectId(string value) { if (value == null) + { throw new ArgumentNullException("value"); + } + Unpack(ParseHexString(value), out _timestamp, out _machine, out _pid, out _increment); } @@ -256,11 +270,16 @@ namespace DotNetCore.CAP.Infrastructure public static byte[] Pack(int timestamp, int machine, short pid, int increment) { if ((machine & 0xff000000) != 0) + { throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + if ((increment & 0xff000000) != 0) + { throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } var bytes = new byte[12]; bytes[0] = (byte) (timestamp >> 24); @@ -286,9 +305,15 @@ namespace DotNetCore.CAP.Infrastructure public static ObjectId Parse(string s) { if (s == null) + { throw new ArgumentNullException("s"); + } + if (s.Length != 24) + { throw new ArgumentOutOfRangeException("s", "ObjectId string value must be 24 characters."); + } + return new ObjectId(ParseHexString(s)); } @@ -303,9 +328,15 @@ namespace DotNetCore.CAP.Infrastructure public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment) { if (bytes == null) + { throw new ArgumentNullException("bytes"); + } + if (bytes.Length != 12) + { throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long."); + } + timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3]; machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6]; pid = (short) ((bytes[7] << 8) + bytes[8]); @@ -349,11 +380,23 @@ namespace DotNetCore.CAP.Infrastructure public int CompareTo(ObjectId other) { var r = _timestamp.CompareTo(other._timestamp); - if (r != 0) return r; + if (r != 0) + { + return r; + } + r = _machine.CompareTo(other._machine); - if (r != 0) return r; + if (r != 0) + { + return r; + } + r = _pid.CompareTo(other._pid); - if (r != 0) return r; + if (r != 0) + { + return r; + } + return _increment.CompareTo(other._increment); } @@ -379,7 +422,10 @@ namespace DotNetCore.CAP.Infrastructure public override bool Equals(object obj) { if (obj is ObjectId) + { return Equals((ObjectId) obj); + } + return false; } @@ -423,15 +469,21 @@ namespace DotNetCore.CAP.Infrastructure public static byte[] ParseHexString(string s) { if (s == null) + { throw new ArgumentNullException("s"); + } if (s.Length % 2 == 1) + { throw new Exception("The binary key cannot have an odd number of digits"); + } var arr = new byte[s.Length >> 1]; for (var i = 0; i < s.Length >> 1; ++i) + { arr[i] = (byte) ((GetHexVal(s[i << 1]) << 4) + GetHexVal(s[(i << 1) + 1])); + } return arr; } @@ -444,7 +496,10 @@ namespace DotNetCore.CAP.Infrastructure public static string ToHexString(byte[] bytes) { if (bytes == null) + { throw new ArgumentNullException("bytes"); + } + var result = new char[bytes.Length * 2]; for (var i = 0; i < bytes.Length; i++) { @@ -452,6 +507,7 @@ namespace DotNetCore.CAP.Infrastructure result[2 * i] = (char) val; result[2 * i + 1] = (char) (val >> 16); } + return new string(result); } @@ -474,9 +530,15 @@ namespace DotNetCore.CAP.Infrastructure public static DateTime ToUniversalTime(DateTime dateTime) { if (dateTime == DateTime.MinValue) + { return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + } + if (dateTime == DateTime.MaxValue) + { return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc); + } + return dateTime.ToUniversalTime(); } diff --git a/src/DotNetCore.CAP/Infrastructure/StatusName.cs b/src/DotNetCore.CAP/Infrastructure/StatusName.cs index 62ee0fd..65d9427 100644 --- a/src/DotNetCore.CAP/Infrastructure/StatusName.cs +++ b/src/DotNetCore.CAP/Infrastructure/StatusName.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Infrastructure +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Infrastructure { /// /// The message status name. @@ -6,8 +9,6 @@ public struct StatusName { public const string Scheduled = nameof(Scheduled); - public const string Enqueued = nameof(Enqueued); - public const string Processing = nameof(Processing); public const string Succeeded = nameof(Succeeded); public const string Failed = nameof(Failed); } diff --git a/src/DotNetCore.CAP/Infrastructure/WaitHandleEx.cs b/src/DotNetCore.CAP/Infrastructure/WaitHandleEx.cs index 5a277d2..14ed410 100644 --- a/src/DotNetCore.CAP/Infrastructure/WaitHandleEx.cs +++ b/src/DotNetCore.CAP/Infrastructure/WaitHandleEx.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/DotNetCore.CAP/Internal/CapCache.cs b/src/DotNetCore.CAP/Internal/CapCache.cs index d9826db..243f935 100644 --- a/src/DotNetCore.CAP/Internal/CapCache.cs +++ b/src/DotNetCore.CAP/Internal/CapCache.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using System.Linq; @@ -56,6 +59,7 @@ namespace DotNetCore.CAP.Internal Clear(); _locker.Dispose(); } + // Dispose unmanaged resources } } @@ -71,7 +75,9 @@ namespace DotNetCore.CAP.Internal try { foreach (var t in _timers.Values) + { t.Dispose(); + } } catch { @@ -98,9 +104,11 @@ namespace DotNetCore.CAP.Internal if (_timers.TryGetValue(key, out timer)) { if (restartTimerIfExists) + { timer.Change( cacheTimeout ?? Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + } } else { @@ -139,7 +147,10 @@ namespace DotNetCore.CAP.Internal /// public void AddOrUpdate(K key, T cacheObject, TimeSpan? cacheTimeout, bool restartTimerIfExists = false) { - if (disposed) return; + if (disposed) + { + return; + } _locker.EnterWriteLock(); try @@ -147,9 +158,13 @@ namespace DotNetCore.CAP.Internal CheckTimer(key, cacheTimeout, restartTimerIfExists); if (!_cache.ContainsKey(key)) + { _cache.Add(key, cacheObject); + } else + { _cache[key] = cacheObject; + } } finally { @@ -182,7 +197,10 @@ namespace DotNetCore.CAP.Internal /// The object from the cache or default(T), if not found. public T Get(K key) { - if (disposed) return default(T); + if (disposed) + { + return default(T); + } _locker.EnterReadLock(); try @@ -227,7 +245,10 @@ namespace DotNetCore.CAP.Internal /// The key pattern to remove. The Predicate has to return true to get key removed. public void Remove(Predicate keyPattern) { - if (disposed) return; + if (disposed) + { + return; + } _locker.EnterWriteLock(); try @@ -245,6 +266,7 @@ namespace DotNetCore.CAP.Internal catch { } + _timers.Remove(workKey); _cache.Remove(workKey); } @@ -262,7 +284,10 @@ namespace DotNetCore.CAP.Internal /// The cache-key to remove. public void Remove(K key) { - if (disposed) return; + if (disposed) + { + return; + } _locker.EnterWriteLock(); try @@ -276,6 +301,7 @@ namespace DotNetCore.CAP.Internal catch { } + _timers.Remove(key); _cache.Remove(key); } @@ -293,7 +319,10 @@ namespace DotNetCore.CAP.Internal /// True if the key exists in the cache, otherwise False. public bool Exists(K key) { - if (disposed) return false; + if (disposed) + { + return false; + } _locker.EnterReadLock(); try diff --git a/src/DotNetCore.CAP/Internal/ConsumerContext.cs b/src/DotNetCore.CAP/Internal/ConsumerContext.cs index cafd33f..49762ed 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerContext.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerContext.cs @@ -1,11 +1,14 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Internal { /// /// A context for consumers, it used to be provider wrapper of method description and received message. /// - internal class ConsumerContext + public class ConsumerContext { /// /// create a new instance of . diff --git a/src/DotNetCore.CAP/Internal/ConsumerExecutedResult.cs b/src/DotNetCore.CAP/Internal/ConsumerExecutedResult.cs index 093fbb3..d63bfd6 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerExecutedResult.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerExecutedResult.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. namespace DotNetCore.CAP.Internal { @@ -19,4 +18,4 @@ namespace DotNetCore.CAP.Internal public string CallbackName { get; set; } } -} +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs b/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs index b69f394..6f8f532 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs @@ -1,4 +1,7 @@ -using System.Reflection; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; using DotNetCore.CAP.Abstractions; namespace DotNetCore.CAP.Internal @@ -6,7 +9,7 @@ namespace DotNetCore.CAP.Internal /// /// A descriptor of user definition method. /// - internal class ConsumerExecutorDescriptor + public class ConsumerExecutorDescriptor { public MethodInfo MethodInfo { get; set; } diff --git a/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs b/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs index c6eb9e0..90633dc 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerInvokerFactory.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Abstractions; using Microsoft.Extensions.Logging; @@ -6,7 +9,7 @@ namespace DotNetCore.CAP.Internal { internal class ConsumerInvokerFactory : IConsumerInvokerFactory { - private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; private readonly IMessagePacker _messagePacker; private readonly IModelBinderFactory _modelBinderFactory; private readonly IServiceProvider _serviceProvider; @@ -17,7 +20,7 @@ namespace DotNetCore.CAP.Internal IModelBinderFactory modelBinderFactory, IServiceProvider serviceProvider) { - _logger = loggerFactory.CreateLogger(); + _loggerFactory = loggerFactory; _messagePacker = messagePacker; _modelBinderFactory = modelBinderFactory; _serviceProvider = serviceProvider; @@ -25,7 +28,7 @@ namespace DotNetCore.CAP.Internal public IConsumerInvoker CreateInvoker() { - return new DefaultConsumerInvoker(_logger, _serviceProvider, _messagePacker, _modelBinderFactory); + return new DefaultConsumerInvoker(_loggerFactory, _serviceProvider, _messagePacker, _modelBinderFactory); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/HashCodeCombiner.cs b/src/DotNetCore.CAP/Internal/HashCodeCombiner.cs index 842d8ff..43f000e 100644 --- a/src/DotNetCore.CAP/Internal/HashCodeCombiner.cs +++ b/src/DotNetCore.CAP/Internal/HashCodeCombiner.cs @@ -1,4 +1,7 @@ -using System.Collections; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -10,7 +13,8 @@ namespace DotNetCore.CAP.Internal public int CombinedHash { - [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return _combinedHash64.GetHashCode(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return _combinedHash64.GetHashCode(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -34,6 +38,7 @@ namespace DotNetCore.CAP.Internal Add(o); count++; } + Add(count); } } diff --git a/src/DotNetCore.CAP/Internal/ICallbackMessageSender.Default.cs b/src/DotNetCore.CAP/Internal/ICallbackMessageSender.Default.cs index 1a4f446..1dc7fc8 100644 --- a/src/DotNetCore.CAP/Internal/ICallbackMessageSender.Default.cs +++ b/src/DotNetCore.CAP/Internal/ICallbackMessageSender.Default.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Threading.Tasks; using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Infrastructure; @@ -10,10 +13,10 @@ namespace DotNetCore.CAP.Internal { internal class CallbackMessageSender : ICallbackMessageSender { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; private readonly IContentSerializer _contentSerializer; + private readonly ILogger _logger; private readonly IMessagePacker _messagePacker; + private readonly IServiceProvider _serviceProvider; public CallbackMessageSender( ILogger logger, @@ -31,9 +34,13 @@ namespace DotNetCore.CAP.Internal { string body; if (bodyObj != null && Helper.IsComplexType(bodyObj.GetType())) + { body = _contentSerializer.Serialize(bodyObj); + } else + { body = bodyObj?.ToString(); + } _logger.LogDebug($"Callback message will publishing, name:{topicName},content:{body}"); @@ -56,8 +63,8 @@ namespace DotNetCore.CAP.Internal { var provider = scope.ServiceProvider; var callbackPublisher = provider.GetService(); - await callbackPublisher.PublishAsync(publishedMessage); + await callbackPublisher.PublishCallbackAsync(publishedMessage); } } } -} +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/ICallbackMessageSender.cs b/src/DotNetCore.CAP/Internal/ICallbackMessageSender.cs index 2f7d3e6..b66cf9b 100644 --- a/src/DotNetCore.CAP/Internal/ICallbackMessageSender.cs +++ b/src/DotNetCore.CAP/Internal/ICallbackMessageSender.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; namespace DotNetCore.CAP.Internal { diff --git a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs index 7303f80..7fc4d0b 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerInvoker.Default.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Threading.Tasks; using DotNetCore.CAP.Abstractions; using Microsoft.Extensions.DependencyInjection; @@ -10,11 +13,11 @@ namespace DotNetCore.CAP.Internal internal class DefaultConsumerInvoker : IConsumerInvoker { private readonly ILogger _logger; + private readonly IMessagePacker _messagePacker; private readonly IModelBinderFactory _modelBinderFactory; private readonly IServiceProvider _serviceProvider; - private readonly IMessagePacker _messagePacker; - public DefaultConsumerInvoker(ILogger logger, + public DefaultConsumerInvoker(ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IMessagePacker messagePacker, IModelBinderFactory modelBinderFactory) @@ -22,7 +25,7 @@ namespace DotNetCore.CAP.Internal _modelBinderFactory = modelBinderFactory; _serviceProvider = serviceProvider; _messagePacker = messagePacker; - _logger = logger; + _logger = loggerFactory.CreateLogger(); } public async Task InvokeAsync(ConsumerContext context) @@ -44,9 +47,14 @@ namespace DotNetCore.CAP.Internal object resultObj; if (executor.MethodParameters.Length > 0) + { resultObj = await ExecuteWithParameterAsync(executor, obj, message.Content); + } else + { resultObj = await ExecuteAsync(executor, obj); + } + return new ConsumerExecutedResult(resultObj, message.Id, message.CallbackName); } } @@ -54,7 +62,10 @@ namespace DotNetCore.CAP.Internal private async Task ExecuteAsync(ObjectMethodExecutor executor, object @class) { if (executor.IsMethodAsync) + { return await executor.ExecuteAsync(@class); + } + return executor.Execute(@class); } @@ -69,9 +80,13 @@ namespace DotNetCore.CAP.Internal if (bindResult.IsSuccess) { if (executor.IsMethodAsync) + { return await executor.ExecuteAsync(@class, bindResult.Model); + } + return executor.Execute(@class, bindResult.Model); } + throw new MethodBindException( $"Parameters:{firstParameter.Name} bind failed! ParameterString is: {parameterString} "); } diff --git a/src/DotNetCore.CAP/Internal/IConsumerInvoker.cs b/src/DotNetCore.CAP/Internal/IConsumerInvoker.cs index 13c7964..892c2e3 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerInvoker.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerInvoker.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; namespace DotNetCore.CAP.Internal { diff --git a/src/DotNetCore.CAP/Internal/IConsumerInvokerFactory.cs b/src/DotNetCore.CAP/Internal/IConsumerInvokerFactory.cs index 44d38d5..9fd7e74 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerInvokerFactory.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerInvokerFactory.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Internal +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Internal { internal interface IConsumerInvokerFactory { diff --git a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs index e20d306..f751499 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs @@ -1,7 +1,11 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -10,15 +14,17 @@ namespace DotNetCore.CAP.Internal { /// /// - /// A default implementation. + /// A default implementation. /// internal class DefaultConsumerServiceSelector : IConsumerServiceSelector { private readonly CapOptions _capOptions; private readonly IServiceProvider _serviceProvider; + private List> _asteriskList; + private List> _poundList; /// - /// Creates a new . + /// Creates a new . /// public DefaultConsumerServiceSelector(IServiceProvider serviceProvider, CapOptions capOptions) { @@ -26,17 +32,6 @@ namespace DotNetCore.CAP.Internal _capOptions = capOptions; } - /// - /// Selects the best candidate from for - /// the - /// current message associated. - /// - public ConsumerExecutorDescriptor SelectBestCandidate(string key, - IReadOnlyList executeDescriptor) - { - return executeDescriptor.FirstOrDefault(x => x.Attribute.Name == key); - } - public IReadOnlyList SelectCandidates() { var executorDescriptorList = new List(); @@ -48,6 +43,26 @@ namespace DotNetCore.CAP.Internal return executorDescriptorList; } + public ConsumerExecutorDescriptor SelectBestCandidate(string key, IReadOnlyList executeDescriptor) + { + var result = MatchUsingName(key, executeDescriptor); + if (result != null) + { + return result; + } + + //[*] match with regex, i.e. foo.*.abc + result = MatchAsteriskUsingRegex(key, executeDescriptor); + if (result != null) + { + return result; + } + + //[#] match regex, i.e. foo.# + result = MatchPoundUsingRegex(key, executeDescriptor); + return result; + } + private IEnumerable FindConsumersFromInterfaceTypes( IServiceProvider provider) { @@ -61,10 +76,13 @@ namespace DotNetCore.CAP.Internal { var typeInfo = service.GetType().GetTypeInfo(); if (!typeof(ICapSubscribe).GetTypeInfo().IsAssignableFrom(typeInfo)) + { continue; + } executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo)); } + return executorDescriptorList; } } @@ -78,7 +96,9 @@ namespace DotNetCore.CAP.Internal { var typeInfo = type.GetTypeInfo(); if (Helper.IsController(typeInfo)) + { executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo)); + } } return executorDescriptorList; @@ -91,12 +111,18 @@ namespace DotNetCore.CAP.Internal var topicAttr = method.GetCustomAttributes(true); var topicAttributes = topicAttr as IList ?? topicAttr.ToList(); - if (!topicAttributes.Any()) continue; + if (!topicAttributes.Any()) + { + continue; + } foreach (var attr in topicAttributes) { if (attr.Group == null) + { attr.Group = _capOptions.DefaultGroup; + } + yield return InitDescriptor(attr, method, typeInfo); } } @@ -116,5 +142,65 @@ namespace DotNetCore.CAP.Internal return descriptor; } + + private ConsumerExecutorDescriptor MatchUsingName(string key, IReadOnlyList executeDescriptor) + { + return executeDescriptor.FirstOrDefault(x => x.Attribute.Name == key); + } + + private ConsumerExecutorDescriptor MatchAsteriskUsingRegex(string key, IReadOnlyList executeDescriptor) + { + if (_asteriskList == null) + { + _asteriskList = executeDescriptor + .Where(x => x.Attribute.Name.IndexOf('*') >= 0) + .Select(x => new RegexExecuteDescriptor + { + Name = ("^" + x.Attribute.Name + "$").Replace("*", "[a-zA-Z]+").Replace(".", "\\."), + Descriptor = x + }).ToList(); + } + foreach (var red in _asteriskList) + { + if (Regex.IsMatch(key, red.Name, RegexOptions.Singleline)) + { + return red.Descriptor; + } + } + + return null; + } + + private ConsumerExecutorDescriptor MatchPoundUsingRegex(string key, IReadOnlyList executeDescriptor) + { + if (_poundList == null) + { + _poundList = executeDescriptor + .Where(x => x.Attribute.Name.IndexOf('#') >= 0) + .Select(x => new RegexExecuteDescriptor + { + Name = ("^" + x.Attribute.Name + "$").Replace("#", "[a-zA-Z\\.]+"), + Descriptor = x + }).ToList(); + } + + foreach (var red in _poundList) + { + if (Regex.IsMatch(key, red.Name, RegexOptions.Singleline)) + { + return red.Descriptor; + } + } + + return null; + } + + + private class RegexExecuteDescriptor + { + public string Name { get; set; } + + public T Descriptor { get; set; } + } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.cs b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.cs index 3944d79..0b53af7 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; namespace DotNetCore.CAP.Internal { @@ -19,8 +22,6 @@ namespace DotNetCore.CAP.Internal /// /// topic or exchange router key. /// the set of candidates. - /// - ConsumerExecutorDescriptor - SelectBestCandidate(string key, IReadOnlyList candidates); + ConsumerExecutorDescriptor SelectBestCandidate(string key, IReadOnlyList candidates); } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/IContentSerializer.Json.cs b/src/DotNetCore.CAP/Internal/IContentSerializer.Json.cs index 21b656d..408b82c 100644 --- a/src/DotNetCore.CAP/Internal/IContentSerializer.Json.cs +++ b/src/DotNetCore.CAP/Internal/IContentSerializer.Json.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Infrastructure; @@ -6,7 +9,7 @@ namespace DotNetCore.CAP.Internal { internal class JsonContentSerializer : IContentSerializer { - public T DeSerialize(string messageObjStr) + public T DeSerialize(string messageObjStr) { return Helper.FromJson(messageObjStr); } diff --git a/src/DotNetCore.CAP/Internal/IMessagePacker.Default.cs b/src/DotNetCore.CAP/Internal/IMessagePacker.Default.cs index ea333fa..6de5ac8 100644 --- a/src/DotNetCore.CAP/Internal/IMessagePacker.Default.cs +++ b/src/DotNetCore.CAP/Internal/IMessagePacker.Default.cs @@ -1,4 +1,7 @@ -using DotNetCore.CAP.Abstractions; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Internal @@ -22,4 +25,4 @@ namespace DotNetCore.CAP.Internal return _serializer.DeSerialize(packingMessage); } } -} +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs b/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs index c2bfc19..79cf4c3 100644 --- a/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs +++ b/src/DotNetCore.CAP/Internal/IModelBinder.ComplexType.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Reflection; using System.Threading.Tasks; using DotNetCore.CAP.Abstractions; diff --git a/src/DotNetCore.CAP/Internal/IModelBinder.SimpleType.cs b/src/DotNetCore.CAP/Internal/IModelBinder.SimpleType.cs index c6f6c80..3761891 100644 --- a/src/DotNetCore.CAP/Internal/IModelBinder.SimpleType.cs +++ b/src/DotNetCore.CAP/Internal/IModelBinder.SimpleType.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.ComponentModel; using System.Globalization; using System.Reflection; @@ -22,7 +25,9 @@ namespace DotNetCore.CAP.Internal public Task BindModelAsync(string content) { if (content == null) + { throw new ArgumentNullException(nameof(content)); + } var parameterType = _parameterInfo.ParameterType; @@ -30,27 +35,43 @@ namespace DotNetCore.CAP.Internal { object model; if (parameterType == typeof(string)) + { if (string.IsNullOrWhiteSpace(content)) + { model = null; + } else + { model = content; + } + } else if (string.IsNullOrWhiteSpace(content)) + { model = null; + } else + { model = _typeConverter.ConvertFrom( null, CultureInfo.CurrentCulture, content); + } if (model == null && !IsReferenceOrNullableType(parameterType)) + { return Task.FromResult(ModelBindingResult.Failed()); + } + return Task.FromResult(ModelBindingResult.Success(model)); } catch (Exception exception) { var isFormatException = exception is FormatException; if (!isFormatException && exception.InnerException != null) + { exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException; + } + throw; } } diff --git a/src/DotNetCore.CAP/Internal/ISubscriberExecutor.Default.cs b/src/DotNetCore.CAP/Internal/ISubscriberExecutor.Default.cs deleted file mode 100644 index ca13a7f..0000000 --- a/src/DotNetCore.CAP/Internal/ISubscriberExecutor.Default.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Threading.Tasks; -using DotNetCore.CAP.Abstractions; -using DotNetCore.CAP.Models; -using Microsoft.Extensions.Logging; - -namespace DotNetCore.CAP.Internal -{ - internal class DefaultSubscriberExecutor : ISubscriberExecutor - { - private readonly ICallbackMessageSender _callbackMessageSender; - private readonly ILogger _logger; - private readonly MethodMatcherCache _selector; - - private IConsumerInvoker Invoker { get; } - - public DefaultSubscriberExecutor(MethodMatcherCache selector, - IConsumerInvokerFactory consumerInvokerFactory, - ICallbackMessageSender callbackMessageSender, - ILogger logger) - { - _selector = selector; - _callbackMessageSender = callbackMessageSender; - _logger = logger; - - Invoker = consumerInvokerFactory.CreateInvoker(); - } - - public async Task ExecuteAsync(CapReceivedMessage receivedMessage) - { - if (!_selector.TryGetTopicExector(receivedMessage.Name, receivedMessage.Group, - out var executor)) - { - var error = "message can not be found subscriber. Message:" + receivedMessage; - error += "\r\n see: https://github.com/dotnetcore/CAP/issues/63"; - throw new SubscriberNotFoundException(error); - } - - var consumerContext = new ConsumerContext(executor, receivedMessage.ToMessageContext()); - try - { - var ret = await Invoker.InvokeAsync(consumerContext); - if (!string.IsNullOrEmpty(ret.CallbackName)) - await _callbackMessageSender.SendAsync(ret.MessageId, ret.CallbackName, ret.Result); - - return OperateResult.Success; - } - catch (Exception ex) - { - _logger.ConsumerMethodExecutingFailed($"Group:{receivedMessage.Group}, Topic:{receivedMessage.Name}", - ex); - - return OperateResult.Failed(ex); - } - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/MethodBindException.cs b/src/DotNetCore.CAP/Internal/MethodBindException.cs index 63eb454..1d6c0bc 100644 --- a/src/DotNetCore.CAP/Internal/MethodBindException.cs +++ b/src/DotNetCore.CAP/Internal/MethodBindException.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Internal { diff --git a/src/DotNetCore.CAP/Internal/MethodMatcherCache.cs b/src/DotNetCore.CAP/Internal/MethodMatcherCache.cs index 831a082..8ff25c7 100644 --- a/src/DotNetCore.CAP/Internal/MethodMatcherCache.cs +++ b/src/DotNetCore.CAP/Internal/MethodMatcherCache.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -8,55 +11,41 @@ namespace DotNetCore.CAP.Internal internal class MethodMatcherCache { private readonly IConsumerServiceSelector _selector; - private List _allTopics; public MethodMatcherCache(IConsumerServiceSelector selector) { _selector = selector; - Entries = new ConcurrentDictionary>(); + Entries = new ConcurrentDictionary>(); } - private ConcurrentDictionary> Entries { get; } + private ConcurrentDictionary> Entries { get; } /// /// Get a dictionary of candidates.In the dictionary, /// the Key is the CAPSubscribeAttribute Group, the Value for the current Group of candidates /// - public ConcurrentDictionary> GetCandidatesMethodsOfGroupNameGrouped() + public ConcurrentDictionary> GetCandidatesMethodsOfGroupNameGrouped() { - if (Entries.Count != 0) return Entries; + if (Entries.Count != 0) + { + return Entries; + } var executorCollection = _selector.SelectCandidates(); var groupedCandidates = executorCollection.GroupBy(x => x.Attribute.Group); foreach (var item in groupedCandidates) + { Entries.TryAdd(item.Key, item.ToList()); + } return Entries; } /// - /// Get a dictionary of specify topic candidates. - /// The Key is Group name, the value is specify topic candidates. - /// - /// message topic name - public IDictionary> GetTopicExector(string topicName) - { - if (Entries == null) - throw new ArgumentNullException(nameof(Entries)); - - var dic = new Dictionary>(); - foreach (var item in Entries) - { - var topicCandidates = item.Value.Where(x => x.Attribute.Name == topicName); - dic.Add(item.Key, topicCandidates.ToList()); - } - return dic; - } - - /// - /// Attempts to get the topic exector associated with the specified topic name and group name from the . + /// Attempts to get the topic exector associated with the specified topic name and group name from the + /// . /// /// The topic name of the value to get. /// The group name of the value to get. @@ -66,38 +55,20 @@ namespace DotNetCore.CAP.Internal out ConsumerExecutorDescriptor matchTopic) { if (Entries == null) + { throw new ArgumentNullException(nameof(Entries)); + } matchTopic = null; if (Entries.TryGetValue(groupName, out var groupMatchTopics)) { - matchTopic = groupMatchTopics.FirstOrDefault(x => x.Attribute.Name == topicName); - return matchTopic != null; - } - return false; - } + matchTopic = _selector.SelectBestCandidate(topicName, groupMatchTopics); - /// - /// Get all subscribe topics name. - /// - public IEnumerable GetSubscribeTopics() - { - if (_allTopics != null) - { - return _allTopics; + return matchTopic != null; } - if (Entries == null) - throw new ArgumentNullException(nameof(Entries)); - - _allTopics = new List(); - - foreach (var descriptors in Entries.Values) - { - _allTopics.AddRange(descriptors.Select(x => x.Attribute.Name)); - } - return _allTopics; + return false; } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/ModelBinderFactory.cs b/src/DotNetCore.CAP/Internal/ModelBinderFactory.cs index a7ad0de..d8f0291 100644 --- a/src/DotNetCore.CAP/Internal/ModelBinderFactory.cs +++ b/src/DotNetCore.CAP/Internal/ModelBinderFactory.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Concurrent; using System.Reflection; using System.Runtime.CompilerServices; @@ -13,8 +16,8 @@ namespace DotNetCore.CAP.Internal /// internal class ModelBinderFactory : IModelBinderFactory { - private readonly IContentSerializer _serializer; private readonly ConcurrentDictionary _cache; + private readonly IContentSerializer _serializer; public ModelBinderFactory(IContentSerializer contentSerializer) { @@ -25,13 +28,17 @@ namespace DotNetCore.CAP.Internal public IModelBinder CreateBinder(ParameterInfo parameter) { if (parameter == null) + { throw new ArgumentNullException(nameof(parameter)); + } object token = parameter; var binder = CreateBinderCoreCached(parameter, token); if (binder == null) + { throw new InvalidOperationException("Format Could Not Create IModelBinder"); + } return binder; } @@ -39,11 +46,18 @@ namespace DotNetCore.CAP.Internal private IModelBinder CreateBinderCoreCached(ParameterInfo parameterInfo, object token) { if (TryGetCachedBinder(parameterInfo, token, out var binder)) + { return binder; + } + if (!Helper.IsComplexType(parameterInfo.ParameterType)) + { binder = new SimpleTypeModelBinder(parameterInfo); + } else + { binder = new ComplexTypeModelBinder(parameterInfo, _serializer); + } AddToCache(parameterInfo, token, binder); @@ -53,7 +67,9 @@ namespace DotNetCore.CAP.Internal private void AddToCache(ParameterInfo info, object cacheToken, IModelBinder binder) { if (cacheToken == null) + { return; + } _cache.TryAdd(new Key(info, cacheToken), binder); } diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs index 7046303..5c494e9 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Linq; diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs index 386e297..168e2e9 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Linq.Expressions; @@ -41,11 +41,13 @@ namespace Microsoft.Extensions.Internal if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, out var coercerExpression, out var coercerResultType)) + { if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) { info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); return true; } + } info = default(CoercedAwaitableInfo); return false; diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs index 9f33dfa..cf25fc9 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Collections.Generic; @@ -30,7 +30,9 @@ namespace Microsoft.Extensions.Internal private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) { if (methodInfo == null) + { throw new ArgumentNullException(nameof(methodInfo)); + } MethodInfo = methodInfo; MethodParameters = methodInfo.GetParameters(); @@ -48,7 +50,9 @@ namespace Microsoft.Extensions.Internal _executor = GetExecutor(methodInfo, targetTypeInfo); if (IsMethodAsync) + { _executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo); + } _parameterDefaultValues = parameterDefaultValues; } @@ -75,7 +79,9 @@ namespace Microsoft.Extensions.Internal object[] parameterDefaultValues) { if (parameterDefaultValues == null) + { throw new ArgumentNullException(nameof(parameterDefaultValues)); + } return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues); } @@ -127,11 +133,15 @@ namespace Microsoft.Extensions.Internal public object GetDefaultValueForParameter(int index) { if (_parameterDefaultValues == null) + { throw new InvalidOperationException( $"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied."); + } if (index < 0 || index > MethodParameters.Length - 1) + { throw new ArgumentOutOfRangeException(nameof(index)); + } return _parameterDefaultValues[index]; } @@ -241,6 +251,7 @@ namespace Microsoft.Extensions.Internal var getResultParam = Expression.Parameter(typeof(object), "awaiter"); Func getResultFunc; if (awaitableInfo.ResultType == typeof(void)) + { getResultFunc = Expression.Lambda>( Expression.Block( Expression.Call( @@ -249,7 +260,9 @@ namespace Microsoft.Extensions.Internal Expression.Constant(null) ), getResultParam).Compile(); + } else + { getResultFunc = Expression.Lambda>( Expression.Convert( Expression.Call( @@ -257,6 +270,7 @@ namespace Microsoft.Extensions.Internal awaitableInfo.AwaiterGetResultMethod), typeof(object)), getResultParam).Compile(); + } // var onCompletedFunc = (object awaiter, Action continuation) => { // ((CustomAwaiterType)awaiter).OnCompleted(continuation); diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs index ed2fee8..e5becb1 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Runtime.CompilerServices; diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs index fb2be0f..09a8fda 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Linq; @@ -73,12 +73,17 @@ namespace Microsoft.Extensions.Internal { var typeFullName = possibleFSharpAsyncGenericType?.FullName; if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal)) + { return false; + } lock (_fsharpValuesCacheLock) { if (_fsharpCoreAssembly != null) + { return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly; + } + return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType); } } @@ -90,7 +95,9 @@ namespace Microsoft.Extensions.Internal var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"); if (fsharpOptionType == null || fsharpAsyncType == null) + { return false; + } // Get a reference to FSharpOption.None var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType diff --git a/src/DotNetCore.CAP/Internal/PublisherSentFailedException.cs b/src/DotNetCore.CAP/Internal/PublisherSentFailedException.cs new file mode 100644 index 0000000..9be1758 --- /dev/null +++ b/src/DotNetCore.CAP/Internal/PublisherSentFailedException.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Internal +{ + public class PublisherSentFailedException : Exception + { + public PublisherSentFailedException(string message) : base(message) + { + } + + public PublisherSentFailedException(string message, Exception ex) : base(message, ex) + { + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/SubscriberExecutionFailedException.cs b/src/DotNetCore.CAP/Internal/SubscriberExecutionFailedException.cs new file mode 100644 index 0000000..bc35f9d --- /dev/null +++ b/src/DotNetCore.CAP/Internal/SubscriberExecutionFailedException.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace DotNetCore.CAP.Internal +{ + internal class SubscriberExecutionFailedException : Exception + { + public SubscriberExecutionFailedException(string message, Exception ex) : base(message, ex) + { + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Internal/SubscriberNotFoundException.cs b/src/DotNetCore.CAP/Internal/SubscriberNotFoundException.cs index 6bdb4d3..c27ba29 100644 --- a/src/DotNetCore.CAP/Internal/SubscriberNotFoundException.cs +++ b/src/DotNetCore.CAP/Internal/SubscriberNotFoundException.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Internal { diff --git a/src/DotNetCore.CAP/LoggerExtensions.cs b/src/DotNetCore.CAP/LoggerExtensions.cs index 99cccba..7ff8cf4 100644 --- a/src/DotNetCore.CAP/LoggerExtensions.cs +++ b/src/DotNetCore.CAP/LoggerExtensions.cs @@ -1,35 +1,32 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; namespace DotNetCore.CAP { + [SuppressMessage("ReSharper", "InconsistentNaming")] internal static class LoggerExtensions { - private static readonly Action _serverStarting; + private static readonly Action _serverStarting; private static readonly Action _processorsStartingError; private static readonly Action _serverShuttingDown; private static readonly Action _expectedOperationCanceledException; - - private static readonly Action _enqueueingSentMessage; - private static readonly Action _enqueueingReceivdeMessage; - private static readonly Action _executingConsumerMethod; - private static readonly Action _receivedMessageRetryExecuting; private static readonly Action _modelBinderFormattingException; - - private static readonly Action _jobFailed; - private static readonly Action _jobFailedWillRetry; - private static readonly Action _jobExecuted; - private static readonly Action _jobRetrying; - private static readonly Action _exceptionOccuredWhileExecutingJob; - - private static readonly Action _messageQueueError; + private static readonly Action _consumerFailedWillRetry; + private static readonly Action _consumerExecuted; + private static readonly Action _senderRetrying; + private static readonly Action _messageHasBeenSent; + private static readonly Action _messagePublishException; static LoggerExtensions() { - _serverStarting = LoggerMessage.Define( + _serverStarting = LoggerMessage.Define( LogLevel.Debug, 1, - "Starting the processing server. Detected {MachineProcessorCount} machine processor(s). Initiating {ProcessorCount} job processor(s)."); + "Starting the processors."); _processorsStartingError = LoggerMessage.Define( LogLevel.Error, @@ -39,29 +36,19 @@ namespace DotNetCore.CAP _serverShuttingDown = LoggerMessage.Define( LogLevel.Information, 2, - "Shutting down the processing server..."); + "Processors is shutting down..."); _expectedOperationCanceledException = LoggerMessage.Define( LogLevel.Warning, 3, "Expected an OperationCanceledException, but found '{ExceptionMessage}'."); - _enqueueingSentMessage = LoggerMessage.Define( - LogLevel.Debug, - 2, - "Enqueuing a topic to the sent message store. NameKey: '{NameKey}' Content: '{Content}'."); - - _enqueueingReceivdeMessage = LoggerMessage.Define( - LogLevel.Debug, - 2, - "Enqueuing a topic to the received message store. NameKey: '{NameKey}. Content: '{Content}'."); - - _executingConsumerMethod = LoggerMessage.Define( + LoggerMessage.Define( LogLevel.Error, 5, "Consumer method '{methodName}' failed to execute."); - _receivedMessageRetryExecuting = LoggerMessage.Define( + LoggerMessage.Define( LogLevel.Error, 5, "Received message topic method '{topicName}' failed to execute."); @@ -72,81 +59,60 @@ namespace DotNetCore.CAP "When call subscribe method, a parameter format conversion exception occurs. MethodName:'{MethodName}' ParameterName:'{ParameterName}' Content:'{Content}'." ); - _jobRetrying = LoggerMessage.Define( - LogLevel.Debug, + _senderRetrying = LoggerMessage.Define( + LogLevel.Information, 3, - "Retrying a job: {Retries}..."); + "The {Retries}th retrying send a message failed. message id: {MessageId} "); - _jobExecuted = LoggerMessage.Define( + _consumerExecuted = LoggerMessage.Define( LogLevel.Debug, 4, - "Job executed. Took: {Seconds} secs."); + "Consumer executed. Took: {Seconds} secs."); - _jobFailed = LoggerMessage.Define( - LogLevel.Warning, - 1, - "Job failed to execute."); - - _jobFailedWillRetry = LoggerMessage.Define( + _consumerFailedWillRetry = LoggerMessage.Define( LogLevel.Warning, 2, - "Job failed to execute. Will retry."); + "Consumer failed to execute, it will be retry."); - _exceptionOccuredWhileExecutingJob = LoggerMessage.Define( - LogLevel.Error, - 6, - "An exception occured while trying to execute a message: '{MessageId}'. " + - "Requeuing for another retry."); + _messageHasBeenSent = LoggerMessage.Define( + LogLevel.Debug, + 4, + "Message published. Took: {Seconds} secs."); - _messageQueueError = LoggerMessage.Define( + _messagePublishException = LoggerMessage.Define( LogLevel.Error, - 7, - "The MessageQueue Client fires an internal error:'{error}'."); - } - - public static void JobFailed(this ILogger logger, Exception ex) - { - _jobFailed(logger, ex); - } - - public static void JobFailedWillRetry(this ILogger logger, Exception ex) - { - _jobFailedWillRetry(logger, ex); - } - - public static void JobRetrying(this ILogger logger, int retries) - { - _jobRetrying(logger, retries, null); + 6, + "An exception occured while publishing a message, reason:{Reason}. message id:{MessageId}"); } - public static void JobExecuted(this ILogger logger, double seconds) + public static void ConsumerExecutionFailedWillRetry(this ILogger logger, Exception ex) { - _jobExecuted(logger, seconds, null); + _consumerFailedWillRetry(logger, ex); } - public static void ConsumerMethodExecutingFailed(this ILogger logger, string methodName, Exception ex) + public static void SenderRetrying(this ILogger logger, int messageId, int retries) { - _executingConsumerMethod(logger, methodName, ex); + _senderRetrying(logger, messageId, retries, null); } - public static void ReceivedMessageRetryExecutingFailed(this ILogger logger, string topicName, Exception ex) + public static void MessageHasBeenSent(this ILogger logger, double seconds) { - _receivedMessageRetryExecuting(logger, topicName, ex); + _messageHasBeenSent(logger, seconds, null); } - public static void EnqueuingReceivedMessage(this ILogger logger, string nameKey, string content) + public static void MessagePublishException(this ILogger logger, int messageId, string reason, Exception ex) { - _enqueueingReceivdeMessage(logger, nameKey, content, null); + _messagePublishException(logger, messageId, reason, ex); } - public static void EnqueuingSentMessage(this ILogger logger, string nameKey, string content) + public static void ConsumerExecuted(this ILogger logger, double seconds) { - _enqueueingSentMessage(logger, nameKey, content, null); + _consumerExecuted(logger, seconds, null); } - public static void ServerStarting(this ILogger logger, int machineProcessorCount, int processorCount) + public static void ServerStarting(this ILogger logger) { - _serverStarting(logger, machineProcessorCount, processorCount, null); + _serverStarting(logger, null); } public static void ProcessorsStartedError(this ILogger logger, Exception ex) @@ -164,20 +130,10 @@ namespace DotNetCore.CAP _expectedOperationCanceledException(logger, ex.Message, ex); } - public static void ExceptionOccuredWhileExecuting(this ILogger logger, string messageId, Exception ex) - { - _exceptionOccuredWhileExecutingJob(logger, messageId, ex); - } - public static void ModelBinderFormattingException(this ILogger logger, string methodName, string parameterName, string content, Exception ex) { _modelBinderFormattingException(logger, methodName, parameterName, content, ex); } - - public static void MessageQueueError(this ILogger logger, string error) - { - _messageQueueError(logger, error, null); - } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/MessageContext.cs b/src/DotNetCore.CAP/MessageContext.cs index 632d8ea..b42d7ca 100644 --- a/src/DotNetCore.CAP/MessageContext.cs +++ b/src/DotNetCore.CAP/MessageContext.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP { /// /// Message context diff --git a/src/DotNetCore.CAP/Models/CapMessageDto.cs b/src/DotNetCore.CAP/Models/CapMessageDto.cs index 0b3c699..c79fb97 100644 --- a/src/DotNetCore.CAP/Models/CapMessageDto.cs +++ b/src/DotNetCore.CAP/Models/CapMessageDto.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Infrastructure; namespace DotNetCore.CAP.Models @@ -14,7 +17,7 @@ namespace DotNetCore.CAP.Models public virtual string CallbackName { get; set; } } - public sealed class CapMessageDto: CapMessage + public sealed class CapMessageDto : CapMessage { public CapMessageDto() { diff --git a/src/DotNetCore.CAP/Models/CapPublishedMessage.cs b/src/DotNetCore.CAP/Models/CapPublishedMessage.cs index cc7efa6..41c554d 100644 --- a/src/DotNetCore.CAP/Models/CapPublishedMessage.cs +++ b/src/DotNetCore.CAP/Models/CapPublishedMessage.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Models { diff --git a/src/DotNetCore.CAP/Models/CapQueue.cs b/src/DotNetCore.CAP/Models/CapQueue.cs index ccdd812..9a5b9f9 100644 --- a/src/DotNetCore.CAP/Models/CapQueue.cs +++ b/src/DotNetCore.CAP/Models/CapQueue.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Models +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Models { public class CapQueue { diff --git a/src/DotNetCore.CAP/Models/CapReceivedMessage.cs b/src/DotNetCore.CAP/Models/CapReceivedMessage.cs index eae744d..790aca2 100644 --- a/src/DotNetCore.CAP/Models/CapReceivedMessage.cs +++ b/src/DotNetCore.CAP/Models/CapReceivedMessage.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Models { diff --git a/src/DotNetCore.CAP/Models/MessageType.cs b/src/DotNetCore.CAP/Models/MessageType.cs index 5097962..141040e 100644 --- a/src/DotNetCore.CAP/Models/MessageType.cs +++ b/src/DotNetCore.CAP/Models/MessageType.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.Models +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Models { public enum MessageType { diff --git a/src/DotNetCore.CAP/MqLogType.cs b/src/DotNetCore.CAP/MqLogType.cs index 1a392cb..3d84a48 100644 --- a/src/DotNetCore.CAP/MqLogType.cs +++ b/src/DotNetCore.CAP/MqLogType.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP { @@ -21,4 +24,4 @@ namespace DotNetCore.CAP public MqLogType LogType { get; set; } } -} +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptions.cs b/src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptions.cs index 09e617d..008f568 100644 --- a/src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptions.cs +++ b/src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptions.cs @@ -1,4 +1,5 @@ -// ReSharper disable once CheckNamespace +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. namespace DotNetCore.CAP { diff --git a/src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptionsExtensions.cs b/src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptionsExtensions.cs index cc0decc..8617520 100644 --- a/src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptionsExtensions.cs +++ b/src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptionsExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP; using DotNetCore.CAP.NodeDiscovery; using Microsoft.Extensions.DependencyInjection; @@ -44,7 +47,10 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseDiscovery(this CapOptions capOptions, Action options) { - if (options == null) throw new ArgumentNullException(nameof(options)); + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } capOptions.RegisterExtension(new DiscoveryOptionsExtension(options)); diff --git a/src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.Default.cs b/src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.Default.cs index e7639a6..5627282 100644 --- a/src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.Default.cs +++ b/src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.Default.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.NodeDiscovery @@ -15,7 +18,9 @@ namespace DotNetCore.CAP.NodeDiscovery public INodeDiscoveryProvider Create(DiscoveryOptions options) { if (options == null) + { throw new ArgumentNullException(nameof(options)); + } return new ConsulNodeDiscoveryProvider(_loggerFactory, options); } diff --git a/src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.cs b/src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.cs index df3da3d..22dcee9 100644 --- a/src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.cs +++ b/src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.NodeDiscovery +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.NodeDiscovery { internal interface IDiscoveryProviderFactory { diff --git a/src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.Consul.cs b/src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.Consul.cs index 6f5cc59..1aca30b 100644 --- a/src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.Consul.cs +++ b/src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.Consul.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -36,8 +39,7 @@ namespace DotNetCore.CAP.NodeDiscovery foreach (var service in services.Response) { var serviceInfo = await _consul.Catalog.Service(service.Key); - var node = serviceInfo.Response. - SkipWhile(x => !x.ServiceTags.Contains("CAP")) + var node = serviceInfo.Response.SkipWhile(x => !x.ServiceTags.Contains("CAP")) .Select(info => new Node { Id = info.ServiceID, @@ -58,7 +60,8 @@ namespace DotNetCore.CAP.NodeDiscovery { CapCache.Global.AddOrUpdate("cap.nodes.count", 0, TimeSpan.FromSeconds(20)); - _logger.LogError($"Get consul nodes raised an exception. Exception:{ex.Message},{ex.InnerException.Message}"); + _logger.LogError( + $"Get consul nodes raised an exception. Exception:{ex.Message},{ex.InnerException.Message}"); return null; } } @@ -73,7 +76,7 @@ namespace DotNetCore.CAP.NodeDiscovery Name = _options.NodeName, Address = _options.CurrentNodeHostName, Port = _options.CurrentNodePort, - Tags = new[] { "CAP", "Client", "Dashboard" }, + Tags = new[] {"CAP", "Client", "Dashboard"}, Check = new AgentServiceCheck { DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(30), @@ -86,7 +89,8 @@ namespace DotNetCore.CAP.NodeDiscovery } catch (Exception ex) { - _logger.LogError($"Get consul nodes raised an exception. Exception:{ex.Message},{ex.InnerException.Message}"); + _logger.LogError( + $"Get consul nodes raised an exception. Exception:{ex.Message},{ex.InnerException.Message}"); return null; } } diff --git a/src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.cs b/src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.cs index e2b07ca..960fbc4 100644 --- a/src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.cs +++ b/src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; using System.Threading.Tasks; namespace DotNetCore.CAP.NodeDiscovery diff --git a/src/DotNetCore.CAP/NodeDiscovery/IProcessingServer.Consul.cs b/src/DotNetCore.CAP/NodeDiscovery/IProcessingServer.Consul.cs index 04541df..af3b065 100644 --- a/src/DotNetCore.CAP/NodeDiscovery/IProcessingServer.Consul.cs +++ b/src/DotNetCore.CAP/NodeDiscovery/IProcessingServer.Consul.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.NodeDiscovery +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.NodeDiscovery { internal class ConsulProcessingNodeServer : IProcessingServer { diff --git a/src/DotNetCore.CAP/NodeDiscovery/Node.cs b/src/DotNetCore.CAP/NodeDiscovery/Node.cs index 44d7bf2..ea077f7 100644 --- a/src/DotNetCore.CAP/NodeDiscovery/Node.cs +++ b/src/DotNetCore.CAP/NodeDiscovery/Node.cs @@ -1,4 +1,7 @@ -namespace DotNetCore.CAP.NodeDiscovery +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.NodeDiscovery { public class Node { diff --git a/src/DotNetCore.CAP/OperateResult.cs b/src/DotNetCore.CAP/OperateResult.cs index 726f388..0be4570 100644 --- a/src/DotNetCore.CAP/OperateResult.cs +++ b/src/DotNetCore.CAP/OperateResult.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Linq; @@ -47,7 +50,10 @@ namespace DotNetCore.CAP { var result = new OperateResult {Succeeded = false}; if (errors != null) + { result._errors.AddRange(errors); + } + return result; } @@ -59,7 +65,10 @@ namespace DotNetCore.CAP Exception = ex }; if (errors != null) + { result._errors.AddRange(errors); + } + return result; } diff --git a/src/DotNetCore.CAP/Processor/IAdditionalProcessor.cs b/src/DotNetCore.CAP/Processor/IAdditionalProcessor.cs deleted file mode 100644 index b678614..0000000 --- a/src/DotNetCore.CAP/Processor/IAdditionalProcessor.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DotNetCore.CAP.Processor -{ - public interface IAdditionalProcessor : IProcessor - { - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/ICollectProcessor.cs b/src/DotNetCore.CAP/Processor/ICollectProcessor.cs new file mode 100644 index 0000000..9fd2625 --- /dev/null +++ b/src/DotNetCore.CAP/Processor/ICollectProcessor.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DotNetCore.CAP.Processor +{ + public interface ICollectProcessor : IProcessor + { + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/IDispatcher.Default.cs b/src/DotNetCore.CAP/Processor/IDispatcher.Default.cs index 8f76de1..433c01b 100644 --- a/src/DotNetCore.CAP/Processor/IDispatcher.Default.cs +++ b/src/DotNetCore.CAP/Processor/IDispatcher.Default.cs @@ -1,81 +1,94 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; -using DotNetCore.CAP.Infrastructure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; +using DotNetCore.CAP.Models; +using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.Processor { - public class DefaultDispatcher : IDispatcher + public class Dispatcher : IDispatcher, IDisposable { - internal static readonly AutoResetEvent PulseEvent = new AutoResetEvent(true); + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + private readonly ISubscriberExecutor _executor; + private readonly ILogger _logger; + + private readonly BlockingCollection _publishedMessageQueue = + new BlockingCollection(new ConcurrentQueue()); - private readonly TimeSpan _pollingDelay; - private readonly IQueueExecutorFactory _queueExecutorFactory; + private readonly BlockingCollection _receivedMessageQueue = + new BlockingCollection(new ConcurrentQueue()); - public DefaultDispatcher(IQueueExecutorFactory queueExecutorFactory, - IOptions capOptions) + private readonly IPublishMessageSender _sender; + + public Dispatcher(ILogger logger, + IPublishMessageSender sender, + ISubscriberExecutor executor) { - _queueExecutorFactory = queueExecutorFactory; - _pollingDelay = TimeSpan.FromSeconds(capOptions.Value.PollingDelay); - } + _logger = logger; + _sender = sender; + _executor = executor; - public bool Waiting { get; private set; } + Task.Factory.StartNew(Sending); + Task.Factory.StartNew(Processing); + } - public Task ProcessAsync(ProcessingContext context) + public void EnqueueToPublish(CapPublishedMessage message) { - if (context == null) - throw new ArgumentNullException(nameof(context)); + _publishedMessageQueue.Add(message); + } - context.ThrowIfStopping(); + public void EnqueueToExecute(CapReceivedMessage message) + { + _receivedMessageQueue.Add(message); + } - return ProcessCoreAsync(context); + public void Dispose() + { + _cts.Cancel(); } - public async Task ProcessCoreAsync(ProcessingContext context) + private void Sending() { try { - var worked = await Step(context); - - context.ThrowIfStopping(); - - Waiting = true; - - if (!worked) + while (!_publishedMessageQueue.IsCompleted) { - var token = GetTokenToWaitOn(context); - await WaitHandleEx.WaitAnyAsync(PulseEvent, token.WaitHandle, _pollingDelay); + if (_publishedMessageQueue.TryTake(out var message, 100, _cts.Token)) + { + try + { + _sender.SendAsync(message); + } + catch (Exception ex) + { + _logger.LogError(ex, $"An exception occurred when sending a message to the MQ. Topic:{message.Name}, Id:{message.Id}"); + } + } } } - finally + catch (OperationCanceledException) { - Waiting = false; + // expected } } - protected virtual CancellationToken GetTokenToWaitOn(ProcessingContext context) - { - return context.CancellationToken; - } - - private async Task Step(ProcessingContext context) + private void Processing() { - IFetchedMessage fetched; - using (var scopedContext = context.CreateScope()) + try { - var provider = scopedContext.Provider; - var connection = provider.GetRequiredService(); - - if ((fetched = await connection.FetchNextMessageAsync()) != null) - using (fetched) - { - var queueExecutor = _queueExecutorFactory.GetInstance(fetched.MessageType); - await queueExecutor.ExecuteAsync(connection, fetched); - } + foreach (var message in _receivedMessageQueue.GetConsumingEnumerable(_cts.Token)) + { + _executor.ExecuteAsync(message); + } + } + catch (OperationCanceledException) + { + // expected } - return fetched != null; } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/IDispatcher.cs b/src/DotNetCore.CAP/Processor/IDispatcher.cs deleted file mode 100644 index f612d02..0000000 --- a/src/DotNetCore.CAP/Processor/IDispatcher.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace DotNetCore.CAP.Processor -{ - public interface IDispatcher : IProcessor - { - bool Waiting { get; } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/IProcessingServer.Cap.cs b/src/DotNetCore.CAP/Processor/IProcessingServer.Cap.cs index 2118021..a05c302 100644 --- a/src/DotNetCore.CAP/Processor/IProcessingServer.Cap.cs +++ b/src/DotNetCore.CAP/Processor/IProcessingServer.Cap.cs @@ -1,11 +1,13 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace DotNetCore.CAP.Processor { @@ -14,39 +16,30 @@ namespace DotNetCore.CAP.Processor private readonly CancellationTokenSource _cts; private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; - private readonly IList _messageDispatchers; - private readonly CapOptions _options; private readonly IServiceProvider _provider; + private Task _compositeTask; private ProcessingContext _context; private bool _disposed; - private IProcessor[] _processors; - public CapProcessingServer( ILogger logger, ILoggerFactory loggerFactory, - IServiceProvider provider, - IOptions options) + IServiceProvider provider) { _logger = logger; _loggerFactory = loggerFactory; _provider = provider; - _options = options.Value; _cts = new CancellationTokenSource(); - _messageDispatchers = new List(); } public void Start() { - var processorCount = _options.QueueProcessorCount; - _processors = GetProcessors(processorCount); - - _logger.ServerStarting(processorCount, _processors.Length); + _logger.ServerStarting(); _context = new ProcessingContext(_provider, _cts.Token); - var processorTasks = _processors + var processorTasks = GetProcessors() .Select(InfiniteRetry) .Select(p => p.ProcessAsync(_context)); _compositeTask = Task.WhenAll(processorTasks); @@ -54,37 +47,41 @@ namespace DotNetCore.CAP.Processor public void Pulse() { - if (!AllProcessorsWaiting()) - return; - - _logger.LogTrace("Pulsing the Queuer."); - - PublishQueuer.PulseEvent.Set(); + _logger.LogTrace("Pulsing the processor."); } public void Dispose() { if (_disposed) + { return; - _disposed = true; + } - _logger.ServerShuttingDown(); - _cts.Cancel(); try { - _compositeTask.Wait((int) TimeSpan.FromSeconds(10).TotalMilliseconds); + _disposed = true; + + _logger.ServerShuttingDown(); + _cts.Cancel(); + + _compositeTask?.Wait((int)TimeSpan.FromSeconds(10).TotalMilliseconds); } catch (AggregateException ex) { var innerEx = ex.InnerExceptions[0]; if (!(innerEx is OperationCanceledException)) + { _logger.ExpectedOperationCanceledException(innerEx); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "An exception was occured when disposing."); + } + finally + { + _logger.LogInformation("### CAP shutdown!"); } - } - - private bool AllProcessorsWaiting() - { - return _messageDispatchers.All(processor => processor.Waiting); } private IProcessor InfiniteRetry(IProcessor inner) @@ -92,21 +89,13 @@ namespace DotNetCore.CAP.Processor return new InfiniteRetryProcessor(inner, _loggerFactory); } - private IProcessor[] GetProcessors(int processorCount) + private IProcessor[] GetProcessors() { - var returnedProcessors = new List(); - for (var i = 0; i < processorCount; i++) + var returnedProcessors = new List { - var messageProcessors = _provider.GetRequiredService(); - _messageDispatchers.Add(messageProcessors); - } - returnedProcessors.AddRange(_messageDispatchers); - - returnedProcessors.Add(_provider.GetRequiredService()); - returnedProcessors.Add(_provider.GetRequiredService()); - returnedProcessors.Add(_provider.GetRequiredService()); - - returnedProcessors.Add(_provider.GetRequiredService()); + _provider.GetRequiredService(), + _provider.GetRequiredService() + }; return returnedProcessors.ToArray(); } diff --git a/src/DotNetCore.CAP/Processor/IProcessor.Failed.cs b/src/DotNetCore.CAP/Processor/IProcessor.Failed.cs deleted file mode 100644 index 6c158e4..0000000 --- a/src/DotNetCore.CAP/Processor/IProcessor.Failed.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Threading.Tasks; -using DotNetCore.CAP.Abstractions; -using DotNetCore.CAP.Infrastructure; -using DotNetCore.CAP.Models; -using DotNetCore.CAP.Processor.States; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace DotNetCore.CAP.Processor -{ - public class FailedProcessor : IProcessor - { - private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); - private readonly ILogger _logger; - private readonly CapOptions _options; - private readonly IServiceProvider _provider; - private readonly IStateChanger _stateChanger; - private readonly ISubscriberExecutor _subscriberExecutor; - private readonly IPublishExecutor _publishExecutor; - private readonly TimeSpan _waitingInterval; - - public FailedProcessor( - IOptions options, - ILogger logger, - IServiceProvider provider, - IStateChanger stateChanger, - ISubscriberExecutor subscriberExecutor, - IPublishExecutor publishExecutor) - { - _options = options.Value; - _logger = logger; - _provider = provider; - _stateChanger = stateChanger; - _subscriberExecutor = subscriberExecutor; - _publishExecutor = publishExecutor; - _waitingInterval = TimeSpan.FromSeconds(_options.FailedRetryInterval); - } - - public async Task ProcessAsync(ProcessingContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - using (var scope = _provider.CreateScope()) - { - var provider = scope.ServiceProvider; - var connection = provider.GetRequiredService(); - - await Task.WhenAll( - ProcessPublishedAsync(connection, context), - ProcessReceivedAsync(connection, context)); - - await context.WaitAsync(_waitingInterval); - } - } - - private async Task ProcessPublishedAsync(IStorageConnection connection, ProcessingContext context) - { - var messages = await connection.GetFailedPublishedMessages(); - var hasException = false; - - foreach (var message in messages) - { - if (message.Retries > _options.FailedRetryCount) - continue; - - if (!hasException) - try - { - _options.FailedCallback?.Invoke(MessageType.Publish, message.Name, message.Content); - } - catch (Exception ex) - { - hasException = true; - _logger.LogWarning("Failed call-back method raised an exception:" + ex.Message); - } - - using (var transaction = connection.CreateTransaction()) - { - try - { - await _publishExecutor.PublishAsync(message.Name, message.Content); - - _stateChanger.ChangeState(message, new SucceededState(), transaction); - } - catch (Exception e) - { - message.Content = Helper.AddExceptionProperty(message.Content, e); - message.Retries++; - transaction.UpdateMessage(message); - } - await transaction.CommitAsync(); - } - - context.ThrowIfStopping(); - - await context.WaitAsync(_delay); - } - } - - private async Task ProcessReceivedAsync(IStorageConnection connection, ProcessingContext context) - { - var messages = await connection.GetFailedReceivedMessages(); - var hasException = false; - - foreach (var message in messages) - { - if (message.Retries > _options.FailedRetryCount) - continue; - - if (!hasException) - try - { - _options.FailedCallback?.Invoke(MessageType.Subscribe, message.Name, message.Content); - } - catch (Exception ex) - { - hasException = true; - _logger.LogWarning("Failed call-back method raised an exception:" + ex.Message); - } - - using (var transaction = connection.CreateTransaction()) - { - var ret = await _subscriberExecutor.ExecuteAsync(message); - if (ret.Succeeded) - { - _stateChanger.ChangeState(message, new SucceededState(), transaction); - } - else - { - message.Retries++; - message.Content = Helper.AddExceptionProperty(message.Content, ret.Exception); - transaction.UpdateMessage(message); - } - await transaction.CommitAsync(); - } - - context.ThrowIfStopping(); - - await context.WaitAsync(_delay); - } - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/IProcessor.InfiniteRetry.cs b/src/DotNetCore.CAP/Processor/IProcessor.InfiniteRetry.cs index ed0508e..b7b5d43 100644 --- a/src/DotNetCore.CAP/Processor/IProcessor.InfiniteRetry.cs +++ b/src/DotNetCore.CAP/Processor/IProcessor.InfiniteRetry.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -20,6 +23,7 @@ namespace DotNetCore.CAP.Processor public async Task ProcessAsync(ProcessingContext context) { while (!context.IsStopping) + { try { await _inner.ProcessAsync(context); @@ -32,6 +36,7 @@ namespace DotNetCore.CAP.Processor { _logger.LogWarning(1, ex, "Processor '{ProcessorName}' failed. Retrying...", _inner.ToString()); } + } } public override string ToString() diff --git a/src/DotNetCore.CAP/Processor/IProcessor.NeedRetry.cs b/src/DotNetCore.CAP/Processor/IProcessor.NeedRetry.cs new file mode 100644 index 0000000..2f7b829 --- /dev/null +++ b/src/DotNetCore.CAP/Processor/IProcessor.NeedRetry.cs @@ -0,0 +1,185 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using DotNetCore.CAP.Infrastructure; +using DotNetCore.CAP.Models; +using DotNetCore.CAP.Processor.States; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace DotNetCore.CAP.Processor +{ + public class NeedRetryMessageProcessor : IProcessor + { + private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); + private readonly ILogger _logger; + private readonly CapOptions _options; + private readonly IPublishExecutor _publishExecutor; + private readonly IStateChanger _stateChanger; + private readonly ISubscriberExecutor _subscriberExecutor; + private readonly TimeSpan _waitingInterval; + + public NeedRetryMessageProcessor( + IOptions options, + ILogger logger, + IStateChanger stateChanger, + ISubscriberExecutor subscriberExecutor, + IPublishExecutor publishExecutor) + { + _options = options.Value; + _logger = logger; + _stateChanger = stateChanger; + _subscriberExecutor = subscriberExecutor; + _publishExecutor = publishExecutor; + _waitingInterval = TimeSpan.FromSeconds(_options.FailedRetryInterval); + } + + public async Task ProcessAsync(ProcessingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var connection = context.Provider.GetRequiredService(); + + await Task.WhenAll( + ProcessPublishedAsync(connection, context), + ProcessReceivedAsync(connection, context)); + + await context.WaitAsync(_waitingInterval); + } + + private async Task ProcessPublishedAsync(IStorageConnection connection, ProcessingContext context) + { + var messages = await connection.GetPublishedMessagesOfNeedRetry(); + var hasException = false; + + foreach (var message in messages) + { + if (message.Retries > _options.FailedRetryCount) + { + continue; + } + + using (var transaction = connection.CreateTransaction()) + { + var result = await _publishExecutor.PublishAsync(message.Name, message.Content); + if (result.Succeeded) + { + _stateChanger.ChangeState(message, new SucceededState(), transaction); + _logger.LogInformation("The message was sent successfully during the retry. MessageId:" + message.Id); + } + else + { + message.Content = Helper.AddExceptionProperty(message.Content, result.Exception); + message.Retries++; + if (message.StatusName == StatusName.Scheduled) + { + message.ExpiresAt = GetDueTime(message.Added, message.Retries); + message.StatusName = StatusName.Failed; + } + transaction.UpdateMessage(message); + + if (message.Retries >= _options.FailedRetryCount) + { + _logger.LogError($"The message still sent failed after {_options.FailedRetryCount} retries. We will stop retrying the message. " + + "MessageId:" + message.Id); + if (message.Retries == _options.FailedRetryCount) + { + if (!hasException) + { + try + { + _options.FailedThresholdCallback?.Invoke(MessageType.Publish, message.Name, message.Content); + } + catch (Exception ex) + { + hasException = true; + _logger.LogWarning("Failed call-back method raised an exception:" + ex.Message); + } + } + } + } + } + await transaction.CommitAsync(); + } + + context.ThrowIfStopping(); + + await context.WaitAsync(_delay); + } + } + + private async Task ProcessReceivedAsync(IStorageConnection connection, ProcessingContext context) + { + var messages = await connection.GetReceivedMessagesOfNeedRetry(); + var hasException = false; + + foreach (var message in messages) + { + if (message.Retries > _options.FailedRetryCount) + { + continue; + } + + using (var transaction = connection.CreateTransaction()) + { + var result = await _subscriberExecutor.ExecuteAsync(message); + if (result.Succeeded) + { + _stateChanger.ChangeState(message, new SucceededState(), transaction); + _logger.LogInformation("The message was execute successfully during the retry. MessageId:" + message.Id); + } + else + { + message.Content = Helper.AddExceptionProperty(message.Content, result.Exception); + message.Retries++; + if (message.StatusName == StatusName.Scheduled) + { + message.ExpiresAt = GetDueTime(message.Added, message.Retries); + message.StatusName = StatusName.Failed; + } + transaction.UpdateMessage(message); + + if (message.Retries >= _options.FailedRetryCount) + { + _logger.LogError($"[Subscriber]The message still executed failed after {_options.FailedRetryCount} retries. " + + "We will stop retrying to execute the message. message id:" + message.Id); + + if (message.Retries == _options.FailedRetryCount) + { + if (!hasException) + { + try + { + _options.FailedThresholdCallback?.Invoke(MessageType.Subscribe, message.Name, message.Content); + } + catch (Exception ex) + { + hasException = true; + _logger.LogWarning("Failed call-back method raised an exception:" + ex.Message); + } + } + } + } + } + await transaction.CommitAsync(); + } + + context.ThrowIfStopping(); + + await context.WaitAsync(_delay); + } + } + + public DateTime GetDueTime(DateTime addedTime, int retries) + { + var retryBehavior = RetryBehavior.DefaultRetry; + return addedTime.AddSeconds(retryBehavior.RetryIn(retries)); + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/IProcessor.PublishQueuer.cs b/src/DotNetCore.CAP/Processor/IProcessor.PublishQueuer.cs deleted file mode 100644 index 90d1b5f..0000000 --- a/src/DotNetCore.CAP/Processor/IProcessor.PublishQueuer.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using DotNetCore.CAP.Infrastructure; -using DotNetCore.CAP.Models; -using DotNetCore.CAP.Processor.States; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace DotNetCore.CAP.Processor -{ - public class PublishQueuer : IProcessor - { - public static readonly AutoResetEvent PulseEvent = new AutoResetEvent(true); - private readonly ILogger _logger; - private readonly TimeSpan _pollingDelay; - private readonly IServiceProvider _provider; - private readonly IStateChanger _stateChanger; - - public PublishQueuer( - ILogger logger, - IOptions options, - IStateChanger stateChanger, - IServiceProvider provider) - { - _logger = logger; - _stateChanger = stateChanger; - _provider = provider; - - var capOptions = options.Value; - _pollingDelay = TimeSpan.FromSeconds(capOptions.PollingDelay); - } - - public async Task ProcessAsync(ProcessingContext context) - { - _logger.LogDebug("Publish Queuer start calling."); - using (var scope = _provider.CreateScope()) - { - CapPublishedMessage sentMessage; - var provider = scope.ServiceProvider; - var connection = provider.GetRequiredService(); - - while ( - !context.IsStopping && - (sentMessage = await connection.GetNextPublishedMessageToBeEnqueuedAsync()) != null) - - { - var state = new EnqueuedState(); - - using (var transaction = connection.CreateTransaction()) - { - _stateChanger.ChangeState(sentMessage, state, transaction); - await transaction.CommitAsync(); - } - } - } - - context.ThrowIfStopping(); - - DefaultDispatcher.PulseEvent.Set(); - - await WaitHandleEx.WaitAnyAsync(PulseEvent, - context.CancellationToken.WaitHandle, _pollingDelay); - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/IProcessor.SubscribeQueuer.cs b/src/DotNetCore.CAP/Processor/IProcessor.SubscribeQueuer.cs deleted file mode 100644 index 29913d5..0000000 --- a/src/DotNetCore.CAP/Processor/IProcessor.SubscribeQueuer.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using DotNetCore.CAP.Infrastructure; -using DotNetCore.CAP.Models; -using DotNetCore.CAP.Processor.States; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace DotNetCore.CAP.Processor -{ - public class SubscribeQueuer : IProcessor - { - internal static readonly AutoResetEvent PulseEvent = new AutoResetEvent(true); - private readonly ILogger _logger; - private readonly TimeSpan _pollingDelay; - private readonly IServiceProvider _provider; - private readonly IStateChanger _stateChanger; - - public SubscribeQueuer( - ILogger logger, - IOptions options, - IStateChanger stateChanger, - IServiceProvider provider) - { - _logger = logger; - _stateChanger = stateChanger; - _provider = provider; - - var capOptions = options.Value; - _pollingDelay = TimeSpan.FromSeconds(capOptions.PollingDelay); - } - - public async Task ProcessAsync(ProcessingContext context) - { - _logger.LogDebug("SubscribeQueuer start calling."); - using (var scope = _provider.CreateScope()) - { - CapReceivedMessage message; - var provider = scope.ServiceProvider; - var connection = provider.GetRequiredService(); - - while ( - !context.IsStopping && - (message = await connection.GetNextReceivedMessageToBeEnqueuedAsync()) != null) - - { - var state = new EnqueuedState(); - - using (var transaction = connection.CreateTransaction()) - { - _stateChanger.ChangeState(message, state, transaction); - await transaction.CommitAsync(); - } - } - } - - context.ThrowIfStopping(); - - DefaultDispatcher.PulseEvent.Set(); - - await WaitHandleEx.WaitAnyAsync(PulseEvent, - context.CancellationToken.WaitHandle, _pollingDelay); - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/IProcessor.cs b/src/DotNetCore.CAP/Processor/IProcessor.cs index 20d484f..3e83c6e 100644 --- a/src/DotNetCore.CAP/Processor/IProcessor.cs +++ b/src/DotNetCore.CAP/Processor/IProcessor.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; namespace DotNetCore.CAP.Processor { diff --git a/src/DotNetCore.CAP/Processor/ProcessingContext.cs b/src/DotNetCore.CAP/Processor/ProcessingContext.cs index 5129532..6a280b2 100644 --- a/src/DotNetCore.CAP/Processor/ProcessingContext.cs +++ b/src/DotNetCore.CAP/Processor/ProcessingContext.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; diff --git a/src/DotNetCore.CAP/Processor/RetryBehavior.cs b/src/DotNetCore.CAP/Processor/RetryBehavior.cs index d1767c0..05115fc 100644 --- a/src/DotNetCore.CAP/Processor/RetryBehavior.cs +++ b/src/DotNetCore.CAP/Processor/RetryBehavior.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; namespace DotNetCore.CAP.Processor { @@ -17,9 +20,9 @@ namespace DotNetCore.CAP.Processor static RetryBehavior() { - DefaultRetryCount = 15; + DefaultRetryCount = 3; DefaultRetryInThunk = retries => - (int) Math.Round(Math.Pow(retries - 1, 4) + 15 + _random.Next(30) * retries); + (int) Math.Round(Math.Pow(retries - 1, 4) + 3 + _random.Next(30) * retries); DefaultRetry = new RetryBehavior(true); NoRetry = new RetryBehavior(false); @@ -39,7 +42,12 @@ namespace DotNetCore.CAP.Processor public RetryBehavior(bool retry, int retryCount, Func retryInThunk) { if (retry) - if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Can't be negative."); + { + if (retryCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(retryCount), "Can't be negative."); + } + } Retry = retry; RetryCount = retryCount; diff --git a/src/DotNetCore.CAP/Processor/States/IState.Enqueued.cs b/src/DotNetCore.CAP/Processor/States/IState.Enqueued.cs deleted file mode 100644 index ca53699..0000000 --- a/src/DotNetCore.CAP/Processor/States/IState.Enqueued.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP.Processor.States -{ - public class EnqueuedState : IState - { - public const string StateName = "Enqueued"; - - public TimeSpan? ExpiresAfter => null; - - public string Name => StateName; - - public void Apply(CapPublishedMessage message, IStorageTransaction transaction) - { - transaction.EnqueueMessage(message); - } - - public void Apply(CapReceivedMessage message, IStorageTransaction transaction) - { - transaction.EnqueueMessage(message); - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/States/IState.Failed.cs b/src/DotNetCore.CAP/Processor/States/IState.Failed.cs index 49fda9b..4fb34b6 100644 --- a/src/DotNetCore.CAP/Processor/States/IState.Failed.cs +++ b/src/DotNetCore.CAP/Processor/States/IState.Failed.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Processor.States diff --git a/src/DotNetCore.CAP/Processor/States/IState.Processing.cs b/src/DotNetCore.CAP/Processor/States/IState.Processing.cs deleted file mode 100644 index 9827e76..0000000 --- a/src/DotNetCore.CAP/Processor/States/IState.Processing.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using DotNetCore.CAP.Models; - -namespace DotNetCore.CAP.Processor.States -{ - public class ProcessingState : IState - { - public const string StateName = "Processing"; - - public TimeSpan? ExpiresAfter => null; - - public string Name => StateName; - - public void Apply(CapPublishedMessage message, IStorageTransaction transaction) - { - } - - public void Apply(CapReceivedMessage message, IStorageTransaction transaction) - { - } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Processor/States/IState.Scheduled.cs b/src/DotNetCore.CAP/Processor/States/IState.Scheduled.cs index 49f0c95..b5cd805 100644 --- a/src/DotNetCore.CAP/Processor/States/IState.Scheduled.cs +++ b/src/DotNetCore.CAP/Processor/States/IState.Scheduled.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Processor.States diff --git a/src/DotNetCore.CAP/Processor/States/IState.Succeeded.cs b/src/DotNetCore.CAP/Processor/States/IState.Succeeded.cs index fdb41e5..3c0d16d 100644 --- a/src/DotNetCore.CAP/Processor/States/IState.Succeeded.cs +++ b/src/DotNetCore.CAP/Processor/States/IState.Succeeded.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Processor.States diff --git a/src/DotNetCore.CAP/Processor/States/IState.cs b/src/DotNetCore.CAP/Processor/States/IState.cs index c43fc74..f1b8417 100644 --- a/src/DotNetCore.CAP/Processor/States/IState.cs +++ b/src/DotNetCore.CAP/Processor/States/IState.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Processor.States diff --git a/src/DotNetCore.CAP/Processor/States/IStateChanger.Default.cs b/src/DotNetCore.CAP/Processor/States/IStateChanger.Default.cs index 93af696..436dcb6 100644 --- a/src/DotNetCore.CAP/Processor/States/IStateChanger.Default.cs +++ b/src/DotNetCore.CAP/Processor/States/IStateChanger.Default.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Processor.States @@ -9,9 +12,13 @@ namespace DotNetCore.CAP.Processor.States { var now = DateTime.Now; if (state.ExpiresAfter != null) + { message.ExpiresAt = now.Add(state.ExpiresAfter.Value); + } else + { message.ExpiresAt = null; + } message.StatusName = state.Name; state.Apply(message, transaction); @@ -22,9 +29,13 @@ namespace DotNetCore.CAP.Processor.States { var now = DateTime.Now; if (state.ExpiresAfter != null) + { message.ExpiresAt = now.Add(state.ExpiresAfter.Value); + } else + { message.ExpiresAt = null; + } message.StatusName = state.Name; state.Apply(message, transaction); diff --git a/src/DotNetCore.CAP/Processor/States/IStateChanger.Extensions.cs b/src/DotNetCore.CAP/Processor/States/IStateChanger.Extensions.cs index 6bd1d12..b0e1e75 100644 --- a/src/DotNetCore.CAP/Processor/States/IStateChanger.Extensions.cs +++ b/src/DotNetCore.CAP/Processor/States/IStateChanger.Extensions.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Processor.States diff --git a/src/DotNetCore.CAP/Processor/States/IStateChanger.cs b/src/DotNetCore.CAP/Processor/States/IStateChanger.cs index 949ea31..c15e23a 100644 --- a/src/DotNetCore.CAP/Processor/States/IStateChanger.cs +++ b/src/DotNetCore.CAP/Processor/States/IStateChanger.cs @@ -1,4 +1,7 @@ -using DotNetCore.CAP.Models; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using DotNetCore.CAP.Models; namespace DotNetCore.CAP.Processor.States { diff --git a/src/DotNetCore.CAP/Properties/AssemblyInfo.cs b/src/DotNetCore.CAP/Properties/AssemblyInfo.cs index 0e62b37..3008bc6 100644 --- a/src/DotNetCore.CAP/Properties/AssemblyInfo.cs +++ b/src/DotNetCore.CAP/Properties/AssemblyInfo.cs @@ -1,3 +1,6 @@ -using System.Runtime.CompilerServices; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("DotNetCore.CAP.Test")] \ No newline at end of file diff --git a/src/DotNetCore.CAP/QueueExecutorFactory.cs b/src/DotNetCore.CAP/QueueExecutorFactory.cs deleted file mode 100644 index 4e6882f..0000000 --- a/src/DotNetCore.CAP/QueueExecutorFactory.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Linq; -using DotNetCore.CAP.Models; -using Microsoft.Extensions.DependencyInjection; - -namespace DotNetCore.CAP -{ - public class QueueExecutorFactory : IQueueExecutorFactory - { - private readonly IServiceProvider _serviceProvider; - - public QueueExecutorFactory(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public IQueueExecutor GetInstance(MessageType messageType) - { - var queueExecutors = _serviceProvider.GetServices(); - - return messageType == MessageType.Publish - ? queueExecutors.FirstOrDefault(x => x is BasePublishQueueExecutor) - : queueExecutors.FirstOrDefault(x => !(x is BasePublishQueueExecutor)); - } - } -} \ No newline at end of file diff --git a/test/DotNetCore.CAP.MySql.Test/DatabaseTestHost.cs b/test/DotNetCore.CAP.MySql.Test/DatabaseTestHost.cs index 1fac1b6..66ffa33 100644 --- a/test/DotNetCore.CAP.MySql.Test/DatabaseTestHost.cs +++ b/test/DotNetCore.CAP.MySql.Test/DatabaseTestHost.cs @@ -1,6 +1,5 @@ using System.Threading; using Dapper; -using Microsoft.EntityFrameworkCore; namespace DotNetCore.CAP.MySql.Test { @@ -59,8 +58,7 @@ CREATE DATABASE `{databaseName}`;"); { connection.Execute($@" TRUNCATE TABLE `cap.published`; -TRUNCATE TABLE `cap.received`; -TRUNCATE TABLE `cap.queue`;"); +TRUNCATE TABLE `cap.received`;"); } } } diff --git a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj index e7cd172..f96229e 100644 --- a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj +++ b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj @@ -15,19 +15,19 @@ - - + + - + - - - - - - - + + + + + + + \ No newline at end of file diff --git a/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs b/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs index 50bc489..aefa831 100644 --- a/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs +++ b/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs @@ -40,27 +40,6 @@ namespace DotNetCore.CAP.MySql.Test Assert.Equal(StatusName.Scheduled, message.StatusName); } - [Fact] - public async Task FetchNextMessageAsync_Test() - { - var sql = "INSERT INTO `Cap.Queue`(`MessageId`,`MessageType`) VALUES(@MessageId,@MessageType);"; - var queue = new CapQueue - { - MessageId = 3333, - MessageType = MessageType.Publish - }; - using (var connection = ConnectionUtil.CreateConnection()) - { - connection.Execute(sql, queue); - } - using (var fetchedMessage = await _storage.FetchNextMessageAsync()) - { - Assert.NotNull(fetchedMessage); - Assert.Equal(MessageType.Publish, fetchedMessage.MessageType); - Assert.Equal(3333, fetchedMessage.MessageId); - } - } - [Fact] public async Task StoreReceivedMessageAsync_Test() { @@ -110,25 +89,5 @@ namespace DotNetCore.CAP.MySql.Test Assert.Equal("MySqlStorageConnectionTest", message.Name); Assert.Equal("mygroup", message.Group); } - - [Fact] - public async Task GetNextReceviedMessageToBeEnqueuedAsync_Test() - { - var receivedMessage = new CapReceivedMessage - { - Name = "MySqlStorageConnectionTest", - Content = "", - Group = "mygroup", - StatusName = StatusName.Scheduled - }; - await _storage.StoreReceivedMessageAsync(receivedMessage); - - var message = await _storage.GetNextReceivedMessageToBeEnqueuedAsync(); - - Assert.NotNull(message); - Assert.Equal(StatusName.Scheduled, message.StatusName); - Assert.Equal("MySqlStorageConnectionTest", message.Name); - Assert.Equal("mygroup", message.Group); - } } } \ No newline at end of file diff --git a/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs b/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs index 715cb5c..49337f5 100644 --- a/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs +++ b/test/DotNetCore.CAP.MySql.Test/MySqlStorageTest.cs @@ -30,7 +30,6 @@ namespace DotNetCore.CAP.MySql.Test [Theory] [InlineData("cap.published")] - [InlineData("cap.queue")] [InlineData("cap.received")] public void DatabaseTable_IsExists(string tableName) { diff --git a/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs b/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs index 66ae8e1..ec59a74 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/DatabaseTestHost.cs @@ -59,8 +59,7 @@ CREATE DATABASE ""{databaseName}"";"); { connection.Execute($@" TRUNCATE TABLE ""cap"".""published""; -TRUNCATE TABLE ""cap"".""received""; -TRUNCATE TABLE ""cap"".""queue"";"); +TRUNCATE TABLE ""cap"".""received"";"); } } } diff --git a/test/DotNetCore.CAP.PostgreSql.Test/DotNetCore.CAP.PostgreSql.Test.csproj b/test/DotNetCore.CAP.PostgreSql.Test/DotNetCore.CAP.PostgreSql.Test.csproj index 2c34952..2346ca5 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/DotNetCore.CAP.PostgreSql.Test.csproj +++ b/test/DotNetCore.CAP.PostgreSql.Test/DotNetCore.CAP.PostgreSql.Test.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs index df9dd64..b1cb25c 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs @@ -40,26 +40,6 @@ namespace DotNetCore.CAP.PostgreSql.Test Assert.Equal(StatusName.Scheduled, message.StatusName); } - [Fact] - public async Task FetchNextMessageAsync_Test() - { - var sql = @"INSERT INTO ""cap"".""queue""(""MessageId"",""MessageType"") VALUES(@MessageId,@MessageType);"; - var queue = new CapQueue - { - MessageId = 3333, - MessageType = MessageType.Publish - }; - using (var connection = ConnectionUtil.CreateConnection()) - { - connection.Execute(sql, queue); - } - var fetchedMessage = await _storage.FetchNextMessageAsync(); - fetchedMessage.Dispose(); - Assert.NotNull(fetchedMessage); - Assert.Equal(MessageType.Publish, fetchedMessage.MessageType); - Assert.Equal(3333, fetchedMessage.MessageId); - } - [Fact] public async Task StoreReceivedMessageAsync_Test() { @@ -109,25 +89,5 @@ namespace DotNetCore.CAP.PostgreSql.Test Assert.Equal("PostgreSqlStorageConnectionTest", message.Name); Assert.Equal("mygroup", message.Group); } - - [Fact] - public async Task GetNextReceviedMessageToBeEnqueuedAsync_Test() - { - var receivedMessage = new CapReceivedMessage - { - Name = "PostgreSqlStorageConnectionTest", - Content = "", - Group = "mygroup", - StatusName = StatusName.Scheduled - }; - await _storage.StoreReceivedMessageAsync(receivedMessage); - - var message = await _storage.GetNextReceivedMessageToBeEnqueuedAsync(); - - Assert.NotNull(message); - Assert.Equal(StatusName.Scheduled, message.StatusName); - Assert.Equal("PostgreSqlStorageConnectionTest", message.Name); - Assert.Equal("mygroup", message.Group); - } } } \ No newline at end of file diff --git a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs index c4f5748..ec0bc2b 100644 --- a/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs +++ b/test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs @@ -32,7 +32,6 @@ namespace DotNetCore.CAP.PostgreSql.Test [Theory] [InlineData("cap.published")] - [InlineData("cap.queue")] [InlineData("cap.received")] public void DatabaseTable_IsExists(string tableName) { diff --git a/test/DotNetCore.CAP.SqlServer.Test/DatabaseTestHost.cs b/test/DotNetCore.CAP.SqlServer.Test/DatabaseTestHost.cs index 5bf5a7d..96828f6 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/DatabaseTestHost.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/DatabaseTestHost.cs @@ -2,7 +2,6 @@ using System.Data; using System.Data.SqlClient; using System.Threading; using Dapper; -using Microsoft.EntityFrameworkCore; namespace DotNetCore.CAP.SqlServer.Test { diff --git a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj index 78c7cdd..d8e70d4 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj +++ b/test/DotNetCore.CAP.SqlServer.Test/DotNetCore.CAP.SqlServer.Test.csproj @@ -12,19 +12,19 @@ - - + + - + - - - - - - - + + + + + + + diff --git a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageConnectionTest.cs b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageConnectionTest.cs index 84eba4e..3408ee3 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageConnectionTest.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageConnectionTest.cs @@ -39,27 +39,7 @@ namespace DotNetCore.CAP.SqlServer.Test Assert.Equal("SqlServerStorageConnectionTest", message.Name); Assert.Equal(StatusName.Scheduled, message.StatusName); } - - [Fact] - public async Task FetchNextMessageAsync_Test() - { - var sql = "INSERT INTO [Cap].[Queue]([MessageId],[MessageType]) VALUES(@MessageId,@MessageType);"; - var queue = new CapQueue - { - MessageId = 3333, - MessageType = MessageType.Publish - }; - using (var connection = ConnectionUtil.CreateConnection()) - { - connection.Execute(sql, queue); - } - var fetchedMessage = await _storage.FetchNextMessageAsync(); - fetchedMessage.Dispose(); - Assert.NotNull(fetchedMessage); - Assert.Equal(MessageType.Publish, fetchedMessage.MessageType); - Assert.Equal(3333, fetchedMessage.MessageId); - } - + [Fact] public async Task StoreReceivedMessageAsync_Test() { @@ -109,25 +89,5 @@ namespace DotNetCore.CAP.SqlServer.Test Assert.Equal("SqlServerStorageConnectionTest", message.Name); Assert.Equal("mygroup", message.Group); } - - [Fact] - public async Task GetNextReceviedMessageToBeEnqueuedAsync_Test() - { - var receivedMessage = new CapReceivedMessage - { - Name = "SqlServerStorageConnectionTest", - Content = "", - Group = "mygroup", - StatusName = StatusName.Scheduled - }; - await _storage.StoreReceivedMessageAsync(receivedMessage); - - var message = await _storage.GetNextReceivedMessageToBeEnqueuedAsync(); - - Assert.NotNull(message); - Assert.Equal(StatusName.Scheduled, message.StatusName); - Assert.Equal("SqlServerStorageConnectionTest", message.Name); - Assert.Equal("mygroup", message.Group); - } } } \ No newline at end of file diff --git a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs index af5fc41..427ed30 100644 --- a/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs +++ b/test/DotNetCore.CAP.SqlServer.Test/SqlServerStorageTest.cs @@ -25,7 +25,6 @@ SELECT 'False'"; [Theory] [InlineData("[Cap].[Published]")] - [InlineData("[Cap].[Queue]")] [InlineData("[Cap].[Received]")] public void DatabaseTable_IsExists(string tableName) { diff --git a/test/DotNetCore.CAP.Test/CallbackMessageSenderTest.cs b/test/DotNetCore.CAP.Test/CallbackMessageSenderTest.cs index a29772d..6aa9a44 100644 --- a/test/DotNetCore.CAP.Test/CallbackMessageSenderTest.cs +++ b/test/DotNetCore.CAP.Test/CallbackMessageSenderTest.cs @@ -36,7 +36,7 @@ namespace DotNetCore.CAP.Test { // Arrange _mockCallbackPublisher - .Setup(x => x.PublishAsync(It.IsAny())) + .Setup(x => x.PublishCallbackAsync(It.IsAny())) .Returns(Task.CompletedTask).Verifiable(); _mockContentSerializer diff --git a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs index 71154b5..8f255a4 100644 --- a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs +++ b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs @@ -28,7 +28,7 @@ namespace DotNetCore.CAP.Test var selector = _provider.GetRequiredService(); var candidates = selector.SelectCandidates(); - Assert.Equal(2, candidates.Count); + Assert.Equal(6, candidates.Count); } [Fact] @@ -42,6 +42,66 @@ namespace DotNetCore.CAP.Test Assert.NotNull(bestCandidates.MethodInfo); Assert.Equal(typeof(Task), bestCandidates.MethodInfo.ReturnType); } + + + [Theory] + [InlineData("Candidates.Asterisk")] + [InlineData("candidates.Asterisk")] + [InlineData("AAA.BBB.Asterisk")] + [InlineData("aaa.bbb.Asterisk")] + public void CanFindAsteriskTopic(string topic) + { + var selector = _provider.GetRequiredService(); + var candidates = selector.SelectCandidates(); + + var bestCandidates = selector.SelectBestCandidate(topic, candidates); + Assert.NotNull(bestCandidates); + } + + [Theory] + [InlineData("Candidates.Asterisk.AAA")] + [InlineData("AAA.BBB.CCC.Asterisk")] + [InlineData("aaa.BBB.ccc.Asterisk")] + [InlineData("Asterisk.aaa.bbb")] + public void CanNotFindAsteriskTopic(string topic) + { + var selector = _provider.GetRequiredService(); + var candidates = selector.SelectCandidates(); + + var bestCandidates = selector.SelectBestCandidate(topic, candidates); + Assert.Null(bestCandidates); + } + + [Theory] + [InlineData("Candidates.Pound.AAA")] + [InlineData("Candidates.Pound.AAA.BBB")] + [InlineData("AAA.Pound")] + [InlineData("aaa.Pound")] + [InlineData("aaa.bbb.Pound")] + [InlineData("aaa.BBB.Pound")] + public void CanFindPoundTopic(string topic) + { + var selector = _provider.GetRequiredService(); + var candidates = selector.SelectCandidates(); + + var bestCandidates = selector.SelectBestCandidate(topic, candidates); + Assert.NotNull(bestCandidates); + } + + [Theory] + [InlineData("Pound")] + [InlineData("aaa.Pound.AAA.BBB")] + [InlineData("Pound.AAA")] + [InlineData("Pound.aaa")] + [InlineData("AAA.Pound.aaa")] + public void CanNotFindPoundTopic(string topic) + { + var selector = _provider.GetRequiredService(); + var candidates = selector.SelectCandidates(); + + var bestCandidates = selector.SelectBestCandidate(topic, candidates); + Assert.Null(bestCandidates); + } } public class CandidatesTopic : TopicAttribute @@ -73,6 +133,21 @@ namespace DotNetCore.CAP.Test { Console.WriteLine("GetFoo2() method has bee excuted."); } + + [CandidatesTopic("*.*.Asterisk")] + [CandidatesTopic("*.Asterisk")] + public void GetFooAsterisk() + { + Console.WriteLine("GetFoo2Asterisk() method has bee excuted."); + } + + [CandidatesTopic("Candidates.Pound.#")] + [CandidatesTopic("#.Pound")] + public void GetFooPound() + { + Console.WriteLine("GetFoo2Pound() method has bee excuted."); + } + } public class CandidatesBarTest : IBarTest diff --git a/test/DotNetCore.CAP.Test/DiagnosticsTest.cs b/test/DotNetCore.CAP.Test/DiagnosticsTest.cs new file mode 100644 index 0000000..b3473a9 --- /dev/null +++ b/test/DotNetCore.CAP.Test/DiagnosticsTest.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using DotNetCore.CAP.Diagnostics; +using DotNetCore.CAP.Internal; +using Xunit; + +namespace DotNetCore.CAP.Test +{ + + public class DiagnosticsTest + { + private static readonly DiagnosticListener s_diagnosticListener = + new DiagnosticListener(CapDiagnosticListenerExtensions.DiagnosticListenerName); + + [Fact] + public void WritePublishBeforeTest() + { + Guid operationId = Guid.NewGuid(); + + DiagnosticsWapper(() => + { + var eventData = new BrokerPublishEventData(operationId, "", "", "", "", DateTimeOffset.UtcNow); + s_diagnosticListener.WritePublishBefore(eventData); + + }, kvp => + { + if (kvp.Key.Equals(CapDiagnosticListenerExtensions.CapBeforePublish)) + { + Assert.NotNull(kvp.Value); + Assert.IsType(kvp.Value); + Assert.Equal(operationId, ((BrokerPublishEventData)kvp.Value).OperationId); + } + }); + } + + [Fact] + public void WritePublishAfterTest() + { + Guid operationId = Guid.NewGuid(); + + DiagnosticsWapper(() => + { + var eventData = new BrokerPublishEndEventData(operationId, "", "", "", "", DateTimeOffset.UtcNow, TimeSpan.FromMinutes(1)); + s_diagnosticListener.WritePublishAfter(eventData); + + }, kvp => + { + if (kvp.Key.Equals(CapDiagnosticListenerExtensions.CapAfterPublish)) + { + Assert.NotNull(kvp.Value); + Assert.IsType(kvp.Value); + Assert.Equal(operationId, ((BrokerPublishEndEventData)kvp.Value).OperationId); + Assert.Equal(TimeSpan.FromMinutes(1), ((BrokerPublishEndEventData)kvp.Value).Duration); + } + }); + } + + [Fact] + public void WritePublishErrorTest() + { + Guid operationId = Guid.NewGuid(); + var ex = new Exception("WritePublishErrorTest"); + DiagnosticsWapper(() => + { + var eventData = new BrokerPublishErrorEventData(operationId, "", "", "", "", ex, DateTimeOffset.UtcNow, default(TimeSpan)); + s_diagnosticListener.WritePublishError(eventData); + + }, kvp => + { + if (kvp.Key.Equals(CapDiagnosticListenerExtensions.CapErrorPublish)) + { + Assert.NotNull(kvp.Value); + Assert.IsType(kvp.Value); + Assert.Equal(operationId, ((BrokerPublishErrorEventData)kvp.Value).OperationId); + Assert.Equal(ex, ((BrokerPublishErrorEventData)kvp.Value).Exception); + } + }); + } + + [Fact] + public void WriteConsumeBeforeTest() + { + Guid operationId = Guid.NewGuid(); + + DiagnosticsWapper(() => + { + var eventData = new BrokerConsumeEventData(operationId, "", "", "", "", DateTimeOffset.UtcNow); + s_diagnosticListener.WriteConsumeBefore(eventData); + + }, kvp => + { + if (kvp.Key.Equals(CapDiagnosticListenerExtensions.CapBeforeConsume)) + { + Assert.NotNull(kvp.Value); + Assert.IsType(kvp.Value); + Assert.Equal(operationId, ((BrokerConsumeEventData)kvp.Value).OperationId); + } + }); + } + + [Fact] + public void WriteConsumeAfterTest() + { + Guid operationId = Guid.NewGuid(); + + DiagnosticsWapper(() => + { + var eventData = new BrokerConsumeEndEventData(operationId, "", "", "", "", DateTimeOffset.UtcNow, TimeSpan.FromMinutes(1)); + s_diagnosticListener.WriteConsumeAfter(eventData); + + }, kvp => + { + if (kvp.Key.Equals(CapDiagnosticListenerExtensions.CapAfterConsume)) + { + Assert.NotNull(kvp.Value); + Assert.IsType(kvp.Value); + Assert.Equal(operationId, ((BrokerConsumeEndEventData)kvp.Value).OperationId); + Assert.Equal(TimeSpan.FromMinutes(1), ((BrokerConsumeEndEventData)kvp.Value).Duration); + } + }); + } + + [Fact] + public void WriteConsumeErrorTest() + { + Guid operationId = Guid.NewGuid(); + var ex = new Exception("WriteConsumeErrorTest"); + DiagnosticsWapper(() => + { + var eventData = new BrokerConsumeErrorEventData(operationId, "", "", "", "", ex, DateTimeOffset.UtcNow, default(TimeSpan)); + s_diagnosticListener.WriteConsumeError(eventData); + + }, kvp => + { + if (kvp.Key.Equals(CapDiagnosticListenerExtensions.CapErrorPublish)) + { + Assert.NotNull(kvp.Value); + Assert.IsType(kvp.Value); + Assert.Equal(operationId, ((BrokerConsumeErrorEventData)kvp.Value).OperationId); + Assert.Equal(ex, ((BrokerConsumeErrorEventData)kvp.Value).Exception); + } + }); + } + + [Fact] + public void WriteSubscriberInvokeBeforeTest() + { + DiagnosticsWapper(() => + { + s_diagnosticListener.WriteSubscriberInvokeBefore(FackConsumerContext()); + + }, kvp => + { + if (kvp.Key.Equals(CapDiagnosticListenerExtensions.CapBeforeSubscriberInvoke)) + { + Assert.NotNull(kvp.Value); + Assert.IsType(kvp.Value); + } + }); + } + + [Fact] + public void WriteSubscriberInvokeAfterTest() + { + Guid operationId = Guid.NewGuid(); + + DiagnosticsWapper(() => + { + s_diagnosticListener.WriteSubscriberInvokeAfter(operationId, FackConsumerContext(), DateTimeOffset.Now, TimeSpan.FromMinutes(1)); + + }, kvp => + { + if (kvp.Key.Equals(CapDiagnosticListenerExtensions.CapAfterSubscriberInvoke)) + { + Assert.NotNull(kvp.Value); + Assert.IsType(kvp.Value); + Assert.Equal(operationId, ((SubscriberInvokeEndEventData)kvp.Value).OperationId); + + } + }); + } + + [Fact] + public void WriteSubscriberInvokeErrorTest() + { + Guid operationId = Guid.NewGuid(); + + var ex = new Exception("WriteConsumeErrorTest"); + DiagnosticsWapper(() => + { + s_diagnosticListener.WriteSubscriberInvokeError(operationId, FackConsumerContext(), ex, + DateTimeOffset.Now, TimeSpan.MaxValue); + }, kvp => + { + if (kvp.Key.Equals(CapDiagnosticListenerExtensions.CapErrorSubscriberInvoke)) + { + Assert.NotNull(kvp.Value); + Assert.IsType(kvp.Value); + Assert.Equal(operationId, ((SubscriberInvokeErrorEventData)kvp.Value).OperationId); + Assert.Equal(ex, ((SubscriberInvokeErrorEventData)kvp.Value).Exception); + } + }); + } + + private ConsumerContext FackConsumerContext() + { + //Mock description + var description = new ConsumerExecutorDescriptor + { + MethodInfo = GetType().GetMethod("WriteSubscriberInvokeAfterTest"), + Attribute = new CapSubscribeAttribute("xxx"), + ImplTypeInfo = GetType().GetTypeInfo() + }; + + //Mock messageContext + var messageContext = new MessageContext + { + Name= "Name", + Group= "Group", + Content = "Content" + }; + + return new ConsumerContext(description, messageContext); + } + + private void DiagnosticsWapper(Action operation, Action> assert, [CallerMemberName]string methodName = "") + { + FakeDiagnosticListenerObserver diagnosticListenerObserver = new FakeDiagnosticListenerObserver(assert); + + diagnosticListenerObserver.Enable(); + using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) + { + Console.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); + operation(); + } + } + } +} diff --git a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj index 28bf4b2..09e9dd9 100644 --- a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj +++ b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj @@ -8,14 +8,14 @@ - + - + - - + + diff --git a/test/DotNetCore.CAP.Test/FakeDiagnosticListenerObserver.cs b/test/DotNetCore.CAP.Test/FakeDiagnosticListenerObserver.cs new file mode 100644 index 0000000..81db2c8 --- /dev/null +++ b/test/DotNetCore.CAP.Test/FakeDiagnosticListenerObserver.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using DotNetCore.CAP.Diagnostics; + +namespace DotNetCore.CAP.Test +{ + public sealed class FakeDiagnosticListenerObserver : IObserver + { + private class FakeDiagnosticSourceWriteObserver : IObserver> + { + private readonly Action> _writeCallback; + + public FakeDiagnosticSourceWriteObserver(Action> writeCallback) + { + _writeCallback = writeCallback; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + _writeCallback(value); + } + } + + private readonly Action> _writeCallback; + private bool _writeObserverEnabled; + + public FakeDiagnosticListenerObserver(Action> writeCallback) + { + _writeCallback = writeCallback; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(DiagnosticListener value) + { + if (value.Name.Equals(CapDiagnosticListenerExtensions.DiagnosticListenerName)) + { + value.Subscribe(new FakeDiagnosticSourceWriteObserver(_writeCallback), IsEnabled); + } + } + + public void Enable() + { + _writeObserverEnabled = true; + } + public void Disable() + { + _writeObserverEnabled = false; + } + private bool IsEnabled(string s) + { + return _writeObserverEnabled; + } + } +} diff --git a/test/DotNetCore.CAP.Test/HelperTest.cs b/test/DotNetCore.CAP.Test/HelperTest.cs new file mode 100644 index 0000000..48ee66f --- /dev/null +++ b/test/DotNetCore.CAP.Test/HelperTest.cs @@ -0,0 +1,154 @@ +using System; +using System.Reflection; +using DotNetCore.CAP.Diagnostics; +using DotNetCore.CAP.Infrastructure; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace DotNetCore.CAP.Test +{ + public class HelperTest + { + + [Fact] + public void ToTimestampTest() + { + //Arrange + var time = DateTime.Parse("2018-01-01 00:00:00"); + + //Act + var result = Helper.ToTimestamp(time); + + //Assert + Assert.Equal(1514764800, result); + } + + [Fact] + public void FromTimestampTest() + { + //Arrange + var time = DateTime.Parse("2018-01-01 00:00:00"); + + //Act + var result = Helper.FromTimestamp(1514764800); + + //Assert + Assert.Equal(time, result); + } + + [Fact] + public void IsControllerTest() + { + //Arrange + var typeInfo = typeof(HomeController).GetTypeInfo(); + + //Act + var result = Helper.IsController(typeInfo); + + //Assert + Assert.True(result); + } + + [Theory] + [InlineData(typeof(string))] + [InlineData(typeof(decimal))] + [InlineData(typeof(DateTime))] + [InlineData(typeof(DateTimeOffset))] + [InlineData(typeof(Guid))] + [InlineData(typeof(TimeSpan))] + [InlineData(typeof(Uri))] + public void IsSimpleTypeTest(Type type) + { + //Act + var result = Helper.IsComplexType(type); + + //Assert + Assert.False(result); + } + + [Theory] + [InlineData(typeof(HomeController))] + [InlineData(typeof(Exception))] + [InlineData(typeof(Person))] + public void IsComplexTypeTest(Type type) + { + //Act + var result = Helper.IsComplexType(type); + + //Assert + Assert.True(result); + } + + [Fact] + public void AddExceptionPropertyTest() + { + //Arrange + var json = "{}"; + var exception = new Exception("Test Exception Message") + { + Source = "Test Source", + InnerException = { } + }; + + var expected = new + { + ExceptionMessage = new + { + Source = "Test Source", + Message = "Test Exception Message", + InnerMessage = new { } + } + }; + + //Act + var result = Helper.AddExceptionProperty(json, exception); + + //Assert + var jObj = JObject.Parse(result); + Assert.Equal(jObj["ExceptionMessage"]["Source"].Value(), expected.ExceptionMessage.Source); + Assert.Equal(jObj["ExceptionMessage"]["Message"].Value(), expected.ExceptionMessage.Message); + } + + [Theory] + [InlineData("10.0.0.1")] + [InlineData("172.16.0.1")] + [InlineData("192.168.1.1")] + public void IsInnerIPTest(string ipAddress) + { + Assert.True(Helper.IsInnerIP(ipAddress)); + } + + [Fact] + public void AddTracingHeaderPropertyTest() + { + //Arrange + var json = "{}"; + var header = new TracingHeaders { { "key", "value" } }; + + //Act + var result = Helper.AddTracingHeaderProperty(json, header); + + //Assert + var expected = "{\"TracingHeaders\":{\"key\":\"value\"}}"; + Assert.Equal(expected, result); + } + + [Fact] + public void TryExtractTracingHeadersTest() + { + //Arrange + var json = "{\"TracingHeaders\":{\"key\":\"value\"}}"; + TracingHeaders header = null; + string removedHeadersJson = ""; + + //Act + var result = Helper.TryExtractTracingHeaders(json, out header, out removedHeadersJson); + + //Assert + Assert.True(result); + Assert.NotNull(header); + Assert.Single(header); + Assert.Equal("{}", removedHeadersJson); + } + } +} diff --git a/test/DotNetCore.CAP.Test/Processor/DefaultDispatcherTest.cs b/test/DotNetCore.CAP.Test/Processor/DefaultDispatcherTest.cs deleted file mode 100644 index 8ccdbf0..0000000 --- a/test/DotNetCore.CAP.Test/Processor/DefaultDispatcherTest.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using DotNetCore.CAP.Models; -using DotNetCore.CAP.Processor; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Moq; -using Xunit; - -namespace DotNetCore.CAP.Test -{ - public class DefaultDispatcherTest - { - private CancellationTokenSource _cancellationTokenSource; - private ProcessingContext _context; - private IServiceProvider _provider; - private Mock _mockStorageConnection; - private Mock _mockQueueExecutorFactory; - private Mock _mockQueueExecutor; - - public DefaultDispatcherTest() - { - _mockStorageConnection = new Mock(); - _mockQueueExecutorFactory = new Mock(); - _mockQueueExecutor = new Mock(); - _mockQueueExecutorFactory.Setup(x => x.GetInstance(MessageType.Publish)).Returns(_mockQueueExecutor.Object); - _cancellationTokenSource = new CancellationTokenSource(); - - var services = new ServiceCollection(); - services.AddTransient(); - services.AddLogging(); - services.Configure>(x => { }); - services.AddOptions(); - services.AddSingleton(_mockStorageConnection.Object); - services.AddSingleton(_mockQueueExecutorFactory.Object); - _provider = services.BuildServiceProvider(); - - _context = new ProcessingContext(_provider, _cancellationTokenSource.Token); - } - - [Fact] - public void MockTest() - { - Assert.NotNull(_provider.GetServices()); - } - - [Fact] - public async void ProcessAsync_CancellationTokenCancelled_ThrowsImmediately() - { - // Arrange - _cancellationTokenSource.Cancel(); - var fixture = Create(); - - // Act - await Assert.ThrowsAsync(() => fixture.ProcessAsync(_context)); - } - - [Fact] - public async Task ProcessAsync() - { - // Arrange - var job = new CapPublishedMessage - { - }; - - var mockFetchedJob = Mock.Get(Mock.Of(fj => fj.MessageId == 42 && fj.MessageType == MessageType.Publish)); - - _mockStorageConnection - .Setup(m => m.FetchNextMessageAsync()) - .ReturnsAsync(mockFetchedJob.Object).Verifiable(); - - _mockQueueExecutor - .Setup(x => x.ExecuteAsync(_mockStorageConnection.Object, mockFetchedJob.Object)) - .Returns(Task.FromResult(OperateResult.Success)); - - var fixture = Create(); - - // Act - await fixture.ProcessAsync(_context); - - // Assert - _mockStorageConnection.VerifyAll(); - } - - private DefaultDispatcher Create() - => _provider.GetService(); - } -} \ No newline at end of file diff --git a/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs b/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs index 9ff5208..5a72ffa 100644 --- a/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs +++ b/test/DotNetCore.CAP.Test/Processor/StateChangerTest.cs @@ -16,7 +16,7 @@ namespace DotNetCore.CAP.Test var fixture = Create(); var message = new CapPublishedMessage { - StatusName = StatusName.Enqueued + StatusName = StatusName.Scheduled }; var state = Mock.Of(s => s.Name == "s" && s.ExpiresAfter == null); var mockTransaction = new Mock(); @@ -39,7 +39,7 @@ namespace DotNetCore.CAP.Test var fixture = Create(); var message = new CapPublishedMessage { - StatusName = StatusName.Enqueued + StatusName = StatusName.Scheduled }; var state = Mock.Of(s => s.Name == "s" && s.ExpiresAfter == TimeSpan.FromHours(1)); var mockTransaction = new Mock(); diff --git a/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs b/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs index 03302f0..107d074 100644 --- a/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs +++ b/test/DotNetCore.CAP.Test/QueueExecutorFactoryTest.cs @@ -1,46 +1,46 @@ -using System; -using DotNetCore.CAP.Internal; -using Microsoft.Extensions.DependencyInjection; -using Xunit; -using Moq; +//using System; +//using DotNetCore.CAP.Internal; +//using Microsoft.Extensions.DependencyInjection; +//using Xunit; +//using Moq; -namespace DotNetCore.CAP.Test -{ - public class QueueExecutorFactoryTest - { - private IServiceProvider _provider; +//namespace DotNetCore.CAP.Test +//{ +// public class QueueExecutorFactoryTest +// { +// private IServiceProvider _provider; - public QueueExecutorFactoryTest() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddOptions(); +// public QueueExecutorFactoryTest() +// { +// var services = new ServiceCollection(); +// services.AddLogging(); +// services.AddOptions(); - services.AddCap(x => { }); - _provider = services.BuildServiceProvider(); - } +// services.AddCap(x => { }); +// _provider = services.BuildServiceProvider(); +// } - [Fact] - public void CanCreateInstance() - { - var queueExecutorFactory = _provider.GetService(); - Assert.NotNull(queueExecutorFactory); +// [Fact] +// public void CanCreateInstance() +// { +// var queueExecutorFactory = _provider.GetService(); +// Assert.NotNull(queueExecutorFactory); - var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish); - Assert.Null(publishExecutor); +// var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish); +// Assert.Null(publishExecutor); - var disPatchExector = queueExecutorFactory.GetInstance(Models.MessageType.Subscribe); - Assert.NotNull(disPatchExector); - } +// var disPatchExector = queueExecutorFactory.GetInstance(Models.MessageType.Subscribe); +// Assert.NotNull(disPatchExector); +// } - [Fact] - public void CanGetSubscribeExector() - { - var queueExecutorFactory = _provider.GetService(); - Assert.NotNull(queueExecutorFactory); +// [Fact] +// public void CanGetSubscribeExector() +// { +// var queueExecutorFactory = _provider.GetService(); +// Assert.NotNull(queueExecutorFactory); - var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish); - Assert.Null(publishExecutor); - } - } -} \ No newline at end of file +// var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish); +// Assert.Null(publishExecutor); +// } +// } +//} \ No newline at end of file