Commit 72161b90 authored by Savorboard's avatar Savorboard

merged pr#113

parents e5575427 0f6ae8ad
...@@ -54,15 +54,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MySql.Test", ...@@ -54,15 +54,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MySql.Test",
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.MySql", "samples\Sample.RabbitMQ.MySql\Sample.RabbitMQ.MySql.csproj", "{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.MySql", "samples\Sample.RabbitMQ.MySql\Sample.RabbitMQ.MySql.csproj", "{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}"
EndProject 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.PostgreSql", "src\DotNetCore.CAP.PostgreSql\DotNetCore.CAP.PostgreSql.csproj", "{82C403AB-ED68-4084-9A1D-11334F9F08F9}"
EndProject 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}" 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
...@@ -105,26 +101,18 @@ Global ...@@ -105,26 +101,18 @@ Global
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Debug|Any CPU.Build.0 = Debug|Any CPU {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.ActiveCfg = Release|Any CPU
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{82C403AB-ED68-4084-9A1D-11334F9F08F9}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{82C403AB-ED68-4084-9A1D-11334F9F08F9}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{7CA3625D-1817-4695-881D-7E79A1E1DED2}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{7CA3625D-1817-4695-881D-7E79A1E1DED2}.Release|Any CPU.Build.0 = 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 {9CB51105-A85B-42A4-AFDE-A4FC34D9EA91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{573B4D39-5489-48B3-9B6C-5234249CB980}.Debug|Any CPU.Build.0 = Debug|Any CPU {9CB51105-A85B-42A4-AFDE-A4FC34D9EA91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{573B4D39-5489-48B3-9B6C-5234249CB980}.Release|Any CPU.ActiveCfg = Release|Any CPU {9CB51105-A85B-42A4-AFDE-A4FC34D9EA91}.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}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
...@@ -139,11 +127,9 @@ Global ...@@ -139,11 +127,9 @@ Global
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {FA15685A-778A-4D2A-A2FE-27FAD2FFA65B} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{80A84F62-1558-427B-BA74-B47AA8A665B5} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} {80A84F62-1558-427B-BA74-B47AA8A665B5} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873} = {3A6B6931-A123-477A-9469-8B468B5385AF} {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} {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} {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB}
......
#
# 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
...@@ -187,7 +187,10 @@ Then inject your `ISubscriberService` class in Startup.cs ...@@ -187,7 +187,10 @@ Then inject your `ISubscriberService` class in Startup.cs
```c# ```c#
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
//Note: The injection of services needs before of `services.AddCap()`
services.AddTransient<ISubscriberService,SubscriberService>(); services.AddTransient<ISubscriberService,SubscriberService>();
services.AddCap(x=>{});
} }
``` ```
...@@ -218,6 +221,8 @@ 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) ![dashboard](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220827302-189215107.png)
![received](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220934115-1107747665.png) ![received](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220934115-1107747665.png)
......
...@@ -187,7 +187,10 @@ namespace xxx.Service ...@@ -187,7 +187,10 @@ namespace xxx.Service
```c# ```c#
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
//注意: 注入的服务需要在 `services.AddCap()` 之前
services.AddTransient<ISubscriberService,SubscriberService>(); services.AddTransient<ISubscriberService,SubscriberService>();
services.AddCap(x=>{});
} }
``` ```
...@@ -218,6 +221,8 @@ 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) ![dashboard](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220827302-189215107.png)
![received](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220934115-1107747665.png) ![received](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220934115-1107747665.png)
......
...@@ -11,7 +11,6 @@ services: ...@@ -11,7 +11,6 @@ services:
- mysql - mysql
- postgresql - postgresql
build_script: build_script:
- ps: ./ConfigureMSDTC.ps1
- ps: ./build.ps1 - ps: ./build.ps1
test: off test: off
artifacts: artifacts:
......
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<VersionMajor>2</VersionMajor> <VersionMajor>2</VersionMajor>
<VersionMinor>1</VersionMinor> <VersionMinor>2</VersionMinor>
<VersionPatch>4</VersionPatch> <VersionPatch>0</VersionPatch>
<VersionQuality></VersionQuality> <VersionQuality></VersionQuality>
<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix> <VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix>
</PropertyGroup> </PropertyGroup>
......
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<IActionResult> 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(value);
}
}
}
\ No newline at end of file
using System.IO; using Microsoft.AspNetCore;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace Sample.Kafka.SqlServer namespace Sample.Kafka.MySql
{ {
public class Program public class Program
{ {
......
...@@ -2,20 +2,22 @@ ...@@ -2,20 +2,22 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyName>Sample.Kafka.SqlServer</AssemblyName> <AssemblyName>Sample.Kafka.MySql</AssemblyName>
<WarningsAsErrors>NU1701</WarningsAsErrors> <WarningsAsErrors>NU1701</WarningsAsErrors>
<NoWarn>NU1701</NoWarn> <NoWarn>NU1701</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
<PackageReference Include="MySqlConnector" Version="0.38.0" />
<PackageReference Include="zipkin4net" Version="1.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\DotNetCore.CAP.Kafka\DotNetCore.CAP.Kafka.csproj" /> <ProjectReference Include="..\..\src\DotNetCore.CAP.Kafka\DotNetCore.CAP.Kafka.csproj" />
<ProjectReference Include="..\..\src\DotNetCore.CAP.SqlServer\DotNetCore.CAP.SqlServer.csproj" /> <ProjectReference Include="..\..\src\DotNetCore.CAP.MySql\DotNetCore.CAP.MySql.csproj" />
<ProjectReference Include="..\..\src\DotNetCore.CAP\DotNetCore.CAP.csproj" /> <ProjectReference Include="..\..\src\DotNetCore.CAP\DotNetCore.CAP.csproj" />
</ItemGroup> </ItemGroup>
......
using System; 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.DependencyInjection;
namespace Sample.RabbitMQ.PostgreSql namespace Sample.Kafka.MySql
{ {
public class Startup public class Startup
{ {
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddDbContext<AppDbContext>();
services.AddCap(x => services.AddCap(x =>
{ {
x.UseEntityFramework<AppDbContext>(); x.UseMySql("Server=192.168.10.110;Database=testcap;UserId=root;Password=123123;");
x.UseRabbitMQ("localhost"); x.UseKafka("192.168.10.110:9092");
x.UseDashboard(); x.UseDashboard();
x.UseDiscovery(d =>
{
d.DiscoveryServerHostName = "localhost";
d.DiscoveryServerPort = 8500;
d.CurrentNodeHostName = "localhost";
d.CurrentNodePort = 5800;
d.NodeName = "CAP 一号节点";
});
}); });
services.AddMvc(); services.AddMvc();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app)
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{ {
app.UseMvc(); app.UseMvc();
app.UseCap(); app.UseCap();
} }
} }
} }
\ No newline at end of file
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");
}
}
}
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<MessageContent>(packingMessage);
}
}
}
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<IActionResult> 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<DateTime> 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
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<AppDbContext>();
services.AddCap(x =>
{
x.UseEntityFramework<AppDbContext>();
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<MyMessagePacker>();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMvc();
app.UseCap();
}
}
}
\ No newline at end of file
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP; using DotNetCore.CAP;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
......
{
"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
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.0.1" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
using System; using Microsoft.AspNetCore.Builder;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
......
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;");
}
}
}
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<IActionResult> 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");
}
}
}
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<Startup>()
.Build();
}
}
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotNetCore.CAP.PostgreSql\DotNetCore.CAP.PostgreSql.csproj" />
<ProjectReference Include="..\..\src\DotNetCore.CAP.RabbitMQ\DotNetCore.CAP.RabbitMQ.csproj" />
<ProjectReference Include="..\..\src\DotNetCore.CAP\DotNetCore.CAP.csproj" />
</ItemGroup>
</Project>
using Microsoft.EntityFrameworkCore;
using Sample.RabbitMQ.SqlServer.Controllers;
namespace Sample.RabbitMQ.SqlServer
{
public class AppDbContext : DbContext
{
public DbSet<Person> 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");
}
}
}
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<IActionResult> 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<DateTime> 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
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Age");
b.Property<string>("Name");
b.HasKey("Id");
b.ToTable("Persons");
});
#pragma warning restore 612, 618
}
}
}
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<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Age = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Persons", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Persons");
}
}
}
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Age");
b.Property<string>("Name");
b.HasKey("Id");
b.ToTable("Persons");
});
#pragma warning restore 612, 618
}
}
}
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<Startup>()
.Build();
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyName>Sample.RabbitMQ.SqlServer</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotNetCore.CAP.RabbitMQ\DotNetCore.CAP.RabbitMQ.csproj" />
<ProjectReference Include="..\..\src\DotNetCore.CAP.SqlServer\DotNetCore.CAP.SqlServer.csproj" />
<ProjectReference Include="..\..\src\DotNetCore.CAP\DotNetCore.CAP.csproj" />
</ItemGroup>
</Project>
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();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.RabbitMQ.SqlServer.Services
{
public interface IOrderService
{
void Check();
}
}
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();
}
}
}
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");
}
}
}
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<AppDbContext>();
services.AddScoped<IOrderService, OrderService>();
services.AddTransient<ICmsService, CmsService>();
services.AddCap(x =>
{
x.UseEntityFramework<AppDbContext>();
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
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 DotNetCore.CAP.Kafka;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
...@@ -23,9 +26,9 @@ namespace DotNetCore.CAP ...@@ -23,9 +26,9 @@ namespace DotNetCore.CAP
services.AddSingleton(kafkaOptions); services.AddSingleton(kafkaOptions);
services.AddSingleton<IConsumerClientFactory, KafkaConsumerClientFactory>(); services.AddSingleton<IConsumerClientFactory, KafkaConsumerClientFactory>();
services.AddSingleton<IQueueExecutor, PublishQueueExecutor>(); services.AddSingleton<IPublishExecutor, KafkaPublishMessageSender>();
services.AddSingleton<IPublishExecutor, PublishQueueExecutor>(); services.AddSingleton<IPublishMessageSender, KafkaPublishMessageSender>();
services.AddSingleton<ConnectionPool>(); services.AddSingleton<IConnectionPool,ConnectionPool>();
} }
} }
} }
\ No newline at end of file
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.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
...@@ -45,16 +48,19 @@ namespace DotNetCore.CAP ...@@ -45,16 +48,19 @@ namespace DotNetCore.CAP
if (_kafkaConfig == null) if (_kafkaConfig == null)
{ {
if (string.IsNullOrWhiteSpace(Servers)) if (string.IsNullOrWhiteSpace(Servers))
{
throw new ArgumentNullException(nameof(Servers)); throw new ArgumentNullException(nameof(Servers));
}
MainConfig["bootstrap.servers"] = Servers; MainConfig["bootstrap.servers"] = Servers;
MainConfig["queue.buffering.max.ms"] = "10"; MainConfig["queue.buffering.max.ms"] = "10";
MainConfig["socket.blocking.max.ms"] = "10"; MainConfig["socket.blocking.max.ms"] = "10";
MainConfig["enable.auto.commit"] = "false"; MainConfig["enable.auto.commit"] = "false";
MainConfig["log.connection.close"] = "false"; MainConfig["log.connection.close"] = "false";
_kafkaConfig = MainConfig.AsEnumerable(); _kafkaConfig = MainConfig.AsEnumerable();
} }
return _kafkaConfig; return _kafkaConfig;
} }
} }
......
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;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
...@@ -24,7 +27,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -24,7 +27,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// <returns></returns> /// <returns></returns>
public static CapOptions UseKafka(this CapOptions options, Action<KafkaOptions> configure) public static CapOptions UseKafka(this CapOptions options, Action<KafkaOptions> configure)
{ {
if (configure == null) throw new ArgumentNullException(nameof(configure)); if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
options.RegisterExtension(new KafkaCapOptionsExtension(configure)); options.RegisterExtension(new KafkaCapOptionsExtension(configure));
......
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 // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
......
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.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
...@@ -18,8 +21,12 @@ namespace DotNetCore.CAP.Kafka ...@@ -18,8 +21,12 @@ namespace DotNetCore.CAP.Kafka
{ {
_maxSize = options.ConnectionPoolSize; _maxSize = options.ConnectionPoolSize;
_activator = CreateActivator(options); _activator = CreateActivator(options);
ServersAddress = options.Servers;
} }
public string ServersAddress { get; }
Producer IConnectionPool.Rent() Producer IConnectionPool.Rent()
{ {
return Rent(); return Rent();
...@@ -35,7 +42,9 @@ namespace DotNetCore.CAP.Kafka ...@@ -35,7 +42,9 @@ namespace DotNetCore.CAP.Kafka
_maxSize = 0; _maxSize = 0;
while (_pool.TryDequeue(out var context)) while (_pool.TryDequeue(out var context))
{
context.Dispose(); context.Dispose();
}
} }
private static Func<Producer> CreateActivator(KafkaOptions options) private static Func<Producer> CreateActivator(KafkaOptions options)
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Confluent.Kafka" Version="0.11.3" /> <PackageReference Include="Confluent.Kafka" Version="0.11.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
 // 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; using Confluent.Kafka;
namespace DotNetCore.CAP.Kafka namespace DotNetCore.CAP.Kafka
{ {
public interface IConnectionPool public interface IConnectionPool
{ {
string ServersAddress { get; }
Producer Rent(); Producer Rent();
bool Return(Producer context); bool Return(Producer context);
......
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; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Processor.States; using DotNetCore.CAP.Processor.States;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP.Kafka namespace DotNetCore.CAP.Kafka
{ {
internal class PublishQueueExecutor : BasePublishQueueExecutor internal class KafkaPublishMessageSender : BasePublishMessageSender
{ {
private readonly ConnectionPool _connectionPool; private readonly IConnectionPool _connectionPool;
private readonly ILogger _logger; private readonly ILogger _logger;
public PublishQueueExecutor( public KafkaPublishMessageSender(
CapOptions options, CapOptions options, IStateChanger stateChanger, IStorageConnection connection,
IStateChanger stateChanger, IConnectionPool connectionPool, ILogger<KafkaPublishMessageSender> logger)
ConnectionPool connectionPool, : base(logger, options, connection, stateChanger)
ILogger<PublishQueueExecutor> logger)
: base(options, stateChanger, logger)
{ {
_logger = logger; _logger = logger;
_connectionPool = connectionPool; _connectionPool = connectionPool;
ServersAddress = _connectionPool.ServersAddress;
} }
public override async Task<OperateResult> PublishAsync(string keyName, string content) public override async Task<OperateResult> PublishAsync(string keyName, string content)
{ {
var producer = _connectionPool.Rent(); var producer = _connectionPool.Rent();
try try
{ {
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var message = await producer.ProduceAsync(keyName, null, contentBytes); var message = await producer.ProduceAsync(keyName, null, contentBytes);
if (!message.Error.HasError) if (message.Error.HasError)
{ {
_logger.LogDebug($"kafka topic message [{keyName}] has been published."); return OperateResult.Failed(new OperateError
{
return OperateResult.Success; Code = message.Error.Code.ToString(),
Description = message.Error.Reason
});
} }
return OperateResult.Failed(new OperateError
{
Code = message.Error.Code.ToString(),
Description = message.Error.Reason
});
_logger.LogDebug($"kafka topic message [{keyName}] has been published.");
return OperateResult.Success;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, var wapperEx = new PublisherSentFailedException(ex.Message, ex);
$"An error occurred during sending the topic message to kafka. Topic:[{keyName}], Exception: {ex.Message}");
return OperateResult.Failed(ex); return OperateResult.Failed(wapperEx);
} }
finally finally
{ {
var returned = _connectionPool.Return(producer); var returned = _connectionPool.Return(producer);
if (!returned) if (!returned)
{
producer.Dispose(); producer.Dispose();
}
} }
} }
} }
} }
\ No newline at end of file
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.Collections.Generic;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
...@@ -26,13 +29,19 @@ namespace DotNetCore.CAP.Kafka ...@@ -26,13 +29,19 @@ namespace DotNetCore.CAP.Kafka
public event EventHandler<LogMessageEventArgs> OnLog; public event EventHandler<LogMessageEventArgs> OnLog;
public string ServersAddress => _kafkaOptions.Servers;
public void Subscribe(IEnumerable<string> topics) public void Subscribe(IEnumerable<string> topics)
{ {
if (topics == null) if (topics == null)
{
throw new ArgumentNullException(nameof(topics)); throw new ArgumentNullException(nameof(topics));
}
if (_consumerClient == null) if (_consumerClient == null)
{
InitKafkaClient(); InitKafkaClient();
}
_consumerClient.Subscribe(topics); _consumerClient.Subscribe(topics);
} }
...@@ -44,6 +53,7 @@ namespace DotNetCore.CAP.Kafka ...@@ -44,6 +53,7 @@ namespace DotNetCore.CAP.Kafka
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
_consumerClient.Poll(timeout); _consumerClient.Poll(timeout);
} }
// ReSharper disable once FunctionNeverReturns // ReSharper disable once FunctionNeverReturns
} }
......
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 internal sealed class KafkaConsumerClientFactory : IConsumerClientFactory
{ {
......
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 // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
......
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.MySql;
using DotNetCore.CAP.Processor; using DotNetCore.CAP.Processor;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
......
// 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 namespace DotNetCore.CAP
{ {
public class MySqlOptions : EFOptions public class MySqlOptions : EFOptions
......
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;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
...@@ -14,7 +17,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -14,7 +17,10 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseMySql(this CapOptions options, Action<MySqlOptions> configure) public static CapOptions UseMySql(this CapOptions options, Action<MySqlOptions> configure)
{ {
if (configure == null) throw new ArgumentNullException(nameof(configure)); if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
options.RegisterExtension(new MySqlCapOptionsExtension(configure)); options.RegisterExtension(new MySqlCapOptionsExtension(configure));
...@@ -31,7 +37,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -31,7 +37,10 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure) public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure)
where TContext : DbContext where TContext : DbContext
{ {
if (configure == null) throw new ArgumentNullException(nameof(configure)); if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
options.RegisterExtension(new MySqlCapOptionsExtension(x => options.RegisterExtension(new MySqlCapOptionsExtension(x =>
{ {
......
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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper; using Dapper;
...@@ -14,27 +17,31 @@ namespace DotNetCore.CAP.MySql ...@@ -14,27 +17,31 @@ namespace DotNetCore.CAP.MySql
public class CapPublisher : CapPublisherBase, ICallbackPublisher public class CapPublisher : CapPublisherBase, ICallbackPublisher
{ {
private readonly DbContext _dbContext; private readonly DbContext _dbContext;
private readonly ILogger _logger;
private readonly MySqlOptions _options; private readonly MySqlOptions _options;
public CapPublisher(IServiceProvider provider, public CapPublisher(ILogger<CapPublisher> logger, IDispatcher dispatcher, IServiceProvider provider,
ILogger<CapPublisher> logger,
MySqlOptions options) MySqlOptions options)
: base(logger, dispatcher)
{ {
ServiceProvider = provider; ServiceProvider = provider;
_options = options; _options = options;
_logger = logger;
if (_options.DbContextType == null) return; if (_options.DbContextType == null)
{
return;
}
IsUsingEF = true; 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 MySqlConnection(_options.ConnectionString)) using (var conn = new MySqlConnection(_options.ConnectionString))
{ {
await conn.ExecuteAsync(PrepareSql(), message); var id = await conn.ExecuteScalarAsync<int>(PrepareSql(), message);
message.Id = id;
Enqueue(message);
} }
} }
...@@ -51,23 +58,20 @@ namespace DotNetCore.CAP.MySql ...@@ -51,23 +58,20 @@ namespace DotNetCore.CAP.MySql
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted);
dbTrans = dbContextTransaction.GetDbTransaction(); dbTrans = dbContextTransaction.GetDbTransaction();
} }
DbTransaction = dbTrans; DbTransaction = dbTrans;
} }
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, protected override int Execute(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message) CapPublishedMessage message)
{ {
dbConnection.Execute(PrepareSql(), message, dbTransaction); return dbConnection.ExecuteScalar<int>(PrepareSql(), message, dbTransaction);
_logger.LogInformation("Published Message has been persisted in the database. name:" + message);
} }
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, protected override async Task<int> ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message) CapPublishedMessage message)
{ {
await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); return await dbConnection.ExecuteScalarAsync<int>(PrepareSql(), message, dbTransaction);
_logger.LogInformation("Published Message has been persisted in the database. name:" + message);
} }
#region private methods #region private methods
...@@ -75,7 +79,7 @@ namespace DotNetCore.CAP.MySql ...@@ -75,7 +79,7 @@ namespace DotNetCore.CAP.MySql
private string PrepareSql() private string PrepareSql()
{ {
return 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 #endregion private methods
......
...@@ -15,9 +15,9 @@ ...@@ -15,9 +15,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="1.50.4" /> <PackageReference Include="Dapper" Version="1.50.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.2" />
<PackageReference Include="MySqlConnector" Version="0.36.0" /> <PackageReference Include="MySqlConnector" Version="0.38.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
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
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 System.Threading.Tasks;
using Dapper; using Dapper;
using DotNetCore.CAP.Processor; using DotNetCore.CAP.Processor;
......
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
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.Collections.Generic;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
...@@ -28,9 +31,7 @@ set transaction isolation level read committed; ...@@ -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}.published` where StatusName = N'Succeeded';
select count(Id) from `{0}.received` 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}.published` where StatusName = N'Failed';
select count(Id) from `{0}.received` where StatusName = N'Failed'; select count(Id) from `{0}.received` where StatusName = N'Failed';", _prefix);
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);
var statistics = UseConnection(connection => var statistics = UseConnection(connection =>
{ {
...@@ -42,10 +43,8 @@ select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Sched ...@@ -42,10 +43,8 @@ select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Sched
stats.PublishedFailed = multi.ReadSingle<int>(); stats.PublishedFailed = multi.ReadSingle<int>();
stats.ReceivedFailed = multi.ReadSingle<int>(); stats.ReceivedFailed = multi.ReadSingle<int>();
stats.PublishedProcessing = multi.ReadSingle<int>();
stats.ReceivedProcessing = multi.ReadSingle<int>();
} }
return stats; return stats;
}); });
return statistics; return statistics;
...@@ -70,17 +69,24 @@ select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Sched ...@@ -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 tableName = queryDto.MessageType == MessageType.Publish ? "published" : "received";
var where = string.Empty; var where = string.Empty;
if (!string.IsNullOrEmpty(queryDto.StatusName)) if (!string.IsNullOrEmpty(queryDto.StatusName))
if (string.Equals(queryDto.StatusName, StatusName.Processing, {
StringComparison.CurrentCultureIgnoreCase)) where += " and StatusName=@StatusName";
where += " and StatusName in (N'Processing',N'Scheduled',N'Enqueued')"; }
else
where += " and StatusName=@StatusName";
if (!string.IsNullOrEmpty(queryDto.Name)) if (!string.IsNullOrEmpty(queryDto.Name))
{
where += " and Name=@Name"; where += " and Name=@Name";
}
if (!string.IsNullOrEmpty(queryDto.Group)) if (!string.IsNullOrEmpty(queryDto.Group))
{
where += " and Group=@Group"; where += " and Group=@Group";
}
if (!string.IsNullOrEmpty(queryDto.Content)) if (!string.IsNullOrEmpty(queryDto.Content))
{
where += " and Content like '%@Content%'"; where += " and Content like '%@Content%'";
}
var sqlQuery = var sqlQuery =
$"select * from `{_prefix}.{tableName}` where 1=1 {where} order by Added desc limit @Limit offset @Offset"; $"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 ...@@ -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)); return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Failed));
} }
public int PublishedProcessingCount()
{
return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Processing));
}
public int PublishedSucceededCount() public int PublishedSucceededCount()
{ {
return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Succeeded)); 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 ...@@ -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)); return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Failed));
} }
public int ReceivedProcessingCount()
{
return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Processing));
}
public int ReceivedSucceededCount() public int ReceivedSucceededCount()
{ {
return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Succeeded)); 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 ...@@ -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) private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName)
{ {
var sqlQuery = statusName == StatusName.Processing var sqlQuery = $"select count(Id) from `{_prefix}.{tableName}` where StatusName = @state";
? $"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 count = connection.ExecuteScalar<int>(sqlQuery, new {state = statusName}); var count = connection.ExecuteScalar<int>(sqlQuery, new {state = statusName});
return count; return count;
...@@ -179,7 +173,12 @@ select aggr.* from ( ...@@ -179,7 +173,12 @@ select aggr.* from (
.ToDictionary(x => (string) x.Key, x => (int) x.Count); .ToDictionary(x => (string) x.Key, x => (int) x.Count);
foreach (var key in keyMaps.Keys) 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<DateTime, int>(); var result = new Dictionary<DateTime, int>();
for (var i = 0; i < keyMaps.Count; i++) for (var i = 0; i < keyMaps.Count; i++)
......
// 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;
using System.Data; using System.Data;
using System.Threading; using System.Threading;
...@@ -11,10 +14,10 @@ namespace DotNetCore.CAP.MySql ...@@ -11,10 +14,10 @@ namespace DotNetCore.CAP.MySql
{ {
public class MySqlStorage : IStorage public class MySqlStorage : IStorage
{ {
private readonly CapOptions _capOptions;
private readonly IDbConnection _existingConnection = null; private readonly IDbConnection _existingConnection = null;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly MySqlOptions _options; private readonly MySqlOptions _options;
private readonly CapOptions _capOptions;
public MySqlStorage(ILogger<MySqlStorage> logger, public MySqlStorage(ILogger<MySqlStorage> logger,
MySqlOptions options, MySqlOptions options,
...@@ -37,7 +40,11 @@ namespace DotNetCore.CAP.MySql ...@@ -37,7 +40,11 @@ namespace DotNetCore.CAP.MySql
public async Task InitializeAsync(CancellationToken cancellationToken) public async Task InitializeAsync(CancellationToken cancellationToken)
{ {
if (cancellationToken.IsCancellationRequested) return; if (cancellationToken.IsCancellationRequested)
{
return;
}
var sql = CreateDbTablesScript(_options.TableNamePrefix); var sql = CreateDbTablesScript(_options.TableNamePrefix);
using (var connection = new MySqlConnection(_options.ConnectionString)) using (var connection = new MySqlConnection(_options.ConnectionString))
{ {
...@@ -51,11 +58,7 @@ namespace DotNetCore.CAP.MySql ...@@ -51,11 +58,7 @@ namespace DotNetCore.CAP.MySql
{ {
var batchSql = var batchSql =
$@" $@"
CREATE TABLE IF NOT EXISTS `{prefix}.queue` ( DROP TABLE IF EXISTS `{prefix}.queue`;
`MessageId` int(11) NOT NULL,
`MessageType` tinyint(4) NOT NULL,
`ProcessId` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `{prefix}.received` ( CREATE TABLE IF NOT EXISTS `{prefix}.received` (
`Id` int(127) NOT NULL AUTO_INCREMENT, `Id` int(127) NOT NULL AUTO_INCREMENT,
...@@ -102,7 +105,9 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` ( ...@@ -102,7 +105,9 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` (
var connection = _existingConnection ?? new MySqlConnection(_options.ConnectionString); var connection = _existingConnection ?? new MySqlConnection(_options.ConnectionString);
if (connection.State == ConnectionState.Closed) if (connection.State == ConnectionState.Closed)
{
connection.Open(); connection.Open();
}
return connection; return connection;
} }
...@@ -115,7 +120,9 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` ( ...@@ -115,7 +120,9 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` (
internal void ReleaseConnection(IDbConnection connection) internal void ReleaseConnection(IDbConnection connection)
{ {
if (connection != null && !IsExistingConnection(connection)) if (connection != null && !IsExistingConnection(connection))
{
connection.Dispose(); connection.Dispose();
}
} }
} }
} }
\ No newline at end of file
// 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -13,8 +16,6 @@ namespace DotNetCore.CAP.MySql ...@@ -13,8 +16,6 @@ namespace DotNetCore.CAP.MySql
private readonly CapOptions _capOptions; private readonly CapOptions _capOptions;
private readonly string _prefix; private readonly string _prefix;
private const string DateTimeMaxValue = "9999-12-31 23:59:59";
public MySqlStorageConnection(MySqlOptions options, CapOptions capOptions) public MySqlStorageConnection(MySqlOptions options, CapOptions capOptions)
{ {
_capOptions = capOptions; _capOptions = capOptions;
...@@ -39,51 +40,32 @@ namespace DotNetCore.CAP.MySql ...@@ -39,51 +40,32 @@ namespace DotNetCore.CAP.MySql
} }
} }
public Task<IFetchedMessage> FetchNextMessageAsync() public async Task<IEnumerable<CapPublishedMessage>> GetPublishedMessagesOfNeedRetry()
{ {
var processId = ObjectId.GenerateNewStringId(); var fourMinsAgo = DateTime.Now.AddMinutes(-4);
var sql = $@" var sql =
UPDATE `{_prefix}.queue` SET `ProcessId`=@ProcessId WHERE `ProcessId` IS NULL LIMIT 1; $"SELECT * FROM `{_prefix}.published` WHERE `Retries`<{_capOptions.FailedRetryCount} AND `Added`<'{fourMinsAgo}' AND (`StatusName` = '{StatusName.Failed}' OR `StatusName` = '{StatusName.Scheduled}') LIMIT 200;";
SELECT `MessageId`,`MessageType` FROM `{_prefix}.queue` WHERE `ProcessId`=@ProcessId;";
return FetchNextMessageCoreAsync(sql, processId);
}
public async Task<CapPublishedMessage> 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();";
using (var connection = new MySqlConnection(Options.ConnectionString)) using (var connection = new MySqlConnection(Options.ConnectionString))
{ {
connection.Open(); return await connection.QueryAsync<CapPublishedMessage>(sql);
connection.Execute("SELECT LAST_INSERT_ID(0)");
return await connection.QueryFirstOrDefaultAsync<CapPublishedMessage>(sql);
} }
} }
public async Task<IEnumerable<CapPublishedMessage>> GetFailedPublishedMessages() public async Task<int> StoreReceivedMessageAsync(CapReceivedMessage message)
{ {
var sql = $"SELECT * FROM `{_prefix}.published` WHERE `Retries`<{_capOptions.FailedRetryCount} AND `StatusName` = '{StatusName.Failed}' LIMIT 200;"; if (message == null)
using (var connection = new MySqlConnection(Options.ConnectionString))
{ {
return await connection.QueryAsync<CapPublishedMessage>(sql); throw new ArgumentNullException(nameof(message));
} }
}
public async Task StoreReceivedMessageAsync(CapReceivedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $@" var sql = $@"
INSERT INTO `{_prefix}.received`(`Name`,`Group`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`) 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)) using (var connection = new MySqlConnection(Options.ConnectionString))
{ {
await connection.ExecuteAsync(sql, message); return await connection.ExecuteScalarAsync<int>(sql, message);
} }
} }
...@@ -96,23 +78,11 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; ...@@ -96,23 +78,11 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
} }
} }
public async Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync() public async Task<IEnumerable<CapReceivedMessage>> GetReceivedMessagesOfNeedRetry()
{ {
var sql = $@" var fourMinsAgo = DateTime.Now.AddMinutes(-4);
UPDATE `{_prefix}.received` SET Id=LAST_INSERT_ID(Id),ExpiresAt='{DateTimeMaxValue}' WHERE ExpiresAt IS NULL AND `StatusName` = '{StatusName.Scheduled}' LIMIT 1; var sql =
SELECT * FROM `{_prefix}.received` WHERE Id=LAST_INSERT_ID();"; $"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))
{
connection.Open();
connection.Execute("SELECT LAST_INSERT_ID(0)");
return await connection.QueryFirstOrDefaultAsync<CapReceivedMessage>(sql);
}
}
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages()
{
var sql = $"SELECT * FROM `{_prefix}.received` WHERE `Retries`<{_capOptions.FailedRetryCount} AND `StatusName` = '{StatusName.Failed}' LIMIT 200;";
using (var connection = new MySqlConnection(Options.ConnectionString)) using (var connection = new MySqlConnection(Options.ConnectionString))
{ {
return await connection.QueryAsync<CapReceivedMessage>(sql); return await connection.QueryAsync<CapReceivedMessage>(sql);
...@@ -141,20 +111,6 @@ SELECT * FROM `{_prefix}.received` WHERE Id=LAST_INSERT_ID();"; ...@@ -141,20 +111,6 @@ SELECT * FROM `{_prefix}.received` WHERE Id=LAST_INSERT_ID();";
} }
} }
private async Task<IFetchedMessage> FetchNextMessageCoreAsync(string sql, string processId)
{
FetchedMessage fetchedMessage;
using (var connection = new MySqlConnection(Options.ConnectionString))
{
fetchedMessage = await connection.QuerySingleOrDefaultAsync<FetchedMessage>(sql, new { ProcessId = processId });
}
if (fetchedMessage == null)
return null;
return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, processId, Options);
}
public void Dispose() public void Dispose()
{ {
} }
......
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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper; using Dapper;
...@@ -11,7 +14,7 @@ namespace DotNetCore.CAP.MySql ...@@ -11,7 +14,7 @@ namespace DotNetCore.CAP.MySql
{ {
private readonly IDbConnection _dbConnection; private readonly IDbConnection _dbConnection;
private readonly IDbTransaction _dbTransaction; //private readonly IDbTransaction _dbTransaction;
private readonly string _prefix; private readonly string _prefix;
public MySqlStorageTransaction(MySqlStorageConnection connection) public MySqlStorageTransaction(MySqlStorageConnection connection)
...@@ -20,55 +23,45 @@ namespace DotNetCore.CAP.MySql ...@@ -20,55 +23,45 @@ namespace DotNetCore.CAP.MySql
_prefix = options.TableNamePrefix; _prefix = options.TableNamePrefix;
_dbConnection = new MySqlConnection(options.ConnectionString); _dbConnection = new MySqlConnection(options.ConnectionString);
_dbConnection.Open(); // _dbConnection.Open(); for performance
_dbTransaction = _dbConnection.BeginTransaction(IsolationLevel.ReadCommitted); // _dbTransaction = _dbConnection.BeginTransaction(IsolationLevel.ReadCommitted);
} }
public void UpdateMessage(CapPublishedMessage message) public void UpdateMessage(CapPublishedMessage message)
{ {
if (message == null) throw new ArgumentNullException(nameof(message)); if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
var sql = var sql =
$"UPDATE `{_prefix}.published` SET `Retries` = @Retries,`Content`= @Content,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; $"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) public void UpdateMessage(CapReceivedMessage message)
{ {
if (message == null) throw new ArgumentNullException(nameof(message)); if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
var sql = var sql =
$"UPDATE `{_prefix}.received` SET `Retries` = @Retries,`Content`= @Content,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; $"UPDATE `{_prefix}.received` SET `Retries` = @Retries,`Content`= @Content,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction); _dbConnection.Execute(sql, message);
}
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);
} }
public Task CommitAsync() public Task CommitAsync()
{ {
_dbTransaction.Commit(); _dbConnection.Close();
_dbConnection.Dispose();
//_dbTransaction.Commit();
return Task.CompletedTask; return Task.CompletedTask;
} }
public void Dispose() public void Dispose()
{ {
_dbTransaction.Dispose(); //_dbTransaction.Dispose();
_dbConnection.Dispose(); _dbConnection.Dispose();
} }
} }
......
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 // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
......
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;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
...@@ -14,7 +17,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -14,7 +17,10 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UsePostgreSql(this CapOptions options, Action<PostgreSqlOptions> configure) public static CapOptions UsePostgreSql(this CapOptions options, Action<PostgreSqlOptions> configure)
{ {
if (configure == null) throw new ArgumentNullException(nameof(configure)); if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
options.RegisterExtension(new PostgreSqlCapOptionsExtension(configure)); options.RegisterExtension(new PostgreSqlCapOptionsExtension(configure));
...@@ -30,7 +36,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -30,7 +36,10 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure) public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure)
where TContext : DbContext where TContext : DbContext
{ {
if (configure == null) throw new ArgumentNullException(nameof(configure)); if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
options.RegisterExtension(new PostgreSqlCapOptionsExtension(x => options.RegisterExtension(new PostgreSqlCapOptionsExtension(x =>
{ {
......
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.PostgreSql;
using DotNetCore.CAP.Processor; using DotNetCore.CAP.Processor;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
...@@ -40,7 +43,7 @@ namespace DotNetCore.CAP ...@@ -40,7 +43,7 @@ namespace DotNetCore.CAP
using (var scope = x.CreateScope()) using (var scope = x.CreateScope())
{ {
var provider = scope.ServiceProvider; var provider = scope.ServiceProvider;
var dbContext = (DbContext)provider.GetService(postgreSqlOptions.DbContextType); var dbContext = (DbContext) provider.GetService(postgreSqlOptions.DbContextType);
postgreSqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; postgreSqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString;
return postgreSqlOptions; return postgreSqlOptions;
} }
......
// 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 namespace DotNetCore.CAP
{ {
......
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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper; using Dapper;
...@@ -14,16 +17,14 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -14,16 +17,14 @@ namespace DotNetCore.CAP.PostgreSql
public class CapPublisher : CapPublisherBase, ICallbackPublisher public class CapPublisher : CapPublisherBase, ICallbackPublisher
{ {
private readonly DbContext _dbContext; private readonly DbContext _dbContext;
private readonly ILogger _logger;
private readonly PostgreSqlOptions _options; private readonly PostgreSqlOptions _options;
public CapPublisher(IServiceProvider provider, public CapPublisher(ILogger<CapPublisher> logger, IDispatcher dispatcher,
ILogger<CapPublisher> logger, IServiceProvider provider, PostgreSqlOptions options)
PostgreSqlOptions options) : base(logger, dispatcher)
{ {
ServiceProvider = provider; ServiceProvider = provider;
_options = options; _options = options;
_logger = logger;
if (_options.DbContextType != null) if (_options.DbContextType != null)
{ {
...@@ -31,12 +32,14 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -31,12 +32,14 @@ namespace DotNetCore.CAP.PostgreSql
_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 NpgsqlConnection(_options.ConnectionString)) using (var conn = new NpgsqlConnection(_options.ConnectionString))
{ {
await conn.ExecuteAsync(PrepareSql(), message); var id = await conn.ExecuteScalarAsync<int>(PrepareSql(), message);
message.Id = id;
Enqueue(message);
} }
} }
...@@ -53,23 +56,20 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -53,23 +56,20 @@ namespace DotNetCore.CAP.PostgreSql
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted);
dbTrans = dbContextTransaction.GetDbTransaction(); dbTrans = dbContextTransaction.GetDbTransaction();
} }
DbTransaction = dbTrans; DbTransaction = dbTrans;
} }
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, protected override int Execute(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message) CapPublishedMessage message)
{ {
dbConnection.Execute(PrepareSql(), message, dbTransaction); return dbConnection.ExecuteScalar<int>(PrepareSql(), message, dbTransaction);
_logger.LogInformation("Published Message has been persisted in the database. name:" + message);
} }
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, protected override Task<int> ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message) CapPublishedMessage message)
{ {
await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); return dbConnection.ExecuteScalarAsync<int>(PrepareSql(), message, dbTransaction);
_logger.LogInformation("Published Message has been persisted in the database. name:" + message);
} }
#region private methods #region private methods
...@@ -77,7 +77,7 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -77,7 +77,7 @@ namespace DotNetCore.CAP.PostgreSql
private string PrepareSql() private string PrepareSql()
{ {
return 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 #endregion private methods
......
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="1.50.4" /> <PackageReference Include="Dapper" Version="1.50.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.2" />
<PackageReference Include="Npgsql" Version="3.2.7" /> <PackageReference Include="Npgsql" Version="3.2.7" />
</ItemGroup> </ItemGroup>
......
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
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 System.Threading.Tasks;
using Dapper; using Dapper;
using DotNetCore.CAP.Processor; using DotNetCore.CAP.Processor;
......
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
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.Collections.Generic;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
...@@ -27,9 +30,7 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -27,9 +30,7 @@ namespace DotNetCore.CAP.PostgreSql
select count(""Id"") from ""{0}"".""published"" where ""StatusName"" = N'Succeeded'; 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}"".""received"" where ""StatusName"" = N'Succeeded';
select count(""Id"") from ""{0}"".""published"" where ""StatusName"" = N'Failed'; 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}"".""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');",
_options.Schema); _options.Schema);
var statistics = UseConnection(connection => var statistics = UseConnection(connection =>
...@@ -42,10 +43,8 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce ...@@ -42,10 +43,8 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce
stats.PublishedFailed = multi.ReadSingle<int>(); stats.PublishedFailed = multi.ReadSingle<int>();
stats.ReceivedFailed = multi.ReadSingle<int>(); stats.ReceivedFailed = multi.ReadSingle<int>();
stats.PublishedProcessing = multi.ReadSingle<int>();
stats.ReceivedProcessing = multi.ReadSingle<int>();
} }
return stats; return stats;
}); });
return statistics; return statistics;
...@@ -57,17 +56,24 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce ...@@ -57,17 +56,24 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce
var where = string.Empty; var where = string.Empty;
if (!string.IsNullOrEmpty(queryDto.StatusName)) if (!string.IsNullOrEmpty(queryDto.StatusName))
if (string.Equals(queryDto.StatusName, StatusName.Processing, {
StringComparison.CurrentCultureIgnoreCase)) where += " and Lower(\"StatusName\") = Lower(@StatusName)";
where += " and \"StatusName\" in (N'Processing',N'Scheduled',N'Enqueued')"; }
else
where += " and Lower(\"StatusName\") = Lower(@StatusName)";
if (!string.IsNullOrEmpty(queryDto.Name)) if (!string.IsNullOrEmpty(queryDto.Name))
{
where += " and Lower(\"Name\") = Lower(@Name)"; where += " and Lower(\"Name\") = Lower(@Name)";
}
if (!string.IsNullOrEmpty(queryDto.Group)) if (!string.IsNullOrEmpty(queryDto.Group))
{
where += " and Lower(\"Group\") = Lower(@Group)"; where += " and Lower(\"Group\") = Lower(@Group)";
}
if (!string.IsNullOrEmpty(queryDto.Content)) if (!string.IsNullOrEmpty(queryDto.Content))
{
where += " and \"Content\" ILike '%@Content%'"; where += " and \"Content\" ILike '%@Content%'";
}
var sqlQuery = var sqlQuery =
$"select * from \"{_options.Schema}\".\"{tableName}\" where 1=1 {where} order by \"Added\" desc offset @Offset limit @Limit"; $"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 ...@@ -88,11 +94,6 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce
return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Failed)); return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Failed));
} }
public int PublishedProcessingCount()
{
return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Processing));
}
public int PublishedSucceededCount() public int PublishedSucceededCount()
{ {
return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Succeeded)); return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Succeeded));
...@@ -103,11 +104,6 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce ...@@ -103,11 +104,6 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce
return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Failed)); return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Failed));
} }
public int ReceivedProcessingCount()
{
return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Processing));
}
public int ReceivedSucceededCount() public int ReceivedSucceededCount()
{ {
return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Succeeded)); return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Succeeded));
...@@ -129,11 +125,10 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce ...@@ -129,11 +125,10 @@ select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Proce
private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName) private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName)
{ {
var sqlQuery = statusName == StatusName.Processing var sqlQuery =
? $"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)";
: $"select count(\"Id\") from \"{_options.Schema}\".\"{tableName}\" where Lower(\"StatusName\") = Lower(@state)";
var count = connection.ExecuteScalar<int>(sqlQuery, new { state = statusName }); var count = connection.ExecuteScalar<int>(sqlQuery, new {state = statusName});
return count; return count;
} }
...@@ -175,12 +170,17 @@ with aggr as ( ...@@ -175,12 +170,17 @@ with aggr as (
) )
select ""Key"",""Count"" from aggr where ""Key""= Any(@keys);"; select ""Key"",""Count"" from aggr where ""Key""= Any(@keys);";
var valuesMap = connection.Query(sqlQuery,new { keys = keyMaps.Keys.ToList(), statusName }) var valuesMap = connection.Query(sqlQuery, new {keys = keyMaps.Keys.ToList(), statusName})
.ToList() .ToList()
.ToDictionary(x => (string)x.Key, x => (int)x.Count); .ToDictionary(x => (string) x.Key, x => (int) x.Count);
foreach (var key in keyMaps.Keys) 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<DateTime, int>(); var result = new Dictionary<DateTime, int>();
for (var i = 0; i < keyMaps.Count; i++) for (var i = 0; i < keyMaps.Count; i++)
......
// 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;
using System.Data; using System.Data;
using System.Threading; using System.Threading;
...@@ -11,9 +14,9 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -11,9 +14,9 @@ namespace DotNetCore.CAP.PostgreSql
{ {
public class PostgreSqlStorage : IStorage public class PostgreSqlStorage : IStorage
{ {
private readonly CapOptions _capOptions;
private readonly IDbConnection _existingConnection = null; private readonly IDbConnection _existingConnection = null;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly CapOptions _capOptions;
private readonly PostgreSqlOptions _options; private readonly PostgreSqlOptions _options;
public PostgreSqlStorage(ILogger<PostgreSqlStorage> logger, public PostgreSqlStorage(ILogger<PostgreSqlStorage> logger,
...@@ -37,7 +40,10 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -37,7 +40,10 @@ namespace DotNetCore.CAP.PostgreSql
public async Task InitializeAsync(CancellationToken cancellationToken) public async Task InitializeAsync(CancellationToken cancellationToken)
{ {
if (cancellationToken.IsCancellationRequested) return; if (cancellationToken.IsCancellationRequested)
{
return;
}
var sql = CreateDbTablesScript(_options.Schema); var sql = CreateDbTablesScript(_options.Schema);
...@@ -45,6 +51,7 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -45,6 +51,7 @@ namespace DotNetCore.CAP.PostgreSql
{ {
await connection.ExecuteAsync(sql); await connection.ExecuteAsync(sql);
} }
_logger.LogDebug("Ensuring all create database tables script are applied."); _logger.LogDebug("Ensuring all create database tables script are applied.");
} }
...@@ -68,7 +75,9 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -68,7 +75,9 @@ namespace DotNetCore.CAP.PostgreSql
var connection = _existingConnection ?? new NpgsqlConnection(_options.ConnectionString); var connection = _existingConnection ?? new NpgsqlConnection(_options.ConnectionString);
if (connection.State == ConnectionState.Closed) if (connection.State == ConnectionState.Closed)
{
connection.Open(); connection.Open();
}
return connection; return connection;
} }
...@@ -81,7 +90,9 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -81,7 +90,9 @@ namespace DotNetCore.CAP.PostgreSql
internal void ReleaseConnection(IDbConnection connection) internal void ReleaseConnection(IDbConnection connection)
{ {
if (connection != null && !IsExistingConnection(connection)) if (connection != null && !IsExistingConnection(connection))
{
connection.Dispose(); connection.Dispose();
}
} }
protected virtual string CreateDbTablesScript(string schema) protected virtual string CreateDbTablesScript(string schema)
...@@ -89,10 +100,7 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -89,10 +100,7 @@ namespace DotNetCore.CAP.PostgreSql
var batchSql = $@" var batchSql = $@"
CREATE SCHEMA IF NOT EXISTS ""{schema}""; CREATE SCHEMA IF NOT EXISTS ""{schema}"";
CREATE TABLE IF NOT EXISTS ""{schema}"".""queue""( DROP TABLE IF EXISTS ""{schema}"".""queue"";
""MessageId"" int NOT NULL ,
""MessageType"" int NOT NULL
);
CREATE TABLE IF NOT EXISTS ""{schema}"".""received""( CREATE TABLE IF NOT EXISTS ""{schema}"".""received""(
""Id"" SERIAL PRIMARY KEY NOT NULL, ""Id"" SERIAL PRIMARY KEY NOT NULL,
......
// 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper; using Dapper;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
...@@ -36,44 +38,31 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -36,44 +38,31 @@ namespace DotNetCore.CAP.PostgreSql
} }
} }
public Task<IFetchedMessage> FetchNextMessageAsync() public async Task<IEnumerable<CapPublishedMessage>> GetPublishedMessagesOfNeedRetry()
{
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<CapPublishedMessage> GetNextPublishedMessageToBeEnqueuedAsync()
{ {
var fourMinsAgo = DateTime.Now.AddMinutes(-4);
var sql = 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)) using (var connection = new NpgsqlConnection(Options.ConnectionString))
{ {
return await connection.QueryFirstOrDefaultAsync<CapPublishedMessage>(sql); return await connection.QueryAsync<CapPublishedMessage>(sql);
} }
} }
public async Task<IEnumerable<CapPublishedMessage>> GetFailedPublishedMessages() public async Task<int> StoreReceivedMessageAsync(CapReceivedMessage message)
{ {
var sql = if (message == null)
$"SELECT * FROM \"{Options.Schema}\".\"published\" WHERE \"Retries\"<{_capOptions.FailedRetryCount} AND \"StatusName\"='{StatusName.Failed}' LIMIT 200;";
using (var connection = new NpgsqlConnection(Options.ConnectionString))
{ {
return await connection.QueryAsync<CapPublishedMessage>(sql); throw new ArgumentNullException(nameof(message));
} }
}
public async Task StoreReceivedMessageAsync(CapReceivedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = 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)) using (var connection = new NpgsqlConnection(Options.ConnectionString))
{ {
await connection.ExecuteAsync(sql, message); return await connection.ExecuteScalarAsync<int>(sql, message);
} }
} }
...@@ -86,20 +75,11 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -86,20 +75,11 @@ namespace DotNetCore.CAP.PostgreSql
} }
} }
public async Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync() public async Task<IEnumerable<CapReceivedMessage>> GetReceivedMessagesOfNeedRetry()
{
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<CapReceivedMessage>(sql);
}
}
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages()
{ {
var fourMinsAgo = DateTime.Now.AddMinutes(-4);
var sql = 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)) using (var connection = new NpgsqlConnection(Options.ConnectionString))
{ {
return await connection.QueryAsync<CapReceivedMessage>(sql); return await connection.QueryAsync<CapReceivedMessage>(sql);
...@@ -131,35 +111,5 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -131,35 +111,5 @@ namespace DotNetCore.CAP.PostgreSql
return connection.Execute(sql) > 0; return connection.Execute(sql) > 0;
} }
} }
private async Task<IFetchedMessage> 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<FetchedMessage>(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
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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper; using Dapper;
...@@ -26,7 +29,10 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -26,7 +29,10 @@ namespace DotNetCore.CAP.PostgreSql
public void UpdateMessage(CapPublishedMessage message) public void UpdateMessage(CapPublishedMessage message)
{ {
if (message == null) throw new ArgumentNullException(nameof(message)); if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
var sql = var sql =
$@"UPDATE ""{ $@"UPDATE ""{
...@@ -37,7 +43,10 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -37,7 +43,10 @@ namespace DotNetCore.CAP.PostgreSql
public void UpdateMessage(CapReceivedMessage message) public void UpdateMessage(CapReceivedMessage message)
{ {
if (message == null) throw new ArgumentNullException(nameof(message)); if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
var sql = var sql =
$@"UPDATE ""{ $@"UPDATE ""{
...@@ -46,9 +55,24 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -46,9 +55,24 @@ namespace DotNetCore.CAP.PostgreSql
_dbConnection.Execute(sql, message, _dbTransaction); _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) 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);"; var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish}, _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish},
...@@ -57,23 +81,14 @@ namespace DotNetCore.CAP.PostgreSql ...@@ -57,23 +81,14 @@ namespace DotNetCore.CAP.PostgreSql
public void EnqueueMessage(CapReceivedMessage message) 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);"; var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe}, _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe},
_dbTransaction); _dbTransaction);
} }
public Task CommitAsync()
{
_dbTransaction.Commit();
return Task.CompletedTask;
}
public void Dispose()
{
_dbTransaction.Dispose();
_dbConnection.Dispose();
}
} }
} }
\ No newline at end of file
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;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
...@@ -13,7 +16,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -13,7 +16,10 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseRabbitMQ(this CapOptions options, Action<RabbitMQOptions> configure) public static CapOptions UseRabbitMQ(this CapOptions options, Action<RabbitMQOptions> configure)
{ {
if (configure == null) throw new ArgumentNullException(nameof(configure)); if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
options.RegisterExtension(new RabbitMQCapOptionsExtension(configure)); options.RegisterExtension(new RabbitMQCapOptionsExtension(configure));
......
// 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 namespace DotNetCore.CAP
{ {
public class RabbitMQOptions public class RabbitMQOptions
...@@ -56,7 +58,7 @@ namespace DotNetCore.CAP ...@@ -56,7 +58,7 @@ namespace DotNetCore.CAP
/// <summary> /// <summary>
/// Topic exchange name when declare a topic exchange. /// Topic exchange name when declare a topic exchange.
/// </summary> /// </summary>
public string TopicExchangeName { get; set; } = DefaultExchangeName; public string ExchangeName { get; set; } = DefaultExchangeName;
/// <summary> /// <summary>
/// Timeout setting for connection attempts (in milliseconds). /// Timeout setting for connection attempts (in milliseconds).
......
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 DotNetCore.CAP.RabbitMQ;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
...@@ -24,8 +27,8 @@ namespace DotNetCore.CAP ...@@ -24,8 +27,8 @@ namespace DotNetCore.CAP
services.AddSingleton<IConsumerClientFactory, RabbitMQConsumerClientFactory>(); services.AddSingleton<IConsumerClientFactory, RabbitMQConsumerClientFactory>();
services.AddSingleton<IConnectionChannelPool, ConnectionChannelPool>(); services.AddSingleton<IConnectionChannelPool, ConnectionChannelPool>();
services.AddSingleton<IQueueExecutor, PublishQueueExecutor>(); services.AddSingleton<IPublishExecutor, RabbitMQPublishMessageSender>();
services.AddSingleton<IPublishExecutor, PublishQueueExecutor>(); services.AddSingleton<IPublishMessageSender, RabbitMQPublishMessageSender>();
} }
} }
} }
\ No newline at end of file
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 // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
......
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.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
...@@ -25,6 +28,8 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -25,6 +28,8 @@ namespace DotNetCore.CAP.RabbitMQ
_maxSize = DefaultPoolSize; _maxSize = DefaultPoolSize;
_connectionActivator = CreateConnection(options); _connectionActivator = CreateConnection(options);
HostAddress = options.HostName + ":" + options.Port;
Exchange = options.ExchangeName;
} }
IModel IConnectionChannelPool.Rent() IModel IConnectionChannelPool.Rent()
...@@ -37,10 +42,17 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -37,10 +42,17 @@ namespace DotNetCore.CAP.RabbitMQ
return Return(connection); return Return(connection);
} }
public string HostAddress { get; }
public string Exchange { get; }
public IConnection GetConnection() public IConnection GetConnection()
{ {
if (_connection != null && _connection.IsOpen) if (_connection != null && _connection.IsOpen)
{
return _connection; return _connection;
}
_connection = _connectionActivator(); _connection = _connectionActivator();
_connection.ConnectionShutdown += RabbitMQ_ConnectionShutdown; _connection.ConnectionShutdown += RabbitMQ_ConnectionShutdown;
return _connection; return _connection;
...@@ -51,7 +63,9 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -51,7 +63,9 @@ namespace DotNetCore.CAP.RabbitMQ
_maxSize = 0; _maxSize = 0;
while (_pool.TryDequeue(out var context)) while (_pool.TryDequeue(out var context))
{
context.Dispose(); context.Dispose();
}
} }
private static Func<IConnection> CreateConnection(RabbitMQOptions options) private static Func<IConnection> CreateConnection(RabbitMQOptions options)
......
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 namespace DotNetCore.CAP.RabbitMQ
{ {
public interface IConnectionChannelPool public interface IConnectionChannelPool
{ {
string HostAddress { get; }
string Exchange { get; }
IConnection GetConnection(); IConnection GetConnection();
IModel Rent(); IModel Rent();
......
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; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Processor.States; using DotNetCore.CAP.Processor.States;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using RabbitMQ.Client; using RabbitMQ.Client;
namespace DotNetCore.CAP.RabbitMQ namespace DotNetCore.CAP.RabbitMQ
{ {
internal sealed class PublishQueueExecutor : BasePublishQueueExecutor internal sealed class RabbitMQPublishMessageSender : BasePublishMessageSender
{ {
private readonly IConnectionChannelPool _connectionChannelPool; private readonly IConnectionChannelPool _connectionChannelPool;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly RabbitMQOptions _rabbitMQOptions; private readonly string _exchange;
public PublishQueueExecutor(ILogger<PublishQueueExecutor> logger, CapOptions options, public RabbitMQPublishMessageSender(ILogger<RabbitMQPublishMessageSender> logger, CapOptions options,
RabbitMQOptions rabbitMQOptions, IConnectionChannelPool connectionChannelPool, IStateChanger stateChanger) IStorageConnection connection, IConnectionChannelPool connectionChannelPool, IStateChanger stateChanger)
: base(options, stateChanger, logger) : base(logger, options, connection, stateChanger)
{ {
_logger = logger; _logger = logger;
_connectionChannelPool = connectionChannelPool; _connectionChannelPool = connectionChannelPool;
_rabbitMQOptions = rabbitMQOptions; _exchange = _connectionChannelPool.Exchange;
ServersAddress = _connectionChannelPool.HostAddress;
} }
public override Task<OperateResult> PublishAsync(string keyName, string content) public override Task<OperateResult> PublishAsync(string keyName, string content)
...@@ -28,12 +33,8 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -28,12 +33,8 @@ namespace DotNetCore.CAP.RabbitMQ
try try
{ {
var body = Encoding.UTF8.GetBytes(content); var body = Encoding.UTF8.GetBytes(content);
channel.ExchangeDeclare(_exchange, RabbitMQOptions.ExchangeType, true);
channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType, true); channel.BasicPublish(_exchange, keyName, null, body);
channel.BasicPublish(_rabbitMQOptions.TopicExchangeName,
keyName,
null,
body);
_logger.LogDebug($"RabbitMQ topic message [{keyName}] has been published."); _logger.LogDebug($"RabbitMQ topic message [{keyName}] has been published.");
...@@ -41,21 +42,22 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -41,21 +42,22 @@ namespace DotNetCore.CAP.RabbitMQ
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError( var wapperEx = new PublisherSentFailedException(ex.Message, ex);
$"RabbitMQ topic message [{keyName}] has been raised an exception of sending. the exception is: {ex.Message}"); var errors = new OperateError
{
Code = ex.HResult.ToString(),
Description = ex.Message
};
return Task.FromResult(OperateResult.Failed(ex, return Task.FromResult(OperateResult.Failed(wapperEx, errors));
new OperateError
{
Code = ex.HResult.ToString(),
Description = ex.Message
}));
} }
finally finally
{ {
var returned = _connectionChannelPool.Return(channel); var returned = _connectionChannelPool.Return(channel);
if (!returned) if (!returned)
{
channel.Dispose(); channel.Dispose();
}
} }
} }
} }
......
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.Collections.Generic;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
...@@ -13,9 +16,9 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -13,9 +16,9 @@ namespace DotNetCore.CAP.RabbitMQ
private readonly string _exchageName; private readonly string _exchageName;
private readonly string _queueName; private readonly string _queueName;
private readonly RabbitMQOptions _rabbitMQOptions; private readonly RabbitMQOptions _rabbitMQOptions;
private IModel _channel;
private IConnection _connection; private IConnection _connection;
private IModel _channel;
private ulong _deliveryTag; private ulong _deliveryTag;
public RabbitMQConsumerClient(string queueName, public RabbitMQConsumerClient(string queueName,
...@@ -25,7 +28,7 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -25,7 +28,7 @@ namespace DotNetCore.CAP.RabbitMQ
_queueName = queueName; _queueName = queueName;
_connectionChannelPool = connectionChannelPool; _connectionChannelPool = connectionChannelPool;
_rabbitMQOptions = options; _rabbitMQOptions = options;
_exchageName = options.TopicExchangeName; _exchageName = options.ExchangeName;
InitClient(); InitClient();
} }
...@@ -34,12 +37,19 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -34,12 +37,19 @@ namespace DotNetCore.CAP.RabbitMQ
public event EventHandler<LogMessageEventArgs> OnLog; public event EventHandler<LogMessageEventArgs> OnLog;
public string ServersAddress => _rabbitMQOptions.HostName;
public void Subscribe(IEnumerable<string> topics) public void Subscribe(IEnumerable<string> topics)
{ {
if (topics == null) throw new ArgumentNullException(nameof(topics)); if (topics == null)
{
throw new ArgumentNullException(nameof(topics));
}
foreach (var topic in topics) foreach (var topic in topics)
{
_channel.QueueBind(_queueName, _exchageName, topic); _channel.QueueBind(_queueName, _exchageName, topic);
}
} }
public void Listening(TimeSpan timeout, CancellationToken cancellationToken) public void Listening(TimeSpan timeout, CancellationToken cancellationToken)
...@@ -58,6 +68,7 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -58,6 +68,7 @@ namespace DotNetCore.CAP.RabbitMQ
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
cancellationToken.WaitHandle.WaitOne(timeout); cancellationToken.WaitHandle.WaitOne(timeout);
} }
// ReSharper disable once FunctionNeverReturns // ReSharper disable once FunctionNeverReturns
} }
...@@ -88,8 +99,9 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -88,8 +99,9 @@ namespace DotNetCore.CAP.RabbitMQ
RabbitMQOptions.ExchangeType, RabbitMQOptions.ExchangeType,
true); true);
var arguments = new Dictionary<string, object> { var arguments = new Dictionary<string, object>
{ "x-message-ttl", _rabbitMQOptions.QueueMessageExpires } {
{"x-message-ttl", _rabbitMQOptions.QueueMessageExpires}
}; };
_channel.QueueDeclare(_queueName, true, false, false, arguments); _channel.QueueDeclare(_queueName, true, false, false, arguments);
} }
...@@ -100,7 +112,7 @@ namespace DotNetCore.CAP.RabbitMQ ...@@ -100,7 +112,7 @@ namespace DotNetCore.CAP.RabbitMQ
{ {
var args = new LogMessageEventArgs var args = new LogMessageEventArgs
{ {
LogType = MqLogType.ConsumerCancelled, LogType = MqLogType.ConsumerCancelled,
Reason = e.ConsumerTag Reason = e.ConsumerTag
}; };
OnLog?.Invoke(sender, args); OnLog?.Invoke(sender, args);
......
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 internal sealed class RabbitMQConsumerClientFactory : IConsumerClientFactory
{ {
......
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 // ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
......
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;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
...@@ -14,7 +17,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -14,7 +17,10 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseSqlServer(this CapOptions options, Action<SqlServerOptions> configure) public static CapOptions UseSqlServer(this CapOptions options, Action<SqlServerOptions> configure)
{ {
if (configure == null) throw new ArgumentNullException(nameof(configure)); if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
options.RegisterExtension(new SqlServerCapOptionsExtension(configure)); options.RegisterExtension(new SqlServerCapOptionsExtension(configure));
...@@ -30,7 +36,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -30,7 +36,10 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure) public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure)
where TContext : DbContext where TContext : DbContext
{ {
if (configure == null) throw new ArgumentNullException(nameof(configure)); if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
options.RegisterExtension(new SqlServerCapOptionsExtension(x => options.RegisterExtension(new SqlServerCapOptionsExtension(x =>
{ {
......
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.Processor;
using DotNetCore.CAP.SqlServer; using DotNetCore.CAP.SqlServer;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
......
// 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 namespace DotNetCore.CAP
{ {
......
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;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -14,28 +17,31 @@ namespace DotNetCore.CAP.SqlServer ...@@ -14,28 +17,31 @@ namespace DotNetCore.CAP.SqlServer
public class CapPublisher : CapPublisherBase, ICallbackPublisher public class CapPublisher : CapPublisherBase, ICallbackPublisher
{ {
private readonly DbContext _dbContext; private readonly DbContext _dbContext;
private readonly ILogger _logger;
private readonly SqlServerOptions _options; private readonly SqlServerOptions _options;
public CapPublisher(IServiceProvider provider, public CapPublisher(ILogger<CapPublisher> logger, IDispatcher dispatcher,
ILogger<CapPublisher> logger, IServiceProvider provider, SqlServerOptions options)
SqlServerOptions options) : base(logger, dispatcher)
{ {
ServiceProvider = provider; ServiceProvider = provider;
_logger = logger;
_options = options; _options = options;
if (_options.DbContextType == null) return; if (_options.DbContextType == null)
{
return;
}
IsUsingEF = true; 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)) using (var conn = new SqlConnection(_options.ConnectionString))
{ {
await conn.ExecuteAsync(PrepareSql(), message); var id = await conn.ExecuteScalarAsync<int>(PrepareSql(), message);
message.Id = id;
Enqueue(message);
} }
} }
...@@ -52,23 +58,20 @@ namespace DotNetCore.CAP.SqlServer ...@@ -52,23 +58,20 @@ namespace DotNetCore.CAP.SqlServer
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted);
dbTrans = dbContextTransaction.GetDbTransaction(); dbTrans = dbContextTransaction.GetDbTransaction();
} }
DbTransaction = dbTrans; DbTransaction = dbTrans;
} }
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, protected override int Execute(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message) CapPublishedMessage message)
{ {
dbConnection.Execute(PrepareSql(), message, dbTransaction); return dbConnection.ExecuteScalar<int>(PrepareSql(), message, dbTransaction);
_logger.LogInformation("published message has been persisted to the database. name:" + message);
} }
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, protected override Task<int> ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message) CapPublishedMessage message)
{ {
await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); return dbConnection.ExecuteScalarAsync<int>(PrepareSql(), message, dbTransaction);
_logger.LogInformation("published message has been persisted to the database. name:" + message);
} }
#region private methods #region private methods
...@@ -76,7 +79,7 @@ namespace DotNetCore.CAP.SqlServer ...@@ -76,7 +79,7 @@ namespace DotNetCore.CAP.SqlServer
private string PrepareSql() private string PrepareSql()
{ {
return 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 #endregion private methods
......
...@@ -15,9 +15,9 @@ ...@@ -15,9 +15,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="1.50.4" /> <PackageReference Include="Dapper" Version="1.50.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.2" />
<PackageReference Include="System.Data.SqlClient" Version="4.4.2" /> <PackageReference Include="System.Data.SqlClient" Version="4.4.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
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
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.Data.SqlClient;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper; using Dapper;
......
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
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.Collections.Generic;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
...@@ -28,9 +31,7 @@ set transaction isolation level read committed; ...@@ -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}].Published with (nolock) where StatusName = N'Succeeded';
select count(Id) from [{0}].Received 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}].Published with (nolock) where StatusName = N'Failed';
select count(Id) from [{0}].Received 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');",
_options.Schema); _options.Schema);
var statistics = UseConnection(connection => var statistics = UseConnection(connection =>
...@@ -43,10 +44,8 @@ select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Proces ...@@ -43,10 +44,8 @@ select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Proces
stats.PublishedFailed = multi.ReadSingle<int>(); stats.PublishedFailed = multi.ReadSingle<int>();
stats.ReceivedFailed = multi.ReadSingle<int>(); stats.ReceivedFailed = multi.ReadSingle<int>();
stats.PublishedProcessing = multi.ReadSingle<int>();
stats.ReceivedProcessing = multi.ReadSingle<int>();
} }
return stats; return stats;
}); });
return statistics; return statistics;
...@@ -71,17 +70,24 @@ select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Proces ...@@ -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 tableName = queryDto.MessageType == MessageType.Publish ? "Published" : "Received";
var where = string.Empty; var where = string.Empty;
if (!string.IsNullOrEmpty(queryDto.StatusName)) if (!string.IsNullOrEmpty(queryDto.StatusName))
if (string.Equals(queryDto.StatusName, StatusName.Processing, {
StringComparison.CurrentCultureIgnoreCase)) where += " and statusname=@StatusName";
where += " and statusname in (N'Processing',N'Scheduled',N'Enqueued')"; }
else
where += " and statusname=@StatusName";
if (!string.IsNullOrEmpty(queryDto.Name)) if (!string.IsNullOrEmpty(queryDto.Name))
{
where += " and name=@Name"; where += " and name=@Name";
}
if (!string.IsNullOrEmpty(queryDto.Group)) if (!string.IsNullOrEmpty(queryDto.Group))
{
where += " and group=@Group"; where += " and group=@Group";
}
if (!string.IsNullOrEmpty(queryDto.Content)) if (!string.IsNullOrEmpty(queryDto.Content))
{
where += " and content like '%@Content%'"; where += " and content like '%@Content%'";
}
var sqlQuery = var sqlQuery =
$"select * from [{_options.Schema}].{tableName} where 1=1 {where} order by Added desc offset @Offset rows fetch next @Limit rows only"; $"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 ...@@ -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)); return UseConnection(conn => GetNumberOfMessage(conn, "Published", StatusName.Failed));
} }
public int PublishedProcessingCount()
{
return UseConnection(conn => GetNumberOfMessage(conn, "Published", StatusName.Processing));
}
public int PublishedSucceededCount() public int PublishedSucceededCount()
{ {
return UseConnection(conn => GetNumberOfMessage(conn, "Published", StatusName.Succeeded)); 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 ...@@ -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)); return UseConnection(conn => GetNumberOfMessage(conn, "Received", StatusName.Failed));
} }
public int ReceivedProcessingCount()
{
return UseConnection(conn => GetNumberOfMessage(conn, "Received", StatusName.Processing));
}
public int ReceivedSucceededCount() public int ReceivedSucceededCount()
{ {
return UseConnection(conn => GetNumberOfMessage(conn, "Received", StatusName.Succeeded)); 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 ...@@ -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) private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName)
{ {
var sqlQuery = statusName == StatusName.Processing var sqlQuery =
? $"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";
: $"select count(Id) from [{_options.Schema}].{tableName} with (nolock) where StatusName = @state";
var count = connection.ExecuteScalar<int>(sqlQuery, new {state = statusName}); var count = connection.ExecuteScalar<int>(sqlQuery, new {state = statusName});
return count; return count;
...@@ -182,7 +177,12 @@ select [Key], [Count] from aggr with (nolock) where [Key] in @keys;"; ...@@ -182,7 +177,12 @@ select [Key], [Count] from aggr with (nolock) where [Key] in @keys;";
.ToDictionary(x => (string) x.Key, x => (int) x.Count); .ToDictionary(x => (string) x.Key, x => (int) x.Count);
foreach (var key in keyMaps.Keys) 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<DateTime, int>(); var result = new Dictionary<DateTime, int>();
for (var i = 0; i < keyMaps.Count; i++) for (var i = 0; i < keyMaps.Count; i++)
......
// 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;
using System.Data; using System.Data;
using System.Data.SqlClient; using System.Data.SqlClient;
...@@ -11,9 +14,9 @@ namespace DotNetCore.CAP.SqlServer ...@@ -11,9 +14,9 @@ namespace DotNetCore.CAP.SqlServer
{ {
public class SqlServerStorage : IStorage public class SqlServerStorage : IStorage
{ {
private readonly CapOptions _capOptions;
private readonly IDbConnection _existingConnection = null; private readonly IDbConnection _existingConnection = null;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly CapOptions _capOptions;
private readonly SqlServerOptions _options; private readonly SqlServerOptions _options;
public SqlServerStorage(ILogger<SqlServerStorage> logger, public SqlServerStorage(ILogger<SqlServerStorage> logger,
...@@ -37,7 +40,10 @@ namespace DotNetCore.CAP.SqlServer ...@@ -37,7 +40,10 @@ namespace DotNetCore.CAP.SqlServer
public async Task InitializeAsync(CancellationToken cancellationToken) public async Task InitializeAsync(CancellationToken cancellationToken)
{ {
if (cancellationToken.IsCancellationRequested) return; if (cancellationToken.IsCancellationRequested)
{
return;
}
var sql = CreateDbTablesScript(_options.Schema); var sql = CreateDbTablesScript(_options.Schema);
...@@ -45,6 +51,7 @@ namespace DotNetCore.CAP.SqlServer ...@@ -45,6 +51,7 @@ namespace DotNetCore.CAP.SqlServer
{ {
await connection.ExecuteAsync(sql); await connection.ExecuteAsync(sql);
} }
_logger.LogDebug("Ensuring all create database tables script are applied."); _logger.LogDebug("Ensuring all create database tables script are applied.");
} }
...@@ -57,12 +64,9 @@ BEGIN ...@@ -57,12 +64,9 @@ BEGIN
EXEC('CREATE SCHEMA [{schema}]') EXEC('CREATE SCHEMA [{schema}]')
END; END;
IF OBJECT_ID(N'[{schema}].[Queue]',N'U') IS NULL IF OBJECT_ID(N'[{schema}].[Queue]',N'U') IS NOT NULL
BEGIN BEGIN
CREATE TABLE [{schema}].[Queue]( DROP TABLE [{schema}].[Queue];
[MessageId] [int] NOT NULL,
[MessageType] [tinyint] NOT NULL
) ON [PRIMARY]
END; END;
IF OBJECT_ID(N'[{schema}].[Received]',N'U') IS NULL IF OBJECT_ID(N'[{schema}].[Received]',N'U') IS NULL
...@@ -122,7 +126,9 @@ END;"; ...@@ -122,7 +126,9 @@ END;";
var connection = _existingConnection ?? new SqlConnection(_options.ConnectionString); var connection = _existingConnection ?? new SqlConnection(_options.ConnectionString);
if (connection.State == ConnectionState.Closed) if (connection.State == ConnectionState.Closed)
{
connection.Open(); connection.Open();
}
return connection; return connection;
} }
...@@ -135,7 +141,9 @@ END;"; ...@@ -135,7 +141,9 @@ END;";
internal void ReleaseConnection(IDbConnection connection) internal void ReleaseConnection(IDbConnection connection)
{ {
if (connection != null && !IsExistingConnection(connection)) if (connection != null && !IsExistingConnection(connection))
{
connection.Dispose(); connection.Dispose();
}
} }
} }
} }
\ No newline at end of file
// 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper; using Dapper;
...@@ -36,49 +38,32 @@ namespace DotNetCore.CAP.SqlServer ...@@ -36,49 +38,32 @@ namespace DotNetCore.CAP.SqlServer
} }
} }
public Task<IFetchedMessage> FetchNextMessageAsync() public async Task<IEnumerable<CapPublishedMessage>> GetPublishedMessagesOfNeedRetry()
{
var sql = $@"
DELETE TOP (1)
FROM [{Options.Schema}].[Queue] WITH (readpast, updlock, rowlock)
OUTPUT DELETED.MessageId,DELETED.[MessageType];";
return FetchNextMessageCoreAsync(sql);
}
public async Task<CapPublishedMessage> GetNextPublishedMessageToBeEnqueuedAsync()
{ {
var fourMinsAgo = DateTime.Now.AddMinutes(-4);
var sql = 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)) using (var connection = new SqlConnection(Options.ConnectionString))
{ {
return await connection.QueryFirstOrDefaultAsync<CapPublishedMessage>(sql); return await connection.QueryAsync<CapPublishedMessage>(sql);
} }
} }
public async Task<IEnumerable<CapPublishedMessage>> GetFailedPublishedMessages() public async Task<int> StoreReceivedMessageAsync(CapReceivedMessage message)
{ {
var sql = if (message == null)
$"SELECT TOP (200) * FROM [{Options.Schema}].[Published] WITH (readpast) WHERE Retries<{_capOptions.FailedRetryCount} AND StatusName = '{StatusName.Failed}'";
using (var connection = new SqlConnection(Options.ConnectionString))
{ {
return await connection.QueryAsync<CapPublishedMessage>(sql); throw new ArgumentNullException(nameof(message));
} }
}
public async Task StoreReceivedMessageAsync(CapReceivedMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
var sql = $@" var sql = $@"
INSERT INTO [{Options.Schema}].[Received]([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);"; VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);SELECT SCOPE_IDENTITY();";
using (var connection = new SqlConnection(Options.ConnectionString)) using (var connection = new SqlConnection(Options.ConnectionString))
{ {
await connection.ExecuteAsync(sql, message); return await connection.ExecuteScalarAsync<int>(sql, message);
} }
} }
...@@ -91,20 +76,11 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; ...@@ -91,20 +76,11 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
} }
} }
public async Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync() public async Task<IEnumerable<CapReceivedMessage>> GetReceivedMessagesOfNeedRetry()
{
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<CapReceivedMessage>(sql);
}
}
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages()
{ {
var fourMinsAgo = DateTime.Now.AddMinutes(-4);
var sql = 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)) using (var connection = new SqlConnection(Options.ConnectionString))
{ {
return await connection.QueryAsync<CapReceivedMessage>(sql); return await connection.QueryAsync<CapReceivedMessage>(sql);
...@@ -136,35 +112,5 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; ...@@ -136,35 +112,5 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
public void Dispose() public void Dispose()
{ {
} }
private async Task<IFetchedMessage> 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<FetchedMessage>(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
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;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -26,7 +29,10 @@ namespace DotNetCore.CAP.SqlServer ...@@ -26,7 +29,10 @@ namespace DotNetCore.CAP.SqlServer
public void UpdateMessage(CapPublishedMessage message) public void UpdateMessage(CapPublishedMessage message)
{ {
if (message == null) throw new ArgumentNullException(nameof(message)); if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
var sql = var sql =
$"UPDATE [{_schema}].[Published] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;"; $"UPDATE [{_schema}].[Published] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;";
...@@ -35,16 +41,34 @@ namespace DotNetCore.CAP.SqlServer ...@@ -35,16 +41,34 @@ namespace DotNetCore.CAP.SqlServer
public void UpdateMessage(CapReceivedMessage message) public void UpdateMessage(CapReceivedMessage message)
{ {
if (message == null) throw new ArgumentNullException(nameof(message)); if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
var sql = var sql =
$"UPDATE [{_schema}].[Received] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;"; $"UPDATE [{_schema}].[Received] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction); _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) 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);"; var sql = $"INSERT INTO [{_schema}].[Queue] values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish}, _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish},
...@@ -53,23 +77,14 @@ namespace DotNetCore.CAP.SqlServer ...@@ -53,23 +77,14 @@ namespace DotNetCore.CAP.SqlServer
public void EnqueueMessage(CapReceivedMessage message) 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);"; var sql = $"INSERT INTO [{_schema}].[Queue] values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe}, _dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe},
_dbTransaction); _dbTransaction);
} }
public Task CommitAsync()
{
_dbTransaction.Commit();
return Task.CompletedTask;
}
public void Dispose()
{
_dbTransaction.Dispose();
_dbConnection.Dispose();
}
} }
} }
\ No newline at end of file
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;
using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Diagnostics;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models; using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor; using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP.Abstractions namespace DotNetCore.CAP.Abstractions
{ {
public abstract class CapPublisherBase : ICapPublisher, IDisposable 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<CapPublisherBase> logger, IDispatcher dispatcher)
{
_logger = logger;
_dispatcher = dispatcher;
}
protected IDbConnection DbConnection { get; set; } protected IDbConnection DbConnection { get; set; }
protected IDbTransaction DbTransaction { get; set; } protected IDbTransaction DbTransaction { get; set; }
protected bool IsCapOpenedTrans { get; set; } protected bool IsCapOpenedTrans { get; set; }
...@@ -21,9 +40,7 @@ namespace DotNetCore.CAP.Abstractions ...@@ -21,9 +40,7 @@ namespace DotNetCore.CAP.Abstractions
CheckIsUsingEF(name); CheckIsUsingEF(name);
PrepareConnectionForEF(); PrepareConnectionForEF();
var content = Serialize(contentObj, callbackName); PublishWithTrans(name, contentObj, callbackName);
PublishWithTrans(name, content);
} }
public Task PublishAsync<T>(string name, T contentObj, string callbackName = null) public Task PublishAsync<T>(string name, T contentObj, string callbackName = null)
...@@ -31,9 +48,7 @@ namespace DotNetCore.CAP.Abstractions ...@@ -31,9 +48,7 @@ namespace DotNetCore.CAP.Abstractions
CheckIsUsingEF(name); CheckIsUsingEF(name);
PrepareConnectionForEF(); PrepareConnectionForEF();
var content = Serialize(contentObj, callbackName); return PublishWithTransAsync(name, contentObj, callbackName);
return PublishWithTransAsync(name, content);
} }
public void Publish<T>(string name, T contentObj, IDbTransaction dbTransaction, string callbackName = null) public void Publish<T>(string name, T contentObj, IDbTransaction dbTransaction, string callbackName = null)
...@@ -41,9 +56,7 @@ namespace DotNetCore.CAP.Abstractions ...@@ -41,9 +56,7 @@ namespace DotNetCore.CAP.Abstractions
CheckIsAdoNet(name); CheckIsAdoNet(name);
PrepareConnectionForAdo(dbTransaction); PrepareConnectionForAdo(dbTransaction);
var content = Serialize(contentObj, callbackName); PublishWithTrans(name, contentObj, callbackName);
PublishWithTrans(name, content);
} }
public Task PublishAsync<T>(string name, T contentObj, IDbTransaction dbTransaction, string callbackName = null) public Task PublishAsync<T>(string name, T contentObj, IDbTransaction dbTransaction, string callbackName = null)
...@@ -51,17 +64,20 @@ namespace DotNetCore.CAP.Abstractions ...@@ -51,17 +64,20 @@ namespace DotNetCore.CAP.Abstractions
CheckIsAdoNet(name); CheckIsAdoNet(name);
PrepareConnectionForAdo(dbTransaction); 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 PrepareConnectionForEF();
protected abstract void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, protected abstract int Execute(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message); CapPublishedMessage message);
protected abstract Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, protected abstract Task<int> ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message); CapPublishedMessage message);
protected virtual string Serialize<T>(T obj, string callbackName = null) protected virtual string Serialize<T>(T obj, string callbackName = null)
...@@ -89,7 +105,6 @@ namespace DotNetCore.CAP.Abstractions ...@@ -89,7 +105,6 @@ namespace DotNetCore.CAP.Abstractions
{ {
CallbackName = callbackName CallbackName = callbackName
}; };
return packer.Pack(message); return packer.Pack(message);
} }
...@@ -108,23 +123,38 @@ namespace DotNetCore.CAP.Abstractions ...@@ -108,23 +123,38 @@ namespace DotNetCore.CAP.Abstractions
private void CheckIsUsingEF(string name) private void CheckIsUsingEF(string name)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (!IsUsingEF) if (!IsUsingEF)
{
throw new InvalidOperationException( throw new InvalidOperationException(
"If you are using the EntityFramework, you need to configure the DbContextType first." + "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 IDbConnection and IDbTransaction.");
}
} }
private void CheckIsAdoNet(string name) private void CheckIsAdoNet(string name)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (IsUsingEF) if (IsUsingEF)
{
throw new InvalidOperationException( throw new InvalidOperationException(
"If you are using the EntityFramework, you do not need to use this overloaded."); "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<T>(string name, T contentObj, string callbackName = null)
{ {
Guid operationId = default(Guid);
var content = Serialize(contentObj, callbackName);
var message = new CapPublishedMessage var message = new CapPublishedMessage
{ {
Name = name, Name = name,
...@@ -132,15 +162,39 @@ namespace DotNetCore.CAP.Abstractions ...@@ -132,15 +162,39 @@ namespace DotNetCore.CAP.Abstractions
StatusName = StatusName.Scheduled 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<T>(string name, T contentObj, string callbackName = null)
{ {
Guid operationId = default(Guid);
var content = Serialize(contentObj, callbackName);
var message = new CapPublishedMessage var message = new CapPublishedMessage
{ {
Name = name, Name = name,
...@@ -148,11 +202,29 @@ namespace DotNetCore.CAP.Abstractions ...@@ -148,11 +202,29 @@ namespace DotNetCore.CAP.Abstractions
StatusName = StatusName.Scheduled 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() private void ClosedCap()
...@@ -162,8 +234,11 @@ namespace DotNetCore.CAP.Abstractions ...@@ -162,8 +234,11 @@ namespace DotNetCore.CAP.Abstractions
DbTransaction.Commit(); DbTransaction.Commit();
DbTransaction.Dispose(); DbTransaction.Dispose();
} }
if (IsCapOpenedConn) if (IsCapOpenedConn)
{
DbConnection.Dispose(); DbConnection.Dispose();
}
} }
public void Dispose() public void Dispose()
......
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; using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Abstractions namespace DotNetCore.CAP.Abstractions
{ {
/// <summary> /// <summary>
/// Message content serializer. /// Message content serializer.
/// <para>By default, CAP will use Json as a serializer, and you can customize this interface to achieve serialization of other methods.</para> /// <para>
/// By default, CAP will use Json as a serializer, and you can customize this interface to achieve serialization of
/// other methods.
/// </para>
/// </summary> /// </summary>
public interface IContentSerializer public interface IContentSerializer
{ {
...@@ -40,7 +46,7 @@ namespace DotNetCore.CAP.Abstractions ...@@ -40,7 +46,7 @@ namespace DotNetCore.CAP.Abstractions
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// We use the wrapper to provide some additional information for the message content,which is important for CAP。 /// 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 /// which includes interacting with other message components, which can be adapted in this manner
/// </remarks> /// </remarks>
public interface IMessagePacker public interface IMessagePacker
...@@ -52,9 +58,9 @@ namespace DotNetCore.CAP.Abstractions ...@@ -52,9 +58,9 @@ namespace DotNetCore.CAP.Abstractions
string Pack(CapMessage obj); string Pack(CapMessage obj);
/// <summary> /// <summary>
/// Unpack a message strings to <see cref="CapMessage"/> object. /// Unpack a message strings to <see cref="CapMessage" /> object.
/// </summary> /// </summary>
/// <param name="packingMessage">The string of packed message.</param> /// <param name="packingMessage">The string of packed message.</param>
CapMessage UnPack(string packingMessage); CapMessage UnPack(string packingMessage);
} }
} }
\ No newline at end of file
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; using DotNetCore.CAP.Abstractions.ModelBinding;
namespace DotNetCore.CAP.Abstractions namespace DotNetCore.CAP.Abstractions
......
using System.Threading.Tasks;
using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Abstractions
{
/// <summary>
/// Consumer method executor.
/// </summary>
public interface ISubscriberExecutor
{
/// <summary>
/// Execute the consumer method.
/// </summary>
/// <param name="receivedMessage">The received message.</param>
Task<OperateResult> ExecuteAsync(CapReceivedMessage receivedMessage);
}
}
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 namespace DotNetCore.CAP.Abstractions.ModelBinding
{ {
......
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 namespace DotNetCore.CAP.Abstractions.ModelBinding
{ {
...@@ -42,7 +45,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding ...@@ -42,7 +45,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding
public override string ToString() public override string ToString()
{ {
if (IsSuccess) if (IsSuccess)
{
return $"Success '{Model}'"; return $"Success '{Model}'";
}
return "Failed"; return "Failed";
} }
...@@ -50,7 +56,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding ...@@ -50,7 +56,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding
{ {
var other = obj as ModelBindingResult?; var other = obj as ModelBindingResult?;
if (other == null) if (other == null)
{
return false; return false;
}
return Equals(other.Value); return Equals(other.Value);
} }
......
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 namespace DotNetCore.CAP.Abstractions
{ {
......
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;
using DotNetCore.CAP.Dashboard.GatewayProxy; using DotNetCore.CAP.Dashboard.GatewayProxy;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
...@@ -19,7 +22,9 @@ namespace Microsoft.AspNetCore.Builder ...@@ -19,7 +22,9 @@ namespace Microsoft.AspNetCore.Builder
public static IApplicationBuilder UseCap(this IApplicationBuilder app) public static IApplicationBuilder UseCap(this IApplicationBuilder app)
{ {
if (app == null) if (app == null)
{
throw new ArgumentNullException(nameof(app)); throw new ArgumentNullException(nameof(app));
}
CheckRequirement(app); CheckRequirement(app);
...@@ -31,7 +36,10 @@ namespace Microsoft.AspNetCore.Builder ...@@ -31,7 +36,10 @@ namespace Microsoft.AspNetCore.Builder
if (provider.GetService<DashboardOptions>() != null) if (provider.GetService<DashboardOptions>() != null)
{ {
if (provider.GetService<DiscoveryOptions>() != null) if (provider.GetService<DiscoveryOptions>() != null)
{
app.UseMiddleware<GatewayProxyMiddleware>(); app.UseMiddleware<GatewayProxyMiddleware>();
}
app.UseMiddleware<DashboardMiddleware>(); app.UseMiddleware<DashboardMiddleware>();
} }
...@@ -42,18 +50,24 @@ namespace Microsoft.AspNetCore.Builder ...@@ -42,18 +50,24 @@ namespace Microsoft.AspNetCore.Builder
{ {
var marker = app.ApplicationServices.GetService<CapMarkerService>(); var marker = app.ApplicationServices.GetService<CapMarkerService>();
if (marker == null) if (marker == null)
{
throw new InvalidOperationException( throw new InvalidOperationException(
"AddCap() must be called on the service collection. eg: services.AddCap(...)"); "AddCap() must be called on the service collection. eg: services.AddCap(...)");
}
var messageQueueMarker = app.ApplicationServices.GetService<CapMessageQueueMakerService>(); var messageQueueMarker = app.ApplicationServices.GetService<CapMessageQueueMakerService>();
if (messageQueueMarker == null) if (messageQueueMarker == null)
{
throw new InvalidOperationException( throw new InvalidOperationException(
"You must be config used message queue provider at AddCap() options! eg: services.AddCap(options=>{ options.UseKafka(...) })"); "You must be config used message queue provider at AddCap() options! eg: services.AddCap(options=>{ options.UseKafka(...) })");
}
var databaseMarker = app.ApplicationServices.GetService<CapDatabaseStorageMarkerService>(); var databaseMarker = app.ApplicationServices.GetService<CapDatabaseStorageMarkerService>();
if (databaseMarker == null) if (databaseMarker == null)
{
throw new InvalidOperationException( throw new InvalidOperationException(
"You must be config used database provider at AddCap() options! eg: services.AddCap(options=>{ options.UseSqlServer(...) })"); "You must be config used database provider at AddCap() options! eg: services.AddCap(options=>{ options.UseSqlServer(...) })");
}
} }
} }
} }
\ No newline at end of file
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.Abstractions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
......
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.Collections.Generic;
using System.Reflection; using System.Reflection;
using DotNetCore.CAP.Models; using DotNetCore.CAP.Models;
...@@ -10,16 +13,6 @@ namespace DotNetCore.CAP ...@@ -10,16 +13,6 @@ namespace DotNetCore.CAP
/// </summary> /// </summary>
public class CapOptions public class CapOptions
{ {
/// <summary>
/// Default value for polling delay timeout, in seconds.
/// </summary>
public const int DefaultPollingDelay = 15;
/// <summary>
/// Default processor count to process messages of cap.queue.
/// </summary>
public const int DefaultQueueProcessorCount = 2;
/// <summary> /// <summary>
/// Default succeeded message expiration time span, in seconds. /// Default succeeded message expiration time span, in seconds.
/// </summary> /// </summary>
...@@ -28,18 +21,16 @@ namespace DotNetCore.CAP ...@@ -28,18 +21,16 @@ namespace DotNetCore.CAP
/// <summary> /// <summary>
/// Failed message retry waiting interval. /// Failed message retry waiting interval.
/// </summary> /// </summary>
public const int DefaultFailedMessageWaitingInterval = 600; public const int DefaultFailedMessageWaitingInterval = 60;
/// <summary> /// <summary>
/// Failed message retry count. /// Failed message retry count.
/// </summary> /// </summary>
public const int DefaultFailedRetryCount = 100; public const int DefaultFailedRetryCount = 50;
public CapOptions() public CapOptions()
{ {
PollingDelay = DefaultPollingDelay;
QueueProcessorCount = DefaultQueueProcessorCount;
SucceedMessageExpiredAfter = DefaultSucceedMessageExpirationAfter; SucceedMessageExpiredAfter = DefaultSucceedMessageExpirationAfter;
FailedRetryInterval = DefaultFailedMessageWaitingInterval; FailedRetryInterval = DefaultFailedMessageWaitingInterval;
FailedRetryCount = DefaultFailedRetryCount; FailedRetryCount = DefaultFailedRetryCount;
...@@ -54,18 +45,6 @@ namespace DotNetCore.CAP ...@@ -54,18 +45,6 @@ namespace DotNetCore.CAP
/// </summary> /// </summary>
public string DefaultGroup { get; set; } public string DefaultGroup { get; set; }
/// <summary>
/// Producer job polling delay time.
/// Default is 15 sec.
/// </summary>
public int PollingDelay { get; set; }
/// <summary>
/// Gets or sets the messages queue (Cap.Queue table) processor count.
/// Default is 2 processor.
/// </summary>
public int QueueProcessorCount { get; set; }
/// <summary> /// <summary>
/// Sent or received succeed message after time span of due, then the message will be deleted at due time. /// Sent or received succeed message after time span of due, then the message will be deleted at due time.
/// Default is 24*3600 seconds. /// Default is 24*3600 seconds.
...@@ -74,7 +53,7 @@ namespace DotNetCore.CAP ...@@ -74,7 +53,7 @@ namespace DotNetCore.CAP
/// <summary> /// <summary>
/// Failed messages polling delay time. /// Failed messages polling delay time.
/// Default is 600 seconds. /// Default is 60 seconds.
/// </summary> /// </summary>
public int FailedRetryInterval { get; set; } public int FailedRetryInterval { get; set; }
...@@ -85,7 +64,7 @@ namespace DotNetCore.CAP ...@@ -85,7 +64,7 @@ namespace DotNetCore.CAP
/// <summary> /// <summary>
/// The number of message retries, the retry will stop when the threshold is reached. /// The number of message retries, the retry will stop when the threshold is reached.
/// Default is 100 times. /// Default is 50 times.
/// </summary> /// </summary>
public int FailedRetryCount { get; set; } public int FailedRetryCount { get; set; }
...@@ -96,7 +75,9 @@ namespace DotNetCore.CAP ...@@ -96,7 +75,9 @@ namespace DotNetCore.CAP
public void RegisterExtension(ICapOptionsExtension extension) public void RegisterExtension(ICapOptionsExtension extension)
{ {
if (extension == null) if (extension == null)
{
throw new ArgumentNullException(nameof(extension)); throw new ArgumentNullException(nameof(extension));
}
Extensions.Add(extension); Extensions.Add(extension);
} }
......
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.Collections.Generic;
using DotNetCore.CAP; using DotNetCore.CAP;
using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Abstractions;
...@@ -25,7 +28,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -25,7 +28,10 @@ namespace Microsoft.Extensions.DependencyInjection
this IServiceCollection services, this IServiceCollection services,
Action<CapOptions> setupAction) Action<CapOptions> setupAction)
{ {
if (setupAction == null) throw new ArgumentNullException(nameof(setupAction)); if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
services.TryAddSingleton<CapMarkerService>(); services.TryAddSingleton<CapMarkerService>();
services.Configure(setupAction); services.Configure(setupAction);
...@@ -49,21 +55,21 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -49,21 +55,21 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<IStateChanger, StateChanger>(); services.AddSingleton<IStateChanger, StateChanger>();
//Queue's message processor //Queue's message processor
services.AddTransient<PublishQueuer>(); services.AddTransient<NeedRetryMessageProcessor>();
services.AddTransient<SubscribeQueuer>();
services.AddTransient<FailedProcessor>();
services.AddTransient<IDispatcher, DefaultDispatcher>();
//Executors //Sender and Executors
services.AddSingleton<IQueueExecutorFactory, QueueExecutorFactory>(); services.AddSingleton<IDispatcher, Dispatcher>();
services.AddSingleton<IQueueExecutor, SubscribeQueueExecutor>(); // Warning: IPublishMessageSender need to inject at extension project.
services.TryAddSingleton<ISubscriberExecutor, DefaultSubscriberExecutor>(); services.AddSingleton<ISubscriberExecutor, DefaultSubscriberExecutor>();
//Options and extension service //Options and extension service
var options = new CapOptions(); var options = new CapOptions();
setupAction(options); setupAction(options);
foreach (var serviceExtension in options.Extensions) foreach (var serviceExtension in options.Extensions)
{
serviceExtension.AddServices(services); serviceExtension.AddServices(services);
}
services.AddSingleton(options); services.AddSingleton(options);
return new CapBuilder(services); return new CapBuilder(services);
...@@ -73,13 +79,19 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -73,13 +79,19 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
var consumerListenerServices = new List<KeyValuePair<Type, Type>>(); var consumerListenerServices = new List<KeyValuePair<Type, Type>>();
foreach (var rejectedServices in services) foreach (var rejectedServices in services)
{
if (rejectedServices.ImplementationType != null if (rejectedServices.ImplementationType != null
&& typeof(ICapSubscribe).IsAssignableFrom(rejectedServices.ImplementationType)) && typeof(ICapSubscribe).IsAssignableFrom(rejectedServices.ImplementationType))
{
consumerListenerServices.Add(new KeyValuePair<Type, Type>(typeof(ICapSubscribe), consumerListenerServices.Add(new KeyValuePair<Type, Type>(typeof(ICapSubscribe),
rejectedServices.ImplementationType)); rejectedServices.ImplementationType));
}
}
foreach (var service in consumerListenerServices) foreach (var service in consumerListenerServices)
{
services.AddTransient(service.Key, service.Value); services.AddTransient(service.Key, service.Value);
}
} }
} }
} }
\ No newline at end of file
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.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
......
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.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -27,7 +30,10 @@ namespace DotNetCore.CAP ...@@ -27,7 +30,10 @@ namespace DotNetCore.CAP
public Task Invoke(HttpContext context) public Task Invoke(HttpContext context)
{ {
if (!context.Request.Path.StartsWithSegments(_options.PathMatch, 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 // Update the path
var path = context.Request.Path; var path = context.Request.Path;
...@@ -41,7 +47,9 @@ namespace DotNetCore.CAP ...@@ -41,7 +47,9 @@ namespace DotNetCore.CAP
var findResult = _routes.FindDispatcher(context.Request.Path.Value); var findResult = _routes.FindDispatcher(context.Request.Path.Value);
if (findResult == null) if (findResult == null)
{
return _next.Invoke(context); return _next.Invoke(context);
}
if (_options.Authorization.Any(filter => !filter.Authorize(dashboardContext))) if (_options.Authorization.Any(filter => !filter.Authorize(dashboardContext)))
{ {
......
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; using DotNetCore.CAP.Dashboard;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
......
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;
using DotNetCore.CAP.Dashboard; using DotNetCore.CAP.Dashboard;
using DotNetCore.CAP.Dashboard.GatewayProxy; using DotNetCore.CAP.Dashboard.GatewayProxy;
...@@ -40,7 +43,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -40,7 +43,10 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseDashboard(this CapOptions capOptions, Action<DashboardOptions> options) public static CapOptions UseDashboard(this CapOptions capOptions, Action<DashboardOptions> options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
capOptions.RegisterExtension(new DashboardOptionsExtension(options)); capOptions.RegisterExtension(new DashboardOptionsExtension(options));
......
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 namespace DotNetCore.CAP.Dashboard
{ {
...@@ -22,10 +25,12 @@ namespace DotNetCore.CAP.Dashboard ...@@ -22,10 +25,12 @@ namespace DotNetCore.CAP.Dashboard
protected override void WriteResponse(DashboardResponse response) protected override void WriteResponse(DashboardResponse response)
{ {
foreach (var resourceName in _resourceNames) foreach (var resourceName in _resourceNames)
{
WriteResource( WriteResource(
response, response,
_assembly, _assembly,
$"{_baseNamespace}.{resourceName}"); $"{_baseNamespace}.{resourceName}");
}
} }
} }
} }
\ No newline at end of file
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.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -25,9 +28,13 @@ namespace DotNetCore.CAP.Dashboard ...@@ -25,9 +28,13 @@ namespace DotNetCore.CAP.Dashboard
} }
if (_command(context)) if (_command(context))
{
response.StatusCode = (int) HttpStatusCode.NoContent; response.StatusCode = (int) HttpStatusCode.NoContent;
}
else else
{
response.StatusCode = 422; response.StatusCode = 422;
}
return Task.FromResult(true); return Task.FromResult(true);
} }
......
...@@ -879,15 +879,6 @@ namespace DotNetCore.CAP.Dashboard.Resources { ...@@ -879,15 +879,6 @@ namespace DotNetCore.CAP.Dashboard.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Processing.
/// </summary>
public static string SidebarMenu_Processing {
get {
return ResourceManager.GetString("SidebarMenu_Processing", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Succeeded. /// Looks up a localized string similar to Succeeded.
/// </summary> /// </summary>
......
...@@ -240,9 +240,6 @@ ...@@ -240,9 +240,6 @@
<data name="SidebarMenu_Failed" xml:space="preserve"> <data name="SidebarMenu_Failed" xml:space="preserve">
<value>Failed</value> <value>Failed</value>
</data> </data>
<data name="SidebarMenu_Processing" xml:space="preserve">
<value>Processing</value>
</data>
<data name="SidebarMenu_Succeeded" xml:space="preserve"> <data name="SidebarMenu_Succeeded" xml:space="preserve">
<value>Succeeded</value> <value>Succeeded</value>
</data> </data>
......
...@@ -234,9 +234,6 @@ ...@@ -234,9 +234,6 @@
<data name="SidebarMenu_Failed" xml:space="preserve"> <data name="SidebarMenu_Failed" xml:space="preserve">
<value>失败</value> <value>失败</value>
</data> </data>
<data name="SidebarMenu_Processing" xml:space="preserve">
<value>执行中</value>
</data>
<data name="SidebarMenu_Succeeded" xml:space="preserve"> <data name="SidebarMenu_Succeeded" xml:space="preserve">
<value>完成</value> <value>完成</value>
</data> </data>
......
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.Text.RegularExpressions;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
...@@ -8,8 +11,15 @@ namespace DotNetCore.CAP.Dashboard ...@@ -8,8 +11,15 @@ namespace DotNetCore.CAP.Dashboard
{ {
protected DashboardContext(IStorage storage, DashboardOptions options) protected DashboardContext(IStorage storage, DashboardOptions options)
{ {
if (storage == null) throw new ArgumentNullException(nameof(storage)); if (storage == null)
if (options == null) throw new ArgumentNullException(nameof(options)); {
throw new ArgumentNullException(nameof(storage));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
Storage = storage; Storage = storage;
Options = options; Options = options;
...@@ -36,7 +46,10 @@ namespace DotNetCore.CAP.Dashboard ...@@ -36,7 +46,10 @@ namespace DotNetCore.CAP.Dashboard
HttpContext httpContext) HttpContext httpContext)
: base(storage, options) : base(storage, options)
{ {
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
HttpContext = httpContext; HttpContext = httpContext;
Request = new CapDashboardRequest(httpContext); Request = new CapDashboardRequest(httpContext);
......
// 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;
namespace DotNetCore.CAP.Dashboard namespace DotNetCore.CAP.Dashboard
......
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.Collections.Generic;
using System.Linq; using System.Linq;
using DotNetCore.CAP.Dashboard.Resources; using DotNetCore.CAP.Dashboard.Resources;
...@@ -64,24 +67,6 @@ namespace DotNetCore.CAP.Dashboard ...@@ -64,24 +67,6 @@ namespace DotNetCore.CAP.Dashboard
} }
: null); : 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( public static readonly DashboardMetric PublishedSucceededCount = new DashboardMetric(
"published_succeeded:count", "published_succeeded:count",
...@@ -129,9 +114,6 @@ namespace DotNetCore.CAP.Dashboard ...@@ -129,9 +114,6 @@ namespace DotNetCore.CAP.Dashboard
AddMetric(PublishedFailedCountOrNull); AddMetric(PublishedFailedCountOrNull);
AddMetric(ReceivedFailedCountOrNull); AddMetric(ReceivedFailedCountOrNull);
AddMetric(PublishedProcessingCount);
AddMetric(ReceivedProcessingCount);
AddMetric(PublishedSucceededCount); AddMetric(PublishedSucceededCount);
AddMetric(ReceivedSucceededCount); AddMetric(ReceivedSucceededCount);
...@@ -141,7 +123,10 @@ namespace DotNetCore.CAP.Dashboard ...@@ -141,7 +123,10 @@ namespace DotNetCore.CAP.Dashboard
public static void AddMetric(DashboardMetric metric) public static void AddMetric(DashboardMetric metric)
{ {
if (metric == null) throw new ArgumentNullException(nameof(metric)); if (metric == null)
{
throw new ArgumentNullException(nameof(metric));
}
lock (Metrics) lock (Metrics)
{ {
......
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.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
...@@ -25,7 +28,11 @@ namespace DotNetCore.CAP.Dashboard ...@@ -25,7 +28,11 @@ namespace DotNetCore.CAP.Dashboard
public CapDashboardRequest(HttpContext context) public CapDashboardRequest(HttpContext context)
{ {
if (context == null) throw new ArgumentNullException(nameof(context)); if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
_context = context; _context = context;
} }
......
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.Globalization;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
......
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.Dashboard.Pages;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
......
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.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -47,8 +50,10 @@ namespace DotNetCore.CAP.Dashboard ...@@ -47,8 +50,10 @@ namespace DotNetCore.CAP.Dashboard
using (var inputStream = assembly.GetManifestResourceStream(resourceName)) using (var inputStream = assembly.GetManifestResourceStream(resourceName))
{ {
if (inputStream == null) if (inputStream == null)
{
throw new ArgumentException( throw new ArgumentException(
$@"Resource with name {resourceName} not found in assembly {assembly}."); $@"Resource with name {resourceName} not found in assembly {assembly}.");
}
inputStream.CopyTo(response.Body); inputStream.CopyTo(response.Body);
} }
......
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 public class DownstreamUrl
{ {
......
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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
...@@ -94,7 +97,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy ...@@ -94,7 +97,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
{ {
foreach (var httpResponseHeader in response.Content.Headers) foreach (var httpResponseHeader in response.Content.Headers)
{
AddHeaderIfDoesntExist(context, httpResponseHeader); AddHeaderIfDoesntExist(context, httpResponseHeader);
}
var content = await response.Content.ReadAsByteArrayAsync(); var content = await response.Content.ReadAsByteArrayAsync();
...@@ -113,7 +118,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy ...@@ -113,7 +118,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
using (Stream stream = new MemoryStream(content)) using (Stream stream = new MemoryStream(content))
{ {
if (response.StatusCode != HttpStatusCode.NotModified) if (response.StatusCode != HttpStatusCode.NotModified)
{
await stream.CopyToAsync(context.Response.Body); await stream.CopyToAsync(context.Response.Body);
}
} }
} }
...@@ -134,8 +141,10 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy ...@@ -134,8 +141,10 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
KeyValuePair<string, IEnumerable<string>> httpResponseHeader) KeyValuePair<string, IEnumerable<string>> httpResponseHeader)
{ {
if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key))
{
context.Response.Headers.Add(httpResponseHeader.Key, context.Response.Headers.Add(httpResponseHeader.Key,
new StringValues(httpResponseHeader.Value.ToArray())); new StringValues(httpResponseHeader.Value.ToArray()));
}
} }
} }
} }
\ No newline at end of file
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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
...@@ -45,7 +48,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy ...@@ -45,7 +48,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
FragmentString fragment = new FragmentString()) FragmentString fragment = new FragmentString())
{ {
if (scheme == null) if (scheme == null)
{
throw new ArgumentNullException(nameof(scheme)); throw new ArgumentNullException(nameof(scheme));
}
var combinedPath = pathBase.HasValue || path.HasValue ? (pathBase + path).ToString() : "/"; var combinedPath = pathBase.HasValue || path.HasValue ? (pathBase + path).ToString() : "/";
...@@ -75,7 +80,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy ...@@ -75,7 +80,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
private async Task<HttpContent> MapContent(HttpRequest request) private async Task<HttpContent> MapContent(HttpRequest request)
{ {
if (request.Body == null) if (request.Body == null)
{
return null; return null;
}
var content = new ByteArrayContent(await ToByteArray(request.Body)); var content = new ByteArrayContent(await ToByteArray(request.Body));
...@@ -97,8 +104,12 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy ...@@ -97,8 +104,12 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy
private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage)
{ {
foreach (var header in request.Headers) foreach (var header in request.Headers)
{
if (IsSupportedHeader(header)) if (IsSupportedHeader(header))
{
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
} }
private async Task<byte[]> ToByteArray(Stream stream) private async Task<byte[]> ToByteArray(Stream stream)
......
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 System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
......
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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
......
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.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
...@@ -44,7 +47,10 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester ...@@ -44,7 +47,10 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
var httpClient = _cacheHandlers.Get(cacheKey); var httpClient = _cacheHandlers.Get(cacheKey);
if (httpClient == null) if (httpClient == null)
{
httpClient = builder.Create(); httpClient = builder.Create();
}
return httpClient; return httpClient;
} }
......
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 System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
......
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 namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
{ {
......
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 namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
{ {
......
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 System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
......
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.Concurrent;
namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
...@@ -31,7 +34,10 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester ...@@ -31,7 +34,10 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester
{ {
IHttpClient client = null; IHttpClient client = null;
if (_httpClientsCache.TryGetValue(id, out var connectionQueue)) if (_httpClientsCache.TryGetValue(id, out var connectionQueue))
{
connectionQueue.TryDequeue(out client); connectionQueue.TryDequeue(out client);
}
return client; return client;
} }
......
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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
...@@ -24,44 +27,71 @@ namespace DotNetCore.CAP.Dashboard ...@@ -24,44 +27,71 @@ namespace DotNetCore.CAP.Dashboard
public NonEscapedString Breadcrumbs(string title, IDictionary<string, string> items) public NonEscapedString Breadcrumbs(string title, IDictionary<string, string> items)
{ {
if (items == null) throw new ArgumentNullException(nameof(items)); if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
return RenderPartial(new Breadcrumbs(title, items)); return RenderPartial(new Breadcrumbs(title, items));
} }
public NonEscapedString MessagesSidebar(MessageType type) public NonEscapedString MessagesSidebar(MessageType type)
{ {
if (type == MessageType.Publish) if (type == MessageType.Publish)
{
return SidebarMenu(MessagesSidebarMenu.PublishedItems); return SidebarMenu(MessagesSidebarMenu.PublishedItems);
}
return SidebarMenu(MessagesSidebarMenu.ReceivedItems); return SidebarMenu(MessagesSidebarMenu.ReceivedItems);
} }
public NonEscapedString SidebarMenu(IEnumerable<Func<RazorPage, MenuItem>> items) public NonEscapedString SidebarMenu(IEnumerable<Func<RazorPage, MenuItem>> items)
{ {
if (items == null) throw new ArgumentNullException(nameof(items)); if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
return RenderPartial(new SidebarMenu(items)); return RenderPartial(new SidebarMenu(items));
} }
public NonEscapedString BlockMetric(DashboardMetric metric) 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)); return RenderPartial(new BlockMetric(metric));
} }
public NonEscapedString InlineMetric(DashboardMetric 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)); return RenderPartial(new InlineMetric(metric));
} }
public NonEscapedString Paginator(Pager pager) 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)); return RenderPartial(new Paginator(pager));
} }
public NonEscapedString PerPageSelector(Pager 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)); return RenderPartial(new PerPageSelector(pager));
} }
...@@ -79,7 +109,9 @@ namespace DotNetCore.CAP.Dashboard ...@@ -79,7 +109,9 @@ namespace DotNetCore.CAP.Dashboard
public NonEscapedString StateLabel(string stateName) public NonEscapedString StateLabel(string stateName)
{ {
if (string.IsNullOrWhiteSpace(stateName)) if (string.IsNullOrWhiteSpace(stateName))
{
return Raw($"<em>{Strings.Common_NoState}</em>"); return Raw($"<em>{Strings.Common_NoState}</em>");
}
return Raw( return Raw(
$"<span class=\"label label-default\" style=\"background-color: {MessageHistoryRenderer.GetForegroundStateColor(stateName)};\">{stateName}</span>"); $"<span class=\"label label-default\" style=\"background-color: {MessageHistoryRenderer.GetForegroundStateColor(stateName)};\">{stateName}</span>");
...@@ -102,40 +134,59 @@ namespace DotNetCore.CAP.Dashboard ...@@ -102,40 +134,59 @@ namespace DotNetCore.CAP.Dashboard
public string ToHumanDuration(TimeSpan? duration, bool displaySign = true) public string ToHumanDuration(TimeSpan? duration, bool displaySign = true)
{ {
if (duration == null) return null; if (duration == null)
{
return null;
}
var builder = new StringBuilder(); var builder = new StringBuilder();
if (displaySign) if (displaySign)
{
builder.Append(duration.Value.TotalMilliseconds < 0 ? "-" : "+"); builder.Append(duration.Value.TotalMilliseconds < 0 ? "-" : "+");
}
duration = duration.Value.Duration(); duration = duration.Value.Duration();
if (duration.Value.Days > 0) if (duration.Value.Days > 0)
{
builder.Append($"{duration.Value.Days}d "); builder.Append($"{duration.Value.Days}d ");
}
if (duration.Value.Hours > 0) if (duration.Value.Hours > 0)
{
builder.Append($"{duration.Value.Hours}h "); builder.Append($"{duration.Value.Hours}h ");
}
if (duration.Value.Minutes > 0) if (duration.Value.Minutes > 0)
{
builder.Append($"{duration.Value.Minutes}m "); builder.Append($"{duration.Value.Minutes}m ");
}
if (duration.Value.TotalHours < 1) if (duration.Value.TotalHours < 1)
{
if (duration.Value.Seconds > 0) if (duration.Value.Seconds > 0)
{ {
builder.Append(duration.Value.Seconds); builder.Append(duration.Value.Seconds);
if (duration.Value.Milliseconds > 0) if (duration.Value.Milliseconds > 0)
{
builder.Append($".{duration.Value.Milliseconds.ToString().PadLeft(3, '0')}"); builder.Append($".{duration.Value.Milliseconds.ToString().PadLeft(3, '0')}");
}
builder.Append("s "); builder.Append("s ");
} }
else else
{ {
if (duration.Value.Milliseconds > 0) if (duration.Value.Milliseconds > 0)
{
builder.Append($"{duration.Value.Milliseconds}ms "); builder.Append($"{duration.Value.Milliseconds}ms ");
}
} }
}
if (builder.Length <= 1) if (builder.Length <= 1)
{
builder.Append(" <1ms "); builder.Append(" <1ms ");
}
builder.Remove(builder.Length - 1, 1); builder.Remove(builder.Length - 1, 1);
...@@ -237,14 +288,25 @@ namespace DotNetCore.CAP.Dashboard ...@@ -237,14 +288,25 @@ namespace DotNetCore.CAP.Dashboard
private string WrapType(Type type) private string WrapType(Type type)
{ {
if (type == null) if (type == null)
{
return string.Empty; return string.Empty;
}
if (type.Name == "Void") if (type.Name == "Void")
{
return WrapKeyword(type.Name.ToLower()); return WrapKeyword(type.Name.ToLower());
}
if (Helper.IsComplexType(type)) if (Helper.IsComplexType(type))
{
return WrapType(type.Name); return WrapType(type.Name);
}
if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal)) if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal))
{
return WrapKeyword(type.Name.ToLower()); return WrapKeyword(type.Name.ToLower());
}
return WrapType(type.Name); return WrapType(type.Name);
} }
......
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 public interface IDashboardAuthorizationFilter
{ {
......
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 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.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotNetCore.CAP.Dashboard.Monitoring; using DotNetCore.CAP.Dashboard.Monitoring;
...@@ -13,14 +16,10 @@ namespace DotNetCore.CAP.Dashboard ...@@ -13,14 +16,10 @@ namespace DotNetCore.CAP.Dashboard
int PublishedFailedCount(); int PublishedFailedCount();
int PublishedProcessingCount();
int PublishedSucceededCount(); int PublishedSucceededCount();
int ReceivedFailedCount(); int ReceivedFailedCount();
int ReceivedProcessingCount();
int ReceivedSucceededCount(); int ReceivedSucceededCount();
IDictionary<DateTime, int> HourlySucceededJobs(MessageType type); IDictionary<DateTime, int> HourlySucceededJobs(MessageType type);
......
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 System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
...@@ -37,7 +40,9 @@ namespace DotNetCore.CAP.Dashboard ...@@ -37,7 +40,9 @@ namespace DotNetCore.CAP.Dashboard
} }
if (_jsonCommand != null) if (_jsonCommand != null)
{
serialized = _jsonCommand(context); serialized = _jsonCommand(context);
}
context.Response.ContentType = "application/json"; context.Response.ContentType = "application/json";
await context.Response.WriteAsync(serialized ?? string.Empty); await context.Response.WriteAsync(serialized ?? string.Empty);
......
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.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
......
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 namespace DotNetCore.CAP.Dashboard
{ {
...@@ -8,19 +11,27 @@ namespace DotNetCore.CAP.Dashboard ...@@ -8,19 +11,27 @@ namespace DotNetCore.CAP.Dashboard
{ {
// if unknown, assume not local // if unknown, assume not local
if (string.IsNullOrEmpty(context.Request.RemoteIpAddress)) if (string.IsNullOrEmpty(context.Request.RemoteIpAddress))
{
return false; return false;
}
// check if localhost // check if localhost
if (context.Request.RemoteIpAddress == "127.0.0.1" || context.Request.RemoteIpAddress == "::1") if (context.Request.RemoteIpAddress == "127.0.0.1" || context.Request.RemoteIpAddress == "::1")
{
return true; return true;
}
// compare with local address // compare with local address
if (context.Request.RemoteIpAddress == context.Request.LocalIpAddress) if (context.Request.RemoteIpAddress == context.Request.LocalIpAddress)
{
return true; return true;
}
// check if private ip // check if private ip
if (Helper.IsInnerIP(context.Request.RemoteIpAddress)) if (Helper.IsInnerIP(context.Request.RemoteIpAddress))
{
return true; return true;
}
return false; return false;
} }
......
// 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.Collections.Generic;
using System.Linq; using System.Linq;
...@@ -23,7 +26,9 @@ namespace DotNetCore.CAP.Dashboard ...@@ -23,7 +26,9 @@ namespace DotNetCore.CAP.Dashboard
var metrics = new List<DashboardMetric> {Metric}; var metrics = new List<DashboardMetric> {Metric};
if (Metrics != null) if (Metrics != null)
{
metrics.AddRange(Metrics); metrics.AddRange(Metrics);
}
return metrics.Where(x => x != null).ToList(); return metrics.Where(x => x != null).ToList();
} }
......
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.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Net; using System.Net;
...@@ -23,18 +26,13 @@ namespace DotNetCore.CAP.Dashboard ...@@ -23,18 +26,13 @@ namespace DotNetCore.CAP.Dashboard
{ {
Register(StatusName.Succeeded, SucceededRenderer); Register(StatusName.Succeeded, SucceededRenderer);
Register(StatusName.Failed, FailedRenderer); Register(StatusName.Failed, FailedRenderer);
Register(StatusName.Processing, ProcessingRenderer);
BackgroundStateColors.Add(StatusName.Enqueued, "#F5F5F5");
BackgroundStateColors.Add(StatusName.Succeeded, "#EDF7ED"); BackgroundStateColors.Add(StatusName.Succeeded, "#EDF7ED");
BackgroundStateColors.Add(StatusName.Failed, "#FAEBEA"); BackgroundStateColors.Add(StatusName.Failed, "#FAEBEA");
BackgroundStateColors.Add(StatusName.Processing, "#FCEFDC");
BackgroundStateColors.Add(StatusName.Scheduled, "#E0F3F8"); BackgroundStateColors.Add(StatusName.Scheduled, "#E0F3F8");
ForegroundStateColors.Add(StatusName.Enqueued, "#999");
ForegroundStateColors.Add(StatusName.Succeeded, "#5cb85c"); ForegroundStateColors.Add(StatusName.Succeeded, "#5cb85c");
ForegroundStateColors.Add(StatusName.Failed, "#d9534f"); ForegroundStateColors.Add(StatusName.Failed, "#d9534f");
ForegroundStateColors.Add(StatusName.Processing, "#f0ad4e");
ForegroundStateColors.Add(StatusName.Scheduled, "#5bc0de"); ForegroundStateColors.Add(StatusName.Scheduled, "#5bc0de");
} }
...@@ -46,7 +44,9 @@ namespace DotNetCore.CAP.Dashboard ...@@ -46,7 +44,9 @@ namespace DotNetCore.CAP.Dashboard
public static string GetBackgroundStateColor(string stateName) public static string GetBackgroundStateColor(string stateName)
{ {
if (stateName == null || !BackgroundStateColors.ContainsKey(stateName)) if (stateName == null || !BackgroundStateColors.ContainsKey(stateName))
{
return "inherit"; return "inherit";
}
return BackgroundStateColors[stateName]; return BackgroundStateColors[stateName];
} }
...@@ -59,7 +59,9 @@ namespace DotNetCore.CAP.Dashboard ...@@ -59,7 +59,9 @@ namespace DotNetCore.CAP.Dashboard
public static string GetForegroundStateColor(string stateName) public static string GetForegroundStateColor(string stateName)
{ {
if (stateName == null || !ForegroundStateColors.ContainsKey(stateName)) if (stateName == null || !ForegroundStateColors.ContainsKey(stateName))
{
return "inherit"; return "inherit";
}
return ForegroundStateColors[stateName]; return ForegroundStateColors[stateName];
} }
...@@ -68,9 +70,13 @@ namespace DotNetCore.CAP.Dashboard ...@@ -68,9 +70,13 @@ namespace DotNetCore.CAP.Dashboard
Func<HtmlHelper, IDictionary<string, string>, NonEscapedString> renderer) Func<HtmlHelper, IDictionary<string, string>, NonEscapedString> renderer)
{ {
if (!Renderers.ContainsKey(state)) if (!Renderers.ContainsKey(state))
{
Renderers.Add(state, renderer); Renderers.Add(state, renderer);
}
else else
{
Renderers[state] = renderer; Renderers[state] = renderer;
}
} }
public static bool Exists(string state) public static bool Exists(string state)
...@@ -96,7 +102,10 @@ namespace DotNetCore.CAP.Dashboard ...@@ -96,7 +102,10 @@ namespace DotNetCore.CAP.Dashboard
public static NonEscapedString DefaultRenderer(HtmlHelper helper, IDictionary<string, string> stateData) public static NonEscapedString DefaultRenderer(HtmlHelper helper, IDictionary<string, string> stateData)
{ {
if (stateData == null || stateData.Count == 0) return null; if (stateData == null || stateData.Count == 0)
{
return null;
}
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.Append("<dl class=\"dl-horizontal\">"); builder.Append("<dl class=\"dl-horizontal\">");
...@@ -146,7 +155,10 @@ namespace DotNetCore.CAP.Dashboard ...@@ -146,7 +155,10 @@ namespace DotNetCore.CAP.Dashboard
builder.Append("</dl>"); builder.Append("</dl>");
if (!itemsAdded) return null; if (!itemsAdded)
{
return null;
}
return new NonEscapedString(builder.ToString()); return new NonEscapedString(builder.ToString());
} }
...@@ -166,9 +178,13 @@ namespace DotNetCore.CAP.Dashboard ...@@ -166,9 +178,13 @@ namespace DotNetCore.CAP.Dashboard
string serverId = null; string serverId = null;
if (stateData.ContainsKey("ServerId")) if (stateData.ContainsKey("ServerId"))
{
serverId = stateData["ServerId"]; serverId = stateData["ServerId"];
}
else if (stateData.ContainsKey("ServerName")) else if (stateData.ContainsKey("ServerName"))
{
serverId = stateData["ServerName"]; serverId = stateData["ServerName"];
}
if (serverId != null) if (serverId != null)
{ {
......
// 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;
using System.Collections.Generic; using System.Collections.Generic;
using DotNetCore.CAP.Dashboard.Resources; using DotNetCore.CAP.Dashboard.Resources;
...@@ -20,13 +23,6 @@ namespace DotNetCore.CAP.Dashboard ...@@ -20,13 +23,6 @@ namespace DotNetCore.CAP.Dashboard
Metric = DashboardMetrics.PublishedSucceededCount 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")) PublishedItems.Add(page => new MenuItem(Strings.SidebarMenu_Failed, page.Url.To("/published/failed"))
{ {
Active = page.RequestPath.StartsWith("/published/failed"), Active = page.RequestPath.StartsWith("/published/failed"),
...@@ -41,12 +37,6 @@ namespace DotNetCore.CAP.Dashboard ...@@ -41,12 +37,6 @@ namespace DotNetCore.CAP.Dashboard
Metric = DashboardMetrics.ReceivedSucceededCount 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")) ReceivedItems.Add(page => new MenuItem(Strings.SidebarMenu_Failed, page.Url.To("/received/failed"))
{ {
Active = page.RequestPath.StartsWith("/received/failed"), Active = page.RequestPath.StartsWith("/received/failed"),
......
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 public class Metric
{ {
......
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 namespace DotNetCore.CAP.Dashboard.Monitoring
{ {
......
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 namespace DotNetCore.CAP.Dashboard.Monitoring
{ {
......
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 public class SubscriberDto
{ {
......
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 public class StatisticsDto
{ {
...@@ -9,8 +12,5 @@ ...@@ -9,8 +12,5 @@
public int PublishedFailed { get; set; } public int PublishedFailed { get; set; }
public int ReceivedFailed { get; set; } public int ReceivedFailed { get; set; }
public int PublishedProcessing { get; set; }
public int ReceivedProcessing { get; set; }
} }
} }
\ No newline at end of file
// 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;
using System.Collections.Generic; using System.Collections.Generic;
using DotNetCore.CAP.Dashboard.Resources; using DotNetCore.CAP.Dashboard.Resources;
......
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 public class NonEscapedString
{ {
......
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.Collections.Generic;
namespace DotNetCore.CAP.Dashboard namespace DotNetCore.CAP.Dashboard
...@@ -35,14 +38,21 @@ namespace DotNetCore.CAP.Dashboard ...@@ -35,14 +38,21 @@ namespace DotNetCore.CAP.Dashboard
public string PageUrl(int page) 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; return BasePageUrl + "?from=" + (page - 1) * RecordsPerPage + "&count=" + RecordsPerPage;
} }
public string RecordsPerPageUrl(int perPage) public string RecordsPerPageUrl(int perPage)
{ {
if (perPage <= 0) return "#"; if (perPage <= 0)
{
return "#";
}
return BasePageUrl + "?from=0&count=" + perPage; return BasePageUrl + "?from=0&count=" + perPage;
} }
...@@ -51,23 +61,35 @@ namespace DotNetCore.CAP.Dashboard ...@@ -51,23 +61,35 @@ namespace DotNetCore.CAP.Dashboard
// start page index // start page index
_startPageIndex = CurrentPage - PageItemsCount / 2; _startPageIndex = CurrentPage - PageItemsCount / 2;
if (_startPageIndex + PageItemsCount > TotalPageCount) if (_startPageIndex + PageItemsCount > TotalPageCount)
{
_startPageIndex = TotalPageCount + 1 - PageItemsCount; _startPageIndex = TotalPageCount + 1 - PageItemsCount;
}
if (_startPageIndex < 1) if (_startPageIndex < 1)
{
_startPageIndex = 1; _startPageIndex = 1;
}
// end page index // end page index
_endPageIndex = _startPageIndex + PageItemsCount - 1; _endPageIndex = _startPageIndex + PageItemsCount - 1;
if (_endPageIndex > TotalPageCount) if (_endPageIndex > TotalPageCount)
{
_endPageIndex = TotalPageCount; _endPageIndex = TotalPageCount;
}
var pagerItems = new List<Item>(); var pagerItems = new List<Item>();
if (TotalPageCount == 0) return pagerItems; if (TotalPageCount == 0)
{
return pagerItems;
}
AddPrevious(pagerItems); AddPrevious(pagerItems);
// first page // first page
if (_startPageIndex > 1) if (_startPageIndex > 1)
{
pagerItems.Add(new Item(1, false, ItemType.Page)); pagerItems.Add(new Item(1, false, ItemType.Page));
}
// more page before numeric page buttons // more page before numeric page buttons
AddMoreBefore(pagerItems); AddMoreBefore(pagerItems);
...@@ -80,7 +102,9 @@ namespace DotNetCore.CAP.Dashboard ...@@ -80,7 +102,9 @@ namespace DotNetCore.CAP.Dashboard
// last page // last page
if (_endPageIndex < TotalPageCount) if (_endPageIndex < TotalPageCount)
{
pagerItems.Add(new Item(TotalPageCount, false, ItemType.Page)); pagerItems.Add(new Item(TotalPageCount, false, ItemType.Page));
}
// Next page // Next page
AddNext(pagerItems); AddNext(pagerItems);
...@@ -99,7 +123,11 @@ namespace DotNetCore.CAP.Dashboard ...@@ -99,7 +123,11 @@ namespace DotNetCore.CAP.Dashboard
if (_startPageIndex > 2) if (_startPageIndex > 2)
{ {
var index = _startPageIndex - 1; var index = _startPageIndex - 1;
if (index < 1) index = 1; if (index < 1)
{
index = 1;
}
var item = new Item(index, false, ItemType.MorePage); var item = new Item(index, false, ItemType.MorePage);
results.Add(item); results.Add(item);
} }
...@@ -110,7 +138,11 @@ namespace DotNetCore.CAP.Dashboard ...@@ -110,7 +138,11 @@ namespace DotNetCore.CAP.Dashboard
if (_endPageIndex < TotalPageCount - 1) if (_endPageIndex < TotalPageCount - 1)
{ {
var index = _startPageIndex + PageItemsCount; var index = _startPageIndex + PageItemsCount;
if (index > TotalPageCount) index = TotalPageCount; if (index > TotalPageCount)
{
index = TotalPageCount;
}
var item = new Item(index, false, ItemType.MorePage); var item = new Item(index, false, ItemType.MorePage);
results.Add(item); results.Add(item);
} }
......
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 internal partial class BlockMetric
{ {
......
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 namespace DotNetCore.CAP.Dashboard.Pages
{ {
......
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 namespace DotNetCore.CAP.Dashboard.Pages
{ {
......
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 internal partial class InlineMetric
{ {
......
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 partial class LayoutPage
{ {
......
...@@ -16,68 +16,68 @@ ...@@ -16,68 +16,68 @@
<link rel="stylesheet" href="@Url.To($"/css{version.Major}{version.Minor}{version.Build}")"> <link rel="stylesheet" href="@Url.To($"/css{version.Major}{version.Minor}{version.Build}")">
</head> </head>
<body> <body>
<!-- Wrap all page content here --> <!-- Wrap all page content here -->
<div id="wrap"> <div id="wrap">
<!-- Fixed navbar --> <!-- Fixed navbar -->
<div class="navbar navbar-default navbar-fixed-top"> <div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="@Url.Home()">CAP Dashboard</a>
</div>
<div class="collapse navbar-collapse">
@Html.RenderPartial(new Navigation())
@if (AppPath != null)
{
<ul class="nav navbar-nav navbar-right">
<li>
<a href="@AppPath">
<span class="glyphicon glyphicon-log-out"></span>
@Strings.LayoutPage_Back
</a>
</li>
</ul>
}
</div>
<!--/.nav-collapse -->
</div>
</div>
<!-- Begin page content -->
<div class="container" style="margin-bottom: 20px;">
@RenderBody()
</div>
</div>
<div id="footer">
<div class="container"> <div class="container">
<ul class="list-inline credit"> <div class="navbar-header">
<li> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<a href="https://github.com/dotnetcore/cap/" target="_blank"> <span class="icon-bar"></span>
CAP @($"{version.Major}.{version.Minor}.{version.Build}") <span class="icon-bar"></span>
</a> <span class="icon-bar"></span>
</li> </button>
<li>@Storage</li> <a class="navbar-brand" href="@Url.Home()">CAP Dashboard</a>
<li>@Strings.LayoutPage_Footer_Time @Html.LocalTime(DateTime.UtcNow)</li> </div>
<li>@string.Format(Strings.LayoutPage_Footer_Generatedms, GenerationTime.Elapsed.TotalMilliseconds.ToString("N"))</li> <div class="collapse navbar-collapse">
@if (NodeName != null) @Html.RenderPartial(new Navigation())
@if (AppPath != null)
{ {
<li>@string.Format(Strings.LayoutPage_Footer_NodeCurrent, NodeName)</li> <ul class="nav navbar-nav navbar-right">
<li>
<a href="@AppPath">
<span class="glyphicon glyphicon-log-out"></span>
@Strings.LayoutPage_Back
</a>
</li>
</ul>
} }
</ul> </div>
<!--/.nav-collapse -->
</div> </div>
</div> </div>
<div id="capConfig" <!-- Begin page content -->
data-pollinterval="@StatsPollingInterval" <div class="container" style="margin-bottom: 20px;">
data-pollurl="@(Url.To("/stats"))"> @RenderBody()
</div> </div>
</div>
<div id="footer">
<div class="container">
<ul class="list-inline credit">
<li>
<a href="https://github.com/dotnetcore/cap/" target="_blank">
CAP @($"{version.Major}.{version.Minor}.{version.Build}")
</a>
</li>
<li>@Storage</li>
<li>@Strings.LayoutPage_Footer_Time @Html.LocalTime(DateTime.UtcNow)</li>
<li>@string.Format(Strings.LayoutPage_Footer_Generatedms, GenerationTime.Elapsed.TotalMilliseconds.ToString("N"))</li>
@if (NodeName != null)
{
<li>@string.Format(Strings.LayoutPage_Footer_NodeCurrent, NodeName)</li>
}
</ul>
</div>
</div>
<div id="capConfig"
data-pollinterval="@StatsPollingInterval"
data-pollurl="@(Url.To("/stats"))">
</div>
<script src="@Url.To($"/js{version.Major}{version.Minor}{version.Build}")"></script> <script src="@Url.To($"/js{version.Major}{version.Minor}{version.Build}")"></script>
</body> </body>
</html> </html>
\ No newline at end of file
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 DotNetCore.CAP.NodeDiscovery;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
...@@ -31,8 +34,10 @@ namespace DotNetCore.CAP.Dashboard.Pages ...@@ -31,8 +34,10 @@ namespace DotNetCore.CAP.Dashboard.Pages
{ {
return new List<Node>(); return new List<Node>();
} }
_nodes = _discoveryProvider.GetNodes().GetAwaiter().GetResult(); _nodes = _discoveryProvider.GetNodes().GetAwaiter().GetResult();
} }
return _nodes; return _nodes;
} }
} }
......
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 namespace DotNetCore.CAP.Dashboard.Pages
{ {
...@@ -15,10 +18,10 @@ namespace DotNetCore.CAP.Dashboard.Pages ...@@ -15,10 +18,10 @@ namespace DotNetCore.CAP.Dashboard.Pages
{ {
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded,
StringComparison.CurrentCultureIgnoreCase)) StringComparison.CurrentCultureIgnoreCase))
{
return api.PublishedSucceededCount(); return api.PublishedSucceededCount();
if (string.Equals(StatusName, Infrastructure.StatusName.Processing, }
StringComparison.CurrentCultureIgnoreCase))
return api.PublishedProcessingCount();
return api.PublishedFailedCount(); return api.PublishedFailedCount();
} }
} }
......
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 namespace DotNetCore.CAP.Dashboard.Pages
{ {
...@@ -15,10 +18,10 @@ namespace DotNetCore.CAP.Dashboard.Pages ...@@ -15,10 +18,10 @@ namespace DotNetCore.CAP.Dashboard.Pages
{ {
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded,
StringComparison.CurrentCultureIgnoreCase)) StringComparison.CurrentCultureIgnoreCase))
{
return api.ReceivedSucceededCount(); return api.ReceivedSucceededCount();
if (string.Equals(StatusName, Infrastructure.StatusName.Processing, }
StringComparison.CurrentCultureIgnoreCase))
return api.ReceivedProcessingCount();
return api.ReceivedFailedCount(); return api.ReceivedFailedCount();
} }
} }
......
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.Collections.Generic;
namespace DotNetCore.CAP.Dashboard.Pages namespace DotNetCore.CAP.Dashboard.Pages
......
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 internal partial class Paginator
{ {
......
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@
@using DotNetCore.CAP.Dashboard @using DotNetCore.CAP.Dashboard
@using DotNetCore.CAP.Dashboard.Resources @using DotNetCore.CAP.Dashboard.Resources
@inherits DotNetCore.CAP.Dashboard.RazorPage @inherits DotNetCore.CAP.Dashboard.RazorPage
......
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 internal partial class PerPageSelector
{ {
......
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.Diagnostics;
using System.Net; using System.Net;
using System.Text; using System.Text;
...@@ -36,7 +39,11 @@ namespace DotNetCore.CAP.Dashboard ...@@ -36,7 +39,11 @@ namespace DotNetCore.CAP.Dashboard
{ {
get get
{ {
if (_statisticsLazy == null) throw new InvalidOperationException("Page is not initialized."); if (_statisticsLazy == null)
{
throw new InvalidOperationException("Page is not initialized.");
}
return _statisticsLazy.Value; return _statisticsLazy.Value;
} }
} }
...@@ -104,6 +111,7 @@ namespace DotNetCore.CAP.Dashboard ...@@ -104,6 +111,7 @@ namespace DotNetCore.CAP.Dashboard
{ {
return $"{discoveryOptions.NodeName}({discoveryOptions.NodeId})"; return $"{discoveryOptions.NodeName}({discoveryOptions.NodeId})";
} }
return null; return null;
} }
...@@ -111,7 +119,7 @@ namespace DotNetCore.CAP.Dashboard ...@@ -111,7 +119,7 @@ namespace DotNetCore.CAP.Dashboard
{ {
if (CapCache.Global.TryGet("cap.nodes.count", out var count)) if (CapCache.Global.TryGet("cap.nodes.count", out var count))
{ {
dto.Servers = (int)count; dto.Servers = (int) count;
} }
else else
{ {
...@@ -128,7 +136,10 @@ namespace DotNetCore.CAP.Dashboard ...@@ -128,7 +136,10 @@ namespace DotNetCore.CAP.Dashboard
protected void WriteLiteral(string textToAppend) protected void WriteLiteral(string textToAppend)
{ {
if (string.IsNullOrEmpty(textToAppend)) if (string.IsNullOrEmpty(textToAppend))
{
return; return;
}
_content.Append(textToAppend); _content.Append(textToAppend);
} }
...@@ -136,7 +147,10 @@ namespace DotNetCore.CAP.Dashboard ...@@ -136,7 +147,10 @@ namespace DotNetCore.CAP.Dashboard
protected virtual void Write(object value) protected virtual void Write(object value)
{ {
if (value == null) if (value == null)
{
return; return;
}
var html = value as NonEscapedString; var html = value as NonEscapedString;
WriteLiteral(html?.ToString() ?? Encode(value.ToString())); WriteLiteral(html?.ToString() ?? Encode(value.ToString()));
} }
......
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.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
......
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.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
...@@ -11,24 +14,39 @@ namespace DotNetCore.CAP.Dashboard ...@@ -11,24 +14,39 @@ namespace DotNetCore.CAP.Dashboard
public void Add(string pathTemplate, IDashboardDispatcher dispatcher) public void Add(string pathTemplate, IDashboardDispatcher dispatcher)
{ {
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); if (pathTemplate == null)
if (dispatcher == null) throw new ArgumentNullException(nameof(dispatcher)); {
throw new ArgumentNullException(nameof(pathTemplate));
}
if (dispatcher == null)
{
throw new ArgumentNullException(nameof(dispatcher));
}
_dispatchers.Add(new Tuple<string, IDashboardDispatcher>(pathTemplate, dispatcher)); _dispatchers.Add(new Tuple<string, IDashboardDispatcher>(pathTemplate, dispatcher));
} }
public Tuple<IDashboardDispatcher, Match> FindDispatcher(string path) public Tuple<IDashboardDispatcher, Match> FindDispatcher(string path)
{ {
if (path.Length == 0) path = "/"; if (path.Length == 0)
{
path = "/";
}
foreach (var dispatcher in _dispatchers) foreach (var dispatcher in _dispatchers)
{ {
var pattern = dispatcher.Item1; var pattern = dispatcher.Item1;
if (!pattern.StartsWith("^", StringComparison.OrdinalIgnoreCase)) if (!pattern.StartsWith("^", StringComparison.OrdinalIgnoreCase))
{
pattern = "^" + pattern; pattern = "^" + pattern;
}
if (!pattern.EndsWith("$", StringComparison.OrdinalIgnoreCase)) if (!pattern.EndsWith("$", StringComparison.OrdinalIgnoreCase))
{
pattern += "$"; pattern += "$";
}
var match = Regex.Match( var match = Regex.Match(
path, path,
...@@ -36,7 +54,9 @@ namespace DotNetCore.CAP.Dashboard ...@@ -36,7 +54,9 @@ namespace DotNetCore.CAP.Dashboard
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline); RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline);
if (match.Success) if (match.Success)
{
return new Tuple<IDashboardDispatcher, Match>(dispatcher.Item2, match); return new Tuple<IDashboardDispatcher, Match>(dispatcher.Item2, match);
}
} }
return null; return null;
......
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.Text.RegularExpressions;
namespace DotNetCore.CAP.Dashboard namespace DotNetCore.CAP.Dashboard
...@@ -10,9 +13,20 @@ namespace DotNetCore.CAP.Dashboard ...@@ -10,9 +13,20 @@ namespace DotNetCore.CAP.Dashboard
string pathTemplate, string pathTemplate,
Func<Match, RazorPage> pageFunc) Func<Match, RazorPage> pageFunc)
{ {
if (routes == null) throw new ArgumentNullException(nameof(routes)); if (routes == null)
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); {
if (pageFunc == null) throw new ArgumentNullException(nameof(pageFunc)); 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)); routes.Add(pathTemplate, new RazorPageDispatcher(pageFunc));
} }
...@@ -22,9 +36,20 @@ namespace DotNetCore.CAP.Dashboard ...@@ -22,9 +36,20 @@ namespace DotNetCore.CAP.Dashboard
string pathTemplate, string pathTemplate,
Func<DashboardContext, bool> command) Func<DashboardContext, bool> command)
{ {
if (routes == null) throw new ArgumentNullException(nameof(routes)); if (routes == null)
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); {
if (command == null) throw new ArgumentNullException(nameof(command)); 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)); routes.Add(pathTemplate, new CommandDispatcher(command));
} }
...@@ -34,9 +59,20 @@ namespace DotNetCore.CAP.Dashboard ...@@ -34,9 +59,20 @@ namespace DotNetCore.CAP.Dashboard
string pathTemplate, string pathTemplate,
Func<DashboardContext, object> func) Func<DashboardContext, object> func)
{ {
if (routes == null) throw new ArgumentNullException(nameof(routes)); if (routes == null)
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); {
if (func == null) throw new ArgumentNullException(nameof(func)); 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)); routes.Add(pathTemplate, new JsonDispatcher(func));
} }
...@@ -46,9 +82,20 @@ namespace DotNetCore.CAP.Dashboard ...@@ -46,9 +82,20 @@ namespace DotNetCore.CAP.Dashboard
string pathTemplate, string pathTemplate,
Func<DashboardContext, string> jsonfunc) Func<DashboardContext, string> jsonfunc)
{ {
if (routes == null) throw new ArgumentNullException(nameof(routes)); if (routes == null)
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); {
if (jsonfunc == null) throw new ArgumentNullException(nameof(jsonfunc)); 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)); routes.Add(pathTemplate, new JsonDispatcher(jsonfunc));
} }
...@@ -58,9 +105,20 @@ namespace DotNetCore.CAP.Dashboard ...@@ -58,9 +105,20 @@ namespace DotNetCore.CAP.Dashboard
string pathTemplate, string pathTemplate,
Action<DashboardContext, int> command) Action<DashboardContext, int> command)
{ {
if (routes == null) throw new ArgumentNullException(nameof(routes)); if (routes == null)
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); {
if (command == null) throw new ArgumentNullException(nameof(command)); 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)); routes.Add(pathTemplate, new BatchCommandDispatcher(command));
} }
......
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.Net;
namespace DotNetCore.CAP.Dashboard 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.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Diagnostics
{
/// <summary>
/// Extension methods on the DiagnosticListener class to log CAP data
/// </summary>
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))
{
@this.Write(CapBeforePublish, eventData);
}
}
public static void WritePublishAfter(this DiagnosticListener @this, BrokerPublishEndEventData eventData)
{
if (@this.IsEnabled(CapAfterPublish))
{
@this.Write(CapAfterPublish, eventData);
}
}
public static void WritePublishError(this DiagnosticListener @this, BrokerPublishErrorEventData eventData)
{
if (@this.IsEnabled(CapErrorPublish))
{
@this.Write(CapErrorPublish, eventData);
}
}
//============================================================================
//==================== Consume ====================
//============================================================================
public static Guid WriteConsumeBefore(this DiagnosticListener @this, BrokerConsumeEventData eventData)
{
if (@this.IsEnabled(CapBeforeConsume))
{
@this.Write(CapBeforeConsume, eventData);
}
return Guid.Empty;
}
public static void WriteConsumeAfter(this DiagnosticListener @this, BrokerConsumeEndEventData eventData)
{
if (@this.IsEnabled(CapAfterConsume))
{
@this.Write(CapAfterConsume, eventData);
}
}
public static void WriteConsumeError(this DiagnosticListener @this, BrokerConsumeErrorEventData eventData)
{
if (@this.IsEnabled(CapErrorConsume))
{
@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
// 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
// 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
// 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
// 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
// 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
// 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
// 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)
{
Headers = new TracingHeaders();
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
// 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
// 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
// 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
// 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
// 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
// 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<KeyValuePair<string, string>>
{
private List<KeyValuePair<string, string>> _dataStore;
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return _dataStore.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(string name, string value)
{
if (_dataStore == null)
{
_dataStore = new List<KeyValuePair<string, string>>();
}
_dataStore.Add(new KeyValuePair<string, string>(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
...@@ -49,12 +49,13 @@ ...@@ -49,12 +49,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Consul" Version="0.7.2.4" /> <PackageReference Include="Consul" Version="0.7.2.4" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Options" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.Data.Common" Version="4.3.0" /> <PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.4.1" />
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" /> <PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
...@@ -142,4 +143,9 @@ ...@@ -142,4 +143,9 @@
<LastGenOutput>Strings.Designer.cs</LastGenOutput> <LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="Dashboard\Pages\PublishedPage.cshtml">
<Generator>RazorGenerator</Generator>
</None>
</ItemGroup>
</Project> </Project>
\ No newline at end of file
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.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -19,10 +22,6 @@ namespace DotNetCore.CAP ...@@ -19,10 +22,6 @@ namespace DotNetCore.CAP
private readonly ILogger<DefaultBootstrapper> _logger; private readonly ILogger<DefaultBootstrapper> _logger;
private Task _bootstrappingTask; private Task _bootstrappingTask;
private IStorage Storage { get; }
private IEnumerable<IProcessingServer> Processors { get; }
public DefaultBootstrapper( public DefaultBootstrapper(
ILogger<DefaultBootstrapper> logger, ILogger<DefaultBootstrapper> logger,
IStorage storage, IStorage storage,
...@@ -49,6 +48,10 @@ namespace DotNetCore.CAP ...@@ -49,6 +48,10 @@ namespace DotNetCore.CAP
}); });
} }
private IStorage Storage { get; }
private IEnumerable<IProcessingServer> Processors { get; }
public Task BootstrapAsync() public Task BootstrapAsync()
{ {
return _bootstrappingTask = BootstrapTaskAsync(); return _bootstrappingTask = BootstrapTaskAsync();
...@@ -58,15 +61,23 @@ namespace DotNetCore.CAP ...@@ -58,15 +61,23 @@ namespace DotNetCore.CAP
{ {
await Storage.InitializeAsync(_cts.Token); await Storage.InitializeAsync(_cts.Token);
if (_cts.IsCancellationRequested) return; if (_cts.IsCancellationRequested)
{
return;
}
_appLifetime.ApplicationStopping.Register(() => _appLifetime.ApplicationStopping.Register(() =>
{ {
foreach (var item in Processors) foreach (var item in Processors)
{
item.Dispose(); item.Dispose();
}
}); });
if (_cts.IsCancellationRequested) return; if (_cts.IsCancellationRequested)
{
return;
}
await BootstrapCoreAsync(); await BootstrapCoreAsync();
...@@ -77,6 +88,7 @@ namespace DotNetCore.CAP ...@@ -77,6 +88,7 @@ namespace DotNetCore.CAP
protected virtual Task BootstrapCoreAsync() protected virtual Task BootstrapCoreAsync()
{ {
foreach (var item in Processors) foreach (var item in Processors)
{
try try
{ {
item.Start(); item.Start();
...@@ -85,6 +97,8 @@ namespace DotNetCore.CAP ...@@ -85,6 +97,8 @@ namespace DotNetCore.CAP
{ {
_logger.ProcessorsStartedError(ex); _logger.ProcessorsStartedError(ex);
} }
}
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
......
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 namespace DotNetCore.CAP
{ {
......
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; using DotNetCore.CAP.Models;
namespace DotNetCore.CAP namespace DotNetCore.CAP
...@@ -11,6 +14,6 @@ namespace DotNetCore.CAP ...@@ -11,6 +14,6 @@ namespace DotNetCore.CAP
/// <summary> /// <summary>
/// Publish a callback message /// Publish a callback message
/// </summary> /// </summary>
Task PublishAsync(CapPublishedMessage obj); Task PublishCallbackAsync(CapPublishedMessage obj);
} }
} }
\ No newline at end of file
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 namespace DotNetCore.CAP
{ {
...@@ -10,7 +13,7 @@ namespace DotNetCore.CAP ...@@ -10,7 +13,7 @@ namespace DotNetCore.CAP
/// <summary> /// <summary>
/// Registered child service. /// Registered child service.
/// </summary> /// </summary>
/// <param name="services">add service to the <see cref="IServiceCollection"/></param> /// <param name="services">add service to the <see cref="IServiceCollection" /></param>
void AddServices(IServiceCollection services); void AddServices(IServiceCollection services);
} }
} }
\ No newline at end of file
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; using System.Threading.Tasks;
namespace DotNetCore.CAP namespace DotNetCore.CAP
......
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
{ {
/// <summary> /// <summary>
/// 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 methods.
......
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.Collections.Generic;
using System.Threading; using System.Threading;
...@@ -10,6 +13,8 @@ namespace DotNetCore.CAP ...@@ -10,6 +13,8 @@ namespace DotNetCore.CAP
/// </summary> /// </summary>
public interface IConsumerClient : IDisposable public interface IConsumerClient : IDisposable
{ {
string ServersAddress { get; }
/// <summary> /// <summary>
/// Subscribe to a set of topics to the message queue /// Subscribe to a set of topics to the message queue
/// </summary> /// </summary>
......
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
{ {
/// <summary> /// <summary>
/// Consumer client factory to create consumer client instance. /// Consumer client factory to create consumer client instance.
......
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.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Diagnostics;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Internal; using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Models; using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
internal class ConsumerHandler : IConsumerHandler internal class ConsumerHandler : IConsumerHandler
{ {
private readonly IStorageConnection _connection;
private readonly IConsumerClientFactory _consumerClientFactory; private readonly IConsumerClientFactory _consumerClientFactory;
private readonly CancellationTokenSource _cts; private readonly CancellationTokenSource _cts;
private readonly IDispatcher _dispatcher;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly TimeSpan _pollingDelay = TimeSpan.FromSeconds(1); private readonly TimeSpan _pollingDelay = TimeSpan.FromSeconds(1);
private readonly MethodMatcherCache _selector; private readonly MethodMatcherCache _selector;
private readonly IServiceProvider _serviceProvider;
private string _serverAddress;
private Task _compositeTask; private Task _compositeTask;
private bool _disposed; private bool _disposed;
public ConsumerHandler( // diagnostics listener
IServiceProvider serviceProvider, // ReSharper disable once InconsistentNaming
IConsumerClientFactory consumerClientFactory, private static readonly DiagnosticListener s_diagnosticListener =
new DiagnosticListener(CapDiagnosticListenerExtensions.DiagnosticListenerName);
public ConsumerHandler(IConsumerClientFactory consumerClientFactory,
IDispatcher dispatcher,
IStorageConnection connection,
ILogger<ConsumerHandler> logger, ILogger<ConsumerHandler> logger,
MethodMatcherCache selector) MethodMatcherCache selector)
{ {
_selector = selector; _selector = selector;
_logger = logger; _logger = logger;
_serviceProvider = serviceProvider;
_consumerClientFactory = consumerClientFactory; _consumerClientFactory = consumerClientFactory;
_dispatcher = dispatcher;
_connection = connection;
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
} }
...@@ -44,25 +52,32 @@ namespace DotNetCore.CAP ...@@ -44,25 +52,32 @@ namespace DotNetCore.CAP
var groupingMatches = _selector.GetCandidatesMethodsOfGroupNameGrouped(); var groupingMatches = _selector.GetCandidatesMethodsOfGroupNameGrouped();
foreach (var matchGroup in groupingMatches) foreach (var matchGroup in groupingMatches)
{
Task.Factory.StartNew(() => Task.Factory.StartNew(() =>
{ {
using (var client = _consumerClientFactory.Create(matchGroup.Key)) using (var client = _consumerClientFactory.Create(matchGroup.Key))
{ {
RegisterMessageProcessor(client); _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); client.Listening(_pollingDelay, _cts.Token);
} }
}, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
_compositeTask = Task.CompletedTask; _compositeTask = Task.CompletedTask;
} }
public void Dispose() public void Dispose()
{ {
if (_disposed) if (_disposed)
{
return; return;
}
_disposed = true; _disposed = true;
_cts.Cancel(); _cts.Cancel();
try try
...@@ -73,35 +88,54 @@ namespace DotNetCore.CAP ...@@ -73,35 +88,54 @@ namespace DotNetCore.CAP
{ {
var innerEx = ex.InnerExceptions[0]; var innerEx = ex.InnerExceptions[0];
if (!(innerEx is OperationCanceledException)) if (!(innerEx is OperationCanceledException))
{
_logger.ExpectedOperationCanceledException(innerEx); _logger.ExpectedOperationCanceledException(innerEx);
}
} }
} }
public void Pulse() public void Pulse()
{ {
SubscribeQueuer.PulseEvent.Set(); //ignore
} }
private void RegisterMessageProcessor(IConsumerClient client) 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 StatusName = StatusName.Scheduled,
{ Content = messageBody
StoreMessage(scope, message); };
client.Commit();
} try
catch (Exception e) {
{ StoreMessage(receivedMessage);
_logger.LogError(e, "An exception occurred when storage received message. Message:'{0}'.", message);
client.Reject(); 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; client.OnLog += WriteLog;
...@@ -134,15 +168,57 @@ namespace DotNetCore.CAP ...@@ -134,15 +168,57 @@ namespace DotNetCore.CAP
} }
} }
private static void StoreMessage(IServiceScope serviceScope, MessageContext messageContext) private void StoreMessage(CapReceivedMessage receivedMessage)
{ {
var provider = serviceScope.ServiceProvider; var id = _connection.StoreReceivedMessageAsync(receivedMessage)
var messageStore = provider.GetRequiredService<IStorageConnection>(); .GetAwaiter().GetResult();
var receivedMessage = new CapReceivedMessage(messageContext)
{ receivedMessage.Id = id;
StatusName = StatusName.Scheduled }
};
messageStore.StoreReceivedMessageAsync(receivedMessage).GetAwaiter().GetResult(); private (Guid, string) TracingBefore(string topic, string values)
{
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
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
{ {
/// <summary> /// <summary>
/// Handler received message of subscribed. /// Handler received message of subscribed.
......
// 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
// 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;
using DotNetCore.CAP.Models; using DotNetCore.CAP.Models;
......
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 namespace DotNetCore.CAP
{ {
......
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 namespace DotNetCore.CAP
{ {
...@@ -15,4 +18,4 @@ namespace DotNetCore.CAP ...@@ -15,4 +18,4 @@ namespace DotNetCore.CAP
/// <returns></returns> /// <returns></returns>
Task<OperateResult> PublishAsync(string keyName, string content); Task<OperateResult> PublishAsync(string keyName, string content);
} }
} }
\ No newline at end of file
// 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<OperateResult> PublishAsync(string keyName, string content);
public async Task<OperateResult> 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.Name, sendValues, result.Exception, startTime, stopwatch.Elapsed);
_logger.MessagePublishException(message.Id, result.Exception);
await SetFailedState(message, result.Exception, out bool stillRetry);
if (stillRetry)
{
_logger.SenderRetrying(3);
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();
if (ex is PublisherSentFailedException)
{
stillRetry = false;
message.Retries = _options.FailedRetryCount; // not retry if PublisherSentFailedException
}
else
{
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);
}
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, string topic, string values, Exception ex, DateTimeOffset startTime, TimeSpan du)
{
var eventData = new BrokerPublishErrorEventData(
operationId,
"",
ServersAddress,
topic,
values,
ex,
startTime,
du);
s_diagnosticListener.WritePublishError(eventData);
}
}
}
\ No newline at end of file
// 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<OperateResult> SendAsync(CapPublishedMessage message);
}
}
\ No newline at end of file
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<BasePublishQueueExecutor> logger)
{
_options = options;
_stateChanger = stateChanger;
_logger = logger;
}
public async Task<OperateResult> 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<OperateResult> 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
using System.Threading.Tasks;
namespace DotNetCore.CAP
{
public interface IQueueExecutor
{
Task<OperateResult> ExecuteAsync(IStorageConnection connection, IFetchedMessage message);
}
}
\ No newline at end of file
using DotNetCore.CAP.Models;
namespace DotNetCore.CAP
{
public interface IQueueExecutorFactory
{
IQueueExecutor GetInstance(MessageType messageType);
}
}
\ No newline at end of file
// 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;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Dashboard; using 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.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -18,20 +21,10 @@ namespace DotNetCore.CAP ...@@ -18,20 +21,10 @@ namespace DotNetCore.CAP
/// <param name="id">The message's id.</param> /// <param name="id">The message's id.</param>
Task<CapPublishedMessage> GetPublishedMessageAsync(int id); Task<CapPublishedMessage> GetPublishedMessageAsync(int id);
/// <summary>
/// Fetches the next message to be executed.
/// </summary>
Task<IFetchedMessage> FetchNextMessageAsync();
/// <summary>
/// Returns the next message to be enqueued.
/// </summary>
Task<CapPublishedMessage> GetNextPublishedMessageToBeEnqueuedAsync();
/// <summary> /// <summary>
/// Returns executed failed messages. /// Returns executed failed messages.
/// </summary> /// </summary>
Task<IEnumerable<CapPublishedMessage>> GetFailedPublishedMessages(); Task<IEnumerable<CapPublishedMessage>> GetPublishedMessagesOfNeedRetry();
// Received messages // Received messages
...@@ -39,7 +32,7 @@ namespace DotNetCore.CAP ...@@ -39,7 +32,7 @@ namespace DotNetCore.CAP
/// Stores the message. /// Stores the message.
/// </summary> /// </summary>
/// <param name="message">The message to store.</param> /// <param name="message">The message to store.</param>
Task StoreReceivedMessageAsync(CapReceivedMessage message); Task<int> StoreReceivedMessageAsync(CapReceivedMessage message);
/// <summary> /// <summary>
/// Returns the message with the given id. /// Returns the message with the given id.
...@@ -47,15 +40,10 @@ namespace DotNetCore.CAP ...@@ -47,15 +40,10 @@ namespace DotNetCore.CAP
/// <param name="id">The message's id.</param> /// <param name="id">The message's id.</param>
Task<CapReceivedMessage> GetReceivedMessageAsync(int id); Task<CapReceivedMessage> GetReceivedMessageAsync(int id);
/// <summary>
/// Returns the next message to be enqueued.
/// </summary>
Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync();
/// <summary> /// <summary>
/// Returns executed failed message. /// Returns executed failed message.
/// </summary> /// </summary>
Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages(); Task<IEnumerable<CapReceivedMessage>> GetReceivedMessagesOfNeedRetry();
/// <summary> /// <summary>
/// Creates and returns an <see cref="IStorageTransaction" />. /// Creates and returns an <see cref="IStorageTransaction" />.
......
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 System.Threading.Tasks;
using DotNetCore.CAP.Models; using DotNetCore.CAP.Models;
...@@ -14,10 +17,6 @@ namespace DotNetCore.CAP ...@@ -14,10 +17,6 @@ namespace DotNetCore.CAP
void UpdateMessage(CapReceivedMessage message); void UpdateMessage(CapReceivedMessage message);
void EnqueueMessage(CapPublishedMessage message);
void EnqueueMessage(CapReceivedMessage message);
Task CommitAsync(); Task CommitAsync();
} }
} }
\ No newline at end of file
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.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Diagnostics;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Internal; using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Models; using DotNetCore.CAP.Models;
...@@ -11,104 +14,105 @@ using Microsoft.Extensions.Logging; ...@@ -11,104 +14,105 @@ using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
public class SubscribeQueueExecutor : IQueueExecutor internal class DefaultSubscriberExecutor : ISubscriberExecutor
{ {
private readonly ICallbackMessageSender _callbackMessageSender;
private readonly IStorageConnection _connection;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly CapOptions _options;
private readonly IStateChanger _stateChanger; private readonly IStateChanger _stateChanger;
private readonly ISubscriberExecutor _subscriberExecutor; 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 SubscribeQueueExecutor( public DefaultSubscriberExecutor(
ILogger<DefaultSubscriberExecutor> logger,
CapOptions options, CapOptions options,
IConsumerInvokerFactory consumerInvokerFactory,
ICallbackMessageSender callbackMessageSender,
IStateChanger stateChanger, IStateChanger stateChanger,
ISubscriberExecutor subscriberExecutor, IStorageConnection connection,
ILogger<SubscribeQueueExecutor> logger) MethodMatcherCache selector)
{ {
_selector = selector;
_callbackMessageSender = callbackMessageSender;
_options = options; _options = options;
_subscriberExecutor = subscriberExecutor;
_stateChanger = stateChanger; _stateChanger = stateChanger;
_connection = connection;
_logger = logger; _logger = logger;
Invoker = consumerInvokerFactory.CreateInvoker();
} }
public async Task<OperateResult> ExecuteAsync(IStorageConnection connection, IFetchedMessage fetched) private IConsumerInvoker Invoker { get; }
{
var message = await connection.GetReceivedMessageAsync(fetched.MessageId);
public async Task<OperateResult> ExecuteAsync(CapReceivedMessage message)
{
if (message == null) if (message == null)
{ {
_logger.LogError($"Can not found the `message` at cap received message table, message id:{fetched.MessageId} !!!"); throw new ArgumentNullException(nameof(message));
return OperateResult.Failed();
} }
try try
{ {
var sp = Stopwatch.StartNew(); var sp = Stopwatch.StartNew();
await _stateChanger.ChangeStateAsync(message, new ProcessingState(), connection);
if (message.Retries > 0) await InvokeConsumerMethodAsync(message);
_logger.JobRetrying(message.Retries);
var result = await _subscriberExecutor.ExecuteAsync(message);
sp.Stop(); sp.Stop();
var state = GetNewState(result, message); await SetSuccessfulState(message);
await _stateChanger.ChangeStateAsync(message, state, connection); _logger.ConsumerExecuted(sp.Elapsed.TotalSeconds);
fetched.RemoveFromQueue();
if (result.Succeeded)
_logger.JobExecuted(sp.Elapsed.TotalSeconds);
return OperateResult.Success; return OperateResult.Success;
} }
catch (SubscriberNotFoundException ex)
{
_logger.LogError(ex.Message);
AddErrorReasonToContent(message, ex);
++message.Retries; //issue: https://github.com/dotnetcore/CAP/issues/90
await _stateChanger.ChangeStateAsync(message, new FailedState(), connection);
fetched.RemoveFromQueue();
return OperateResult.Failed(ex);
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.ExceptionOccuredWhileExecuting(message.Name, ex); _logger.ExceptionOccuredWhileExecuting(message.Name, ex);
fetched.Requeue(); await SetFailedState(message, ex, out bool stillRetry);
if (stillRetry)
{
await ExecuteAsync(message);
}
return OperateResult.Failed(ex); return OperateResult.Failed(ex);
} }
} }
private IState GetNewState(OperateResult result, CapReceivedMessage message) 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; IState newState = new FailedState();
if (!result.Succeeded)
if (ex is SubscriberNotFoundException)
{ {
var shouldRetry = UpdateMessageForRetry(message); stillRetry = false;
if (shouldRetry) message.Retries = _options.FailedRetryCount; // not retry if SubscriberNotFoundException
{
newState = new ScheduledState();
_logger.JobFailedWillRetry(result.Exception);
}
else
{
newState = new FailedState();
_logger.JobFailed(result.Exception);
}
AddErrorReasonToContent(message, result.Exception);
} }
else else
{ {
newState = new SucceededState(_options.SucceedMessageExpiredAfter); stillRetry = UpdateMessageForRetry(message);
if (stillRetry)
{
_logger.ConsumerExecutionFailedWillRetry(ex);
return Task.CompletedTask;
}
} }
return newState;
AddErrorReasonToContent(message, ex);
return _stateChanger.ChangeStateAsync(message, newState, _connection);
} }
private static bool UpdateMessageForRetry(CapReceivedMessage message) private static bool UpdateMessageForRetry(CapReceivedMessage message)
...@@ -117,7 +121,9 @@ namespace DotNetCore.CAP ...@@ -117,7 +121,9 @@ namespace DotNetCore.CAP
var retries = ++message.Retries; var retries = ++message.Retries;
if (retries >= retryBehavior.RetryCount) if (retries >= retryBehavior.RetryCount)
{
return false; return false;
}
var due = message.Added.AddSeconds(retryBehavior.RetryIn(retries)); var due = message.Added.AddSeconds(retryBehavior.RetryIn(retries));
message.ExpiresAt = due; message.ExpiresAt = due;
...@@ -126,8 +132,44 @@ namespace DotNetCore.CAP ...@@ -126,8 +132,44 @@ namespace DotNetCore.CAP
} }
private static void AddErrorReasonToContent(CapReceivedMessage message, Exception exception) private static void AddErrorReasonToContent(CapReceivedMessage message, Exception exception)
{ {
message.Content = Helper.AddExceptionProperty(message.Content, 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
// 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<OperateResult> ExecuteAsync(CapReceivedMessage message);
}
}
\ No newline at end of file
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.ComponentModel;
using System.Linq;
using System.Reflection; using System.Reflection;
using DotNetCore.CAP.Diagnostics;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
...@@ -32,7 +38,10 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -32,7 +38,10 @@ namespace DotNetCore.CAP.Infrastructure
public static object FromJson(string value, Type type) 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 return value != null
? JsonConvert.DeserializeObject(value, type, _serializerSettings) ? JsonConvert.DeserializeObject(value, type, _serializerSettings)
...@@ -42,7 +51,7 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -42,7 +51,7 @@ namespace DotNetCore.CAP.Infrastructure
public static long ToTimestamp(DateTime value) public static long ToTimestamp(DateTime value)
{ {
var elapsedTime = value - Epoch; var elapsedTime = value - Epoch;
return (long) elapsedTime.TotalSeconds; return (long)elapsedTime.TotalSeconds;
} }
...@@ -54,13 +63,19 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -54,13 +63,19 @@ namespace DotNetCore.CAP.Infrastructure
public static bool IsController(TypeInfo typeInfo) public static bool IsController(TypeInfo typeInfo)
{ {
if (!typeInfo.IsClass) if (!typeInfo.IsClass)
{
return false; return false;
}
if (typeInfo.IsAbstract) if (typeInfo.IsAbstract)
{
return false; return false;
}
if (!typeInfo.IsPublic) if (!typeInfo.IsPublic)
{
return false; return false;
}
return !typeInfo.ContainsGenericParameters return !typeInfo.ContainsGenericParameters
&& typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase); && typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase);
...@@ -78,6 +93,34 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -78,6 +93,34 @@ namespace DotNetCore.CAP.Infrastructure
return AddJsonProperty(json, "ExceptionMessage", jObject); 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<Dictionary<string,string>>())
{
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) public static bool IsInnerIP(string ipAddress)
{ {
bool isInnerIp; bool isInnerIp;
...@@ -144,14 +187,28 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -144,14 +187,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) private static string AddJsonProperty(string json, string propertyName, JObject propertyValue)
{ {
var jObj = JObject.Parse(json); var jObj = JObject.Parse(json);
if (jObj.TryGetValue(propertyName, out var _)) if (jObj.TryGetValue(propertyName, out var _))
{
jObj[propertyName] = propertyValue; jObj[propertyName] = propertyValue;
}
else else
{
jObj.Add(new JProperty(propertyName, propertyValue)); jObj.Add(new JProperty(propertyName, propertyValue));
}
return jObj.ToString(Formatting.None); return jObj.ToString(Formatting.None);
} }
......
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.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
...@@ -57,7 +60,10 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -57,7 +60,10 @@ namespace DotNetCore.CAP.Infrastructure
public ObjectId(byte[] bytes) public ObjectId(byte[] bytes)
{ {
if (bytes == null) if (bytes == null)
{
throw new ArgumentNullException("bytes"); throw new ArgumentNullException("bytes");
}
Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment); Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment);
} }
...@@ -83,11 +89,16 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -83,11 +89,16 @@ namespace DotNetCore.CAP.Infrastructure
public ObjectId(int timestamp, int machine, short pid, int increment) public ObjectId(int timestamp, int machine, short pid, int increment)
{ {
if ((machine & 0xff000000) != 0) if ((machine & 0xff000000) != 0)
{
throw new ArgumentOutOfRangeException("machine", throw new ArgumentOutOfRangeException("machine",
"The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
}
if ((increment & 0xff000000) != 0) if ((increment & 0xff000000) != 0)
{
throw new ArgumentOutOfRangeException("increment", throw new ArgumentOutOfRangeException("increment",
"The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
}
_timestamp = timestamp; _timestamp = timestamp;
_machine = machine; _machine = machine;
...@@ -102,7 +113,10 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -102,7 +113,10 @@ namespace DotNetCore.CAP.Infrastructure
public ObjectId(string value) public ObjectId(string value)
{ {
if (value == null) if (value == null)
{
throw new ArgumentNullException("value"); throw new ArgumentNullException("value");
}
Unpack(ParseHexString(value), out _timestamp, out _machine, out _pid, out _increment); Unpack(ParseHexString(value), out _timestamp, out _machine, out _pid, out _increment);
} }
...@@ -256,11 +270,16 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -256,11 +270,16 @@ namespace DotNetCore.CAP.Infrastructure
public static byte[] Pack(int timestamp, int machine, short pid, int increment) public static byte[] Pack(int timestamp, int machine, short pid, int increment)
{ {
if ((machine & 0xff000000) != 0) if ((machine & 0xff000000) != 0)
{
throw new ArgumentOutOfRangeException("machine", throw new ArgumentOutOfRangeException("machine",
"The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
}
if ((increment & 0xff000000) != 0) if ((increment & 0xff000000) != 0)
{
throw new ArgumentOutOfRangeException("increment", throw new ArgumentOutOfRangeException("increment",
"The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
}
var bytes = new byte[12]; var bytes = new byte[12];
bytes[0] = (byte) (timestamp >> 24); bytes[0] = (byte) (timestamp >> 24);
...@@ -286,9 +305,15 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -286,9 +305,15 @@ namespace DotNetCore.CAP.Infrastructure
public static ObjectId Parse(string s) public static ObjectId Parse(string s)
{ {
if (s == null) if (s == null)
{
throw new ArgumentNullException("s"); throw new ArgumentNullException("s");
}
if (s.Length != 24) if (s.Length != 24)
{
throw new ArgumentOutOfRangeException("s", "ObjectId string value must be 24 characters."); throw new ArgumentOutOfRangeException("s", "ObjectId string value must be 24 characters.");
}
return new ObjectId(ParseHexString(s)); return new ObjectId(ParseHexString(s));
} }
...@@ -303,9 +328,15 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -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) public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment)
{ {
if (bytes == null) if (bytes == null)
{
throw new ArgumentNullException("bytes"); throw new ArgumentNullException("bytes");
}
if (bytes.Length != 12) if (bytes.Length != 12)
{
throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long."); throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long.");
}
timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3]; timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6]; machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6];
pid = (short) ((bytes[7] << 8) + bytes[8]); pid = (short) ((bytes[7] << 8) + bytes[8]);
...@@ -349,11 +380,23 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -349,11 +380,23 @@ namespace DotNetCore.CAP.Infrastructure
public int CompareTo(ObjectId other) public int CompareTo(ObjectId other)
{ {
var r = _timestamp.CompareTo(other._timestamp); var r = _timestamp.CompareTo(other._timestamp);
if (r != 0) return r; if (r != 0)
{
return r;
}
r = _machine.CompareTo(other._machine); r = _machine.CompareTo(other._machine);
if (r != 0) return r; if (r != 0)
{
return r;
}
r = _pid.CompareTo(other._pid); r = _pid.CompareTo(other._pid);
if (r != 0) return r; if (r != 0)
{
return r;
}
return _increment.CompareTo(other._increment); return _increment.CompareTo(other._increment);
} }
...@@ -379,7 +422,10 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -379,7 +422,10 @@ namespace DotNetCore.CAP.Infrastructure
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (obj is ObjectId) if (obj is ObjectId)
{
return Equals((ObjectId) obj); return Equals((ObjectId) obj);
}
return false; return false;
} }
...@@ -423,15 +469,21 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -423,15 +469,21 @@ namespace DotNetCore.CAP.Infrastructure
public static byte[] ParseHexString(string s) public static byte[] ParseHexString(string s)
{ {
if (s == null) if (s == null)
{
throw new ArgumentNullException("s"); throw new ArgumentNullException("s");
}
if (s.Length % 2 == 1) if (s.Length % 2 == 1)
{
throw new Exception("The binary key cannot have an odd number of digits"); throw new Exception("The binary key cannot have an odd number of digits");
}
var arr = new byte[s.Length >> 1]; var arr = new byte[s.Length >> 1];
for (var i = 0; i < s.Length >> 1; ++i) for (var i = 0; i < s.Length >> 1; ++i)
{
arr[i] = (byte) ((GetHexVal(s[i << 1]) << 4) + GetHexVal(s[(i << 1) + 1])); arr[i] = (byte) ((GetHexVal(s[i << 1]) << 4) + GetHexVal(s[(i << 1) + 1]));
}
return arr; return arr;
} }
...@@ -444,7 +496,10 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -444,7 +496,10 @@ namespace DotNetCore.CAP.Infrastructure
public static string ToHexString(byte[] bytes) public static string ToHexString(byte[] bytes)
{ {
if (bytes == null) if (bytes == null)
{
throw new ArgumentNullException("bytes"); throw new ArgumentNullException("bytes");
}
var result = new char[bytes.Length * 2]; var result = new char[bytes.Length * 2];
for (var i = 0; i < bytes.Length; i++) for (var i = 0; i < bytes.Length; i++)
{ {
...@@ -452,6 +507,7 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -452,6 +507,7 @@ namespace DotNetCore.CAP.Infrastructure
result[2 * i] = (char) val; result[2 * i] = (char) val;
result[2 * i + 1] = (char) (val >> 16); result[2 * i + 1] = (char) (val >> 16);
} }
return new string(result); return new string(result);
} }
...@@ -474,9 +530,15 @@ namespace DotNetCore.CAP.Infrastructure ...@@ -474,9 +530,15 @@ namespace DotNetCore.CAP.Infrastructure
public static DateTime ToUniversalTime(DateTime dateTime) public static DateTime ToUniversalTime(DateTime dateTime)
{ {
if (dateTime == DateTime.MinValue) if (dateTime == DateTime.MinValue)
{
return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
}
if (dateTime == DateTime.MaxValue) if (dateTime == DateTime.MaxValue)
{
return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc); return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc);
}
return dateTime.ToUniversalTime(); return dateTime.ToUniversalTime();
} }
......
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
{ {
/// <summary> /// <summary>
/// The message status name. /// The message status name.
...@@ -6,8 +9,6 @@ ...@@ -6,8 +9,6 @@
public struct StatusName public struct StatusName
{ {
public const string Scheduled = nameof(Scheduled); 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 Succeeded = nameof(Succeeded);
public const string Failed = nameof(Failed); public const string Failed = nameof(Failed);
} }
......
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;
using System.Threading.Tasks; 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; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
...@@ -56,6 +59,7 @@ namespace DotNetCore.CAP.Internal ...@@ -56,6 +59,7 @@ namespace DotNetCore.CAP.Internal
Clear(); Clear();
_locker.Dispose(); _locker.Dispose();
} }
// Dispose unmanaged resources // Dispose unmanaged resources
} }
} }
...@@ -71,7 +75,9 @@ namespace DotNetCore.CAP.Internal ...@@ -71,7 +75,9 @@ namespace DotNetCore.CAP.Internal
try try
{ {
foreach (var t in _timers.Values) foreach (var t in _timers.Values)
{
t.Dispose(); t.Dispose();
}
} }
catch catch
{ {
...@@ -98,9 +104,11 @@ namespace DotNetCore.CAP.Internal ...@@ -98,9 +104,11 @@ namespace DotNetCore.CAP.Internal
if (_timers.TryGetValue(key, out timer)) if (_timers.TryGetValue(key, out timer))
{ {
if (restartTimerIfExists) if (restartTimerIfExists)
{
timer.Change( timer.Change(
cacheTimeout ?? Timeout.InfiniteTimeSpan, cacheTimeout ?? Timeout.InfiniteTimeSpan,
Timeout.InfiniteTimeSpan); Timeout.InfiniteTimeSpan);
}
} }
else else
{ {
...@@ -139,7 +147,10 @@ namespace DotNetCore.CAP.Internal ...@@ -139,7 +147,10 @@ namespace DotNetCore.CAP.Internal
/// </param> /// </param>
public void AddOrUpdate(K key, T cacheObject, TimeSpan? cacheTimeout, bool restartTimerIfExists = false) public void AddOrUpdate(K key, T cacheObject, TimeSpan? cacheTimeout, bool restartTimerIfExists = false)
{ {
if (disposed) return; if (disposed)
{
return;
}
_locker.EnterWriteLock(); _locker.EnterWriteLock();
try try
...@@ -147,9 +158,13 @@ namespace DotNetCore.CAP.Internal ...@@ -147,9 +158,13 @@ namespace DotNetCore.CAP.Internal
CheckTimer(key, cacheTimeout, restartTimerIfExists); CheckTimer(key, cacheTimeout, restartTimerIfExists);
if (!_cache.ContainsKey(key)) if (!_cache.ContainsKey(key))
{
_cache.Add(key, cacheObject); _cache.Add(key, cacheObject);
}
else else
{
_cache[key] = cacheObject; _cache[key] = cacheObject;
}
} }
finally finally
{ {
...@@ -182,7 +197,10 @@ namespace DotNetCore.CAP.Internal ...@@ -182,7 +197,10 @@ namespace DotNetCore.CAP.Internal
/// <returns>The object from the cache or <c>default(T)</c>, if not found.</returns> /// <returns>The object from the cache or <c>default(T)</c>, if not found.</returns>
public T Get(K key) public T Get(K key)
{ {
if (disposed) return default(T); if (disposed)
{
return default(T);
}
_locker.EnterReadLock(); _locker.EnterReadLock();
try try
...@@ -227,7 +245,10 @@ namespace DotNetCore.CAP.Internal ...@@ -227,7 +245,10 @@ namespace DotNetCore.CAP.Internal
/// <param name="keyPattern">The key pattern to remove. The Predicate has to return true to get key removed.</param> /// <param name="keyPattern">The key pattern to remove. The Predicate has to return true to get key removed.</param>
public void Remove(Predicate<K> keyPattern) public void Remove(Predicate<K> keyPattern)
{ {
if (disposed) return; if (disposed)
{
return;
}
_locker.EnterWriteLock(); _locker.EnterWriteLock();
try try
...@@ -245,6 +266,7 @@ namespace DotNetCore.CAP.Internal ...@@ -245,6 +266,7 @@ namespace DotNetCore.CAP.Internal
catch catch
{ {
} }
_timers.Remove(workKey); _timers.Remove(workKey);
_cache.Remove(workKey); _cache.Remove(workKey);
} }
...@@ -262,7 +284,10 @@ namespace DotNetCore.CAP.Internal ...@@ -262,7 +284,10 @@ namespace DotNetCore.CAP.Internal
/// <param name="key">The cache-key to remove.</param> /// <param name="key">The cache-key to remove.</param>
public void Remove(K key) public void Remove(K key)
{ {
if (disposed) return; if (disposed)
{
return;
}
_locker.EnterWriteLock(); _locker.EnterWriteLock();
try try
...@@ -276,6 +301,7 @@ namespace DotNetCore.CAP.Internal ...@@ -276,6 +301,7 @@ namespace DotNetCore.CAP.Internal
catch catch
{ {
} }
_timers.Remove(key); _timers.Remove(key);
_cache.Remove(key); _cache.Remove(key);
} }
...@@ -293,7 +319,10 @@ namespace DotNetCore.CAP.Internal ...@@ -293,7 +319,10 @@ namespace DotNetCore.CAP.Internal
/// <returns><c>True</c> if the key exists in the cache, otherwise <c>False</c>.</returns> /// <returns><c>True</c> if the key exists in the cache, otherwise <c>False</c>.</returns>
public bool Exists(K key) public bool Exists(K key)
{ {
if (disposed) return false; if (disposed)
{
return false;
}
_locker.EnterReadLock(); _locker.EnterReadLock();
try try
......
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 namespace DotNetCore.CAP.Internal
{ {
/// <summary> /// <summary>
/// A context for consumers, it used to be provider wrapper of method description and received message. /// A context for consumers, it used to be provider wrapper of method description and received message.
/// </summary> /// </summary>
internal class ConsumerContext public class ConsumerContext
{ {
/// <summary> /// <summary>
/// create a new instance of <see cref="ConsumerContext" /> . /// create a new instance of <see cref="ConsumerContext" /> .
......
using System; // Copyright (c) .NET Core Community. All rights reserved.
using System.Collections.Generic; // Licensed under the MIT License. See License.txt in the project root for license information.
using System.Text;
namespace DotNetCore.CAP.Internal namespace DotNetCore.CAP.Internal
{ {
...@@ -19,4 +18,4 @@ namespace DotNetCore.CAP.Internal ...@@ -19,4 +18,4 @@ namespace DotNetCore.CAP.Internal
public string CallbackName { get; set; } public string CallbackName { get; set; }
} }
} }
\ No newline at end of file
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; using DotNetCore.CAP.Abstractions;
namespace DotNetCore.CAP.Internal namespace DotNetCore.CAP.Internal
...@@ -6,7 +9,7 @@ namespace DotNetCore.CAP.Internal ...@@ -6,7 +9,7 @@ namespace DotNetCore.CAP.Internal
/// <summary> /// <summary>
/// A descriptor of user definition method. /// A descriptor of user definition method.
/// </summary> /// </summary>
internal class ConsumerExecutorDescriptor public class ConsumerExecutorDescriptor
{ {
public MethodInfo MethodInfo { get; set; } public MethodInfo MethodInfo { get; set; }
......
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.Abstractions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
...@@ -6,7 +9,7 @@ namespace DotNetCore.CAP.Internal ...@@ -6,7 +9,7 @@ namespace DotNetCore.CAP.Internal
{ {
internal class ConsumerInvokerFactory : IConsumerInvokerFactory internal class ConsumerInvokerFactory : IConsumerInvokerFactory
{ {
private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory;
private readonly IMessagePacker _messagePacker; private readonly IMessagePacker _messagePacker;
private readonly IModelBinderFactory _modelBinderFactory; private readonly IModelBinderFactory _modelBinderFactory;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
...@@ -17,7 +20,7 @@ namespace DotNetCore.CAP.Internal ...@@ -17,7 +20,7 @@ namespace DotNetCore.CAP.Internal
IModelBinderFactory modelBinderFactory, IModelBinderFactory modelBinderFactory,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
{ {
_logger = loggerFactory.CreateLogger<ConsumerInvokerFactory>(); _loggerFactory = loggerFactory;
_messagePacker = messagePacker; _messagePacker = messagePacker;
_modelBinderFactory = modelBinderFactory; _modelBinderFactory = modelBinderFactory;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
...@@ -25,7 +28,7 @@ namespace DotNetCore.CAP.Internal ...@@ -25,7 +28,7 @@ namespace DotNetCore.CAP.Internal
public IConsumerInvoker CreateInvoker() public IConsumerInvoker CreateInvoker()
{ {
return new DefaultConsumerInvoker(_logger, _serviceProvider, _messagePacker, _modelBinderFactory); return new DefaultConsumerInvoker(_loggerFactory, _serviceProvider, _messagePacker, _modelBinderFactory);
} }
} }
} }
\ No newline at end of file
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.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
...@@ -10,7 +13,8 @@ namespace DotNetCore.CAP.Internal ...@@ -10,7 +13,8 @@ namespace DotNetCore.CAP.Internal
public int CombinedHash public int CombinedHash
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] get { return _combinedHash64.GetHashCode(); } [MethodImpl(MethodImplOptions.AggressiveInlining)]
get { return _combinedHash64.GetHashCode(); }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
...@@ -34,6 +38,7 @@ namespace DotNetCore.CAP.Internal ...@@ -34,6 +38,7 @@ namespace DotNetCore.CAP.Internal
Add(o); Add(o);
count++; count++;
} }
Add(count); Add(count);
} }
} }
......
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 System.Threading.Tasks;
using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Abstractions;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
...@@ -10,10 +13,10 @@ namespace DotNetCore.CAP.Internal ...@@ -10,10 +13,10 @@ namespace DotNetCore.CAP.Internal
{ {
internal class CallbackMessageSender : ICallbackMessageSender internal class CallbackMessageSender : ICallbackMessageSender
{ {
private readonly ILogger<CallbackMessageSender> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IContentSerializer _contentSerializer; private readonly IContentSerializer _contentSerializer;
private readonly ILogger<CallbackMessageSender> _logger;
private readonly IMessagePacker _messagePacker; private readonly IMessagePacker _messagePacker;
private readonly IServiceProvider _serviceProvider;
public CallbackMessageSender( public CallbackMessageSender(
ILogger<CallbackMessageSender> logger, ILogger<CallbackMessageSender> logger,
...@@ -31,9 +34,13 @@ namespace DotNetCore.CAP.Internal ...@@ -31,9 +34,13 @@ namespace DotNetCore.CAP.Internal
{ {
string body; string body;
if (bodyObj != null && Helper.IsComplexType(bodyObj.GetType())) if (bodyObj != null && Helper.IsComplexType(bodyObj.GetType()))
{
body = _contentSerializer.Serialize(bodyObj); body = _contentSerializer.Serialize(bodyObj);
}
else else
{
body = bodyObj?.ToString(); body = bodyObj?.ToString();
}
_logger.LogDebug($"Callback message will publishing, name:{topicName},content:{body}"); _logger.LogDebug($"Callback message will publishing, name:{topicName},content:{body}");
...@@ -56,8 +63,8 @@ namespace DotNetCore.CAP.Internal ...@@ -56,8 +63,8 @@ namespace DotNetCore.CAP.Internal
{ {
var provider = scope.ServiceProvider; var provider = scope.ServiceProvider;
var callbackPublisher = provider.GetService<ICallbackPublisher>(); var callbackPublisher = provider.GetService<ICallbackPublisher>();
await callbackPublisher.PublishAsync(publishedMessage); await callbackPublisher.PublishCallbackAsync(publishedMessage);
} }
} }
} }
} }
\ No newline at end of file
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 namespace DotNetCore.CAP.Internal
{ {
......
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 System.Threading.Tasks;
using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Abstractions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
...@@ -10,11 +13,11 @@ namespace DotNetCore.CAP.Internal ...@@ -10,11 +13,11 @@ namespace DotNetCore.CAP.Internal
internal class DefaultConsumerInvoker : IConsumerInvoker internal class DefaultConsumerInvoker : IConsumerInvoker
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IMessagePacker _messagePacker;
private readonly IModelBinderFactory _modelBinderFactory; private readonly IModelBinderFactory _modelBinderFactory;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IMessagePacker _messagePacker;
public DefaultConsumerInvoker(ILogger logger, public DefaultConsumerInvoker(ILoggerFactory loggerFactory,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IMessagePacker messagePacker, IMessagePacker messagePacker,
IModelBinderFactory modelBinderFactory) IModelBinderFactory modelBinderFactory)
...@@ -22,7 +25,7 @@ namespace DotNetCore.CAP.Internal ...@@ -22,7 +25,7 @@ namespace DotNetCore.CAP.Internal
_modelBinderFactory = modelBinderFactory; _modelBinderFactory = modelBinderFactory;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_messagePacker = messagePacker; _messagePacker = messagePacker;
_logger = logger; _logger = loggerFactory.CreateLogger<DefaultConsumerInvoker>();
} }
public async Task<ConsumerExecutedResult> InvokeAsync(ConsumerContext context) public async Task<ConsumerExecutedResult> InvokeAsync(ConsumerContext context)
...@@ -44,9 +47,14 @@ namespace DotNetCore.CAP.Internal ...@@ -44,9 +47,14 @@ namespace DotNetCore.CAP.Internal
object resultObj; object resultObj;
if (executor.MethodParameters.Length > 0) if (executor.MethodParameters.Length > 0)
{
resultObj = await ExecuteWithParameterAsync(executor, obj, message.Content); resultObj = await ExecuteWithParameterAsync(executor, obj, message.Content);
}
else else
{
resultObj = await ExecuteAsync(executor, obj); resultObj = await ExecuteAsync(executor, obj);
}
return new ConsumerExecutedResult(resultObj, message.Id, message.CallbackName); return new ConsumerExecutedResult(resultObj, message.Id, message.CallbackName);
} }
} }
...@@ -54,7 +62,10 @@ namespace DotNetCore.CAP.Internal ...@@ -54,7 +62,10 @@ namespace DotNetCore.CAP.Internal
private async Task<object> ExecuteAsync(ObjectMethodExecutor executor, object @class) private async Task<object> ExecuteAsync(ObjectMethodExecutor executor, object @class)
{ {
if (executor.IsMethodAsync) if (executor.IsMethodAsync)
{
return await executor.ExecuteAsync(@class); return await executor.ExecuteAsync(@class);
}
return executor.Execute(@class); return executor.Execute(@class);
} }
...@@ -69,9 +80,13 @@ namespace DotNetCore.CAP.Internal ...@@ -69,9 +80,13 @@ namespace DotNetCore.CAP.Internal
if (bindResult.IsSuccess) if (bindResult.IsSuccess)
{ {
if (executor.IsMethodAsync) if (executor.IsMethodAsync)
{
return await executor.ExecuteAsync(@class, bindResult.Model); return await executor.ExecuteAsync(@class, bindResult.Model);
}
return executor.Execute(@class, bindResult.Model); return executor.Execute(@class, bindResult.Model);
} }
throw new MethodBindException( throw new MethodBindException(
$"Parameters:{firstParameter.Name} bind failed! ParameterString is: {parameterString} "); $"Parameters:{firstParameter.Name} bind failed! ParameterString is: {parameterString} ");
} }
......
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 namespace DotNetCore.CAP.Internal
{ {
......
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 internal interface IConsumerInvokerFactory
{ {
......
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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
...@@ -10,7 +13,7 @@ namespace DotNetCore.CAP.Internal ...@@ -10,7 +13,7 @@ namespace DotNetCore.CAP.Internal
{ {
/// <inheritdoc /> /// <inheritdoc />
/// <summary> /// <summary>
/// A default <see cref="T:DotNetCore.CAP.Abstractions.IConsumerServiceSelector" /> implementation. /// A default <see cref="T:DotNetCore.CAP.Abstractions.IConsumerServiceSelector" /> implementation.
/// </summary> /// </summary>
internal class DefaultConsumerServiceSelector : IConsumerServiceSelector internal class DefaultConsumerServiceSelector : IConsumerServiceSelector
{ {
...@@ -18,7 +21,7 @@ namespace DotNetCore.CAP.Internal ...@@ -18,7 +21,7 @@ namespace DotNetCore.CAP.Internal
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
/// <summary> /// <summary>
/// Creates a new <see cref="DefaultConsumerServiceSelector" />. /// Creates a new <see cref="DefaultConsumerServiceSelector" />.
/// </summary> /// </summary>
public DefaultConsumerServiceSelector(IServiceProvider serviceProvider, CapOptions capOptions) public DefaultConsumerServiceSelector(IServiceProvider serviceProvider, CapOptions capOptions)
{ {
...@@ -27,9 +30,9 @@ namespace DotNetCore.CAP.Internal ...@@ -27,9 +30,9 @@ namespace DotNetCore.CAP.Internal
} }
/// <summary> /// <summary>
/// Selects the best <see cref="ConsumerExecutorDescriptor" /> candidate from <paramref name="executeDescriptor" /> for /// Selects the best <see cref="ConsumerExecutorDescriptor" /> candidate from <paramref name="executeDescriptor" /> for
/// the /// the
/// current message associated. /// current message associated.
/// </summary> /// </summary>
public ConsumerExecutorDescriptor SelectBestCandidate(string key, public ConsumerExecutorDescriptor SelectBestCandidate(string key,
IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor) IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor)
...@@ -61,10 +64,13 @@ namespace DotNetCore.CAP.Internal ...@@ -61,10 +64,13 @@ namespace DotNetCore.CAP.Internal
{ {
var typeInfo = service.GetType().GetTypeInfo(); var typeInfo = service.GetType().GetTypeInfo();
if (!typeof(ICapSubscribe).GetTypeInfo().IsAssignableFrom(typeInfo)) if (!typeof(ICapSubscribe).GetTypeInfo().IsAssignableFrom(typeInfo))
{
continue; continue;
}
executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo)); executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo));
} }
return executorDescriptorList; return executorDescriptorList;
} }
} }
...@@ -78,7 +84,9 @@ namespace DotNetCore.CAP.Internal ...@@ -78,7 +84,9 @@ namespace DotNetCore.CAP.Internal
{ {
var typeInfo = type.GetTypeInfo(); var typeInfo = type.GetTypeInfo();
if (Helper.IsController(typeInfo)) if (Helper.IsController(typeInfo))
{
executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo)); executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo));
}
} }
return executorDescriptorList; return executorDescriptorList;
...@@ -91,12 +99,18 @@ namespace DotNetCore.CAP.Internal ...@@ -91,12 +99,18 @@ namespace DotNetCore.CAP.Internal
var topicAttr = method.GetCustomAttributes<TopicAttribute>(true); var topicAttr = method.GetCustomAttributes<TopicAttribute>(true);
var topicAttributes = topicAttr as IList<TopicAttribute> ?? topicAttr.ToList(); var topicAttributes = topicAttr as IList<TopicAttribute> ?? topicAttr.ToList();
if (!topicAttributes.Any()) continue; if (!topicAttributes.Any())
{
continue;
}
foreach (var attr in topicAttributes) foreach (var attr in topicAttributes)
{ {
if (attr.Group == null) if (attr.Group == null)
{
attr.Group = _capOptions.DefaultGroup; attr.Group = _capOptions.DefaultGroup;
}
yield return InitDescriptor(attr, method, typeInfo); yield return InitDescriptor(attr, method, typeInfo);
} }
} }
......
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 namespace DotNetCore.CAP.Internal
{ {
......
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.Abstractions;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
...@@ -6,7 +9,7 @@ namespace DotNetCore.CAP.Internal ...@@ -6,7 +9,7 @@ namespace DotNetCore.CAP.Internal
{ {
internal class JsonContentSerializer : IContentSerializer internal class JsonContentSerializer : IContentSerializer
{ {
public T DeSerialize<T>(string messageObjStr) public T DeSerialize<T>(string messageObjStr)
{ {
return Helper.FromJson<T>(messageObjStr); return Helper.FromJson<T>(messageObjStr);
} }
......
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; using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Internal namespace DotNetCore.CAP.Internal
...@@ -22,4 +25,4 @@ namespace DotNetCore.CAP.Internal ...@@ -22,4 +25,4 @@ namespace DotNetCore.CAP.Internal
return _serializer.DeSerialize<CapMessageDto>(packingMessage); return _serializer.DeSerialize<CapMessageDto>(packingMessage);
} }
} }
} }
\ No newline at end of file
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.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Abstractions; using DotNetCore.CAP.Abstractions;
......
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.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Reflection; using System.Reflection;
...@@ -22,7 +25,9 @@ namespace DotNetCore.CAP.Internal ...@@ -22,7 +25,9 @@ namespace DotNetCore.CAP.Internal
public Task<ModelBindingResult> BindModelAsync(string content) public Task<ModelBindingResult> BindModelAsync(string content)
{ {
if (content == null) if (content == null)
{
throw new ArgumentNullException(nameof(content)); throw new ArgumentNullException(nameof(content));
}
var parameterType = _parameterInfo.ParameterType; var parameterType = _parameterInfo.ParameterType;
...@@ -30,27 +35,43 @@ namespace DotNetCore.CAP.Internal ...@@ -30,27 +35,43 @@ namespace DotNetCore.CAP.Internal
{ {
object model; object model;
if (parameterType == typeof(string)) if (parameterType == typeof(string))
{
if (string.IsNullOrWhiteSpace(content)) if (string.IsNullOrWhiteSpace(content))
{
model = null; model = null;
}
else else
{
model = content; model = content;
}
}
else if (string.IsNullOrWhiteSpace(content)) else if (string.IsNullOrWhiteSpace(content))
{
model = null; model = null;
}
else else
{
model = _typeConverter.ConvertFrom( model = _typeConverter.ConvertFrom(
null, null,
CultureInfo.CurrentCulture, CultureInfo.CurrentCulture,
content); content);
}
if (model == null && !IsReferenceOrNullableType(parameterType)) if (model == null && !IsReferenceOrNullableType(parameterType))
{
return Task.FromResult(ModelBindingResult.Failed()); return Task.FromResult(ModelBindingResult.Failed());
}
return Task.FromResult(ModelBindingResult.Success(model)); return Task.FromResult(ModelBindingResult.Success(model));
} }
catch (Exception exception) catch (Exception exception)
{ {
var isFormatException = exception is FormatException; var isFormatException = exception is FormatException;
if (!isFormatException && exception.InnerException != null) if (!isFormatException && exception.InnerException != null)
{
exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException; exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException;
}
throw; throw;
} }
} }
......
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<DefaultSubscriberExecutor> _logger;
private readonly MethodMatcherCache _selector;
private IConsumerInvoker Invoker { get; }
public DefaultSubscriberExecutor(MethodMatcherCache selector,
IConsumerInvokerFactory consumerInvokerFactory,
ICallbackMessageSender callbackMessageSender,
ILogger<DefaultSubscriberExecutor> logger)
{
_selector = selector;
_callbackMessageSender = callbackMessageSender;
_logger = logger;
Invoker = consumerInvokerFactory.CreateInvoker();
}
public async Task<OperateResult> 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
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 namespace DotNetCore.CAP.Internal
{ {
......
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.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
...@@ -24,14 +27,19 @@ namespace DotNetCore.CAP.Internal ...@@ -24,14 +27,19 @@ namespace DotNetCore.CAP.Internal
/// </summary> /// </summary>
public ConcurrentDictionary<string, IList<ConsumerExecutorDescriptor>> GetCandidatesMethodsOfGroupNameGrouped() public ConcurrentDictionary<string, IList<ConsumerExecutorDescriptor>> GetCandidatesMethodsOfGroupNameGrouped()
{ {
if (Entries.Count != 0) return Entries; if (Entries.Count != 0)
{
return Entries;
}
var executorCollection = _selector.SelectCandidates(); var executorCollection = _selector.SelectCandidates();
var groupedCandidates = executorCollection.GroupBy(x => x.Attribute.Group); var groupedCandidates = executorCollection.GroupBy(x => x.Attribute.Group);
foreach (var item in groupedCandidates) foreach (var item in groupedCandidates)
{
Entries.TryAdd(item.Key, item.ToList()); Entries.TryAdd(item.Key, item.ToList());
}
return Entries; return Entries;
} }
...@@ -44,7 +52,9 @@ namespace DotNetCore.CAP.Internal ...@@ -44,7 +52,9 @@ namespace DotNetCore.CAP.Internal
public IDictionary<string, IList<ConsumerExecutorDescriptor>> GetTopicExector(string topicName) public IDictionary<string, IList<ConsumerExecutorDescriptor>> GetTopicExector(string topicName)
{ {
if (Entries == null) if (Entries == null)
{
throw new ArgumentNullException(nameof(Entries)); throw new ArgumentNullException(nameof(Entries));
}
var dic = new Dictionary<string, IList<ConsumerExecutorDescriptor>>(); var dic = new Dictionary<string, IList<ConsumerExecutorDescriptor>>();
foreach (var item in Entries) foreach (var item in Entries)
...@@ -52,11 +62,13 @@ namespace DotNetCore.CAP.Internal ...@@ -52,11 +62,13 @@ namespace DotNetCore.CAP.Internal
var topicCandidates = item.Value.Where(x => x.Attribute.Name == topicName); var topicCandidates = item.Value.Where(x => x.Attribute.Name == topicName);
dic.Add(item.Key, topicCandidates.ToList()); dic.Add(item.Key, topicCandidates.ToList());
} }
return dic; return dic;
} }
/// <summary> /// <summary>
/// Attempts to get the topic exector associated with the specified topic name and group name from the <see cref="Entries"/>. /// Attempts to get the topic exector associated with the specified topic name and group name from the
/// <see cref="Entries" />.
/// </summary> /// </summary>
/// <param name="topicName">The topic name of the value to get.</param> /// <param name="topicName">The topic name of the value to get.</param>
/// <param name="groupName">The group name of the value to get.</param> /// <param name="groupName">The group name of the value to get.</param>
...@@ -66,7 +78,9 @@ namespace DotNetCore.CAP.Internal ...@@ -66,7 +78,9 @@ namespace DotNetCore.CAP.Internal
out ConsumerExecutorDescriptor matchTopic) out ConsumerExecutorDescriptor matchTopic)
{ {
if (Entries == null) if (Entries == null)
{
throw new ArgumentNullException(nameof(Entries)); throw new ArgumentNullException(nameof(Entries));
}
matchTopic = null; matchTopic = null;
...@@ -75,6 +89,7 @@ namespace DotNetCore.CAP.Internal ...@@ -75,6 +89,7 @@ namespace DotNetCore.CAP.Internal
matchTopic = groupMatchTopics.FirstOrDefault(x => x.Attribute.Name == topicName); matchTopic = groupMatchTopics.FirstOrDefault(x => x.Attribute.Name == topicName);
return matchTopic != null; return matchTopic != null;
} }
return false; return false;
} }
...@@ -89,7 +104,9 @@ namespace DotNetCore.CAP.Internal ...@@ -89,7 +104,9 @@ namespace DotNetCore.CAP.Internal
} }
if (Entries == null) if (Entries == null)
{
throw new ArgumentNullException(nameof(Entries)); throw new ArgumentNullException(nameof(Entries));
}
_allTopics = new List<string>(); _allTopics = new List<string>();
...@@ -97,6 +114,7 @@ namespace DotNetCore.CAP.Internal ...@@ -97,6 +114,7 @@ namespace DotNetCore.CAP.Internal
{ {
_allTopics.AddRange(descriptors.Select(x => x.Attribute.Name)); _allTopics.AddRange(descriptors.Select(x => x.Attribute.Name));
} }
return _allTopics; return _allTopics;
} }
} }
......
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.Concurrent;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
...@@ -13,8 +16,8 @@ namespace DotNetCore.CAP.Internal ...@@ -13,8 +16,8 @@ namespace DotNetCore.CAP.Internal
/// </summary> /// </summary>
internal class ModelBinderFactory : IModelBinderFactory internal class ModelBinderFactory : IModelBinderFactory
{ {
private readonly IContentSerializer _serializer;
private readonly ConcurrentDictionary<Key, IModelBinder> _cache; private readonly ConcurrentDictionary<Key, IModelBinder> _cache;
private readonly IContentSerializer _serializer;
public ModelBinderFactory(IContentSerializer contentSerializer) public ModelBinderFactory(IContentSerializer contentSerializer)
{ {
...@@ -25,13 +28,17 @@ namespace DotNetCore.CAP.Internal ...@@ -25,13 +28,17 @@ namespace DotNetCore.CAP.Internal
public IModelBinder CreateBinder(ParameterInfo parameter) public IModelBinder CreateBinder(ParameterInfo parameter)
{ {
if (parameter == null) if (parameter == null)
{
throw new ArgumentNullException(nameof(parameter)); throw new ArgumentNullException(nameof(parameter));
}
object token = parameter; object token = parameter;
var binder = CreateBinderCoreCached(parameter, token); var binder = CreateBinderCoreCached(parameter, token);
if (binder == null) if (binder == null)
{
throw new InvalidOperationException("Format Could Not Create IModelBinder"); throw new InvalidOperationException("Format Could Not Create IModelBinder");
}
return binder; return binder;
} }
...@@ -39,11 +46,18 @@ namespace DotNetCore.CAP.Internal ...@@ -39,11 +46,18 @@ namespace DotNetCore.CAP.Internal
private IModelBinder CreateBinderCoreCached(ParameterInfo parameterInfo, object token) private IModelBinder CreateBinderCoreCached(ParameterInfo parameterInfo, object token)
{ {
if (TryGetCachedBinder(parameterInfo, token, out var binder)) if (TryGetCachedBinder(parameterInfo, token, out var binder))
{
return binder; return binder;
}
if (!Helper.IsComplexType(parameterInfo.ParameterType)) if (!Helper.IsComplexType(parameterInfo.ParameterType))
{
binder = new SimpleTypeModelBinder(parameterInfo); binder = new SimpleTypeModelBinder(parameterInfo);
}
else else
{
binder = new ComplexTypeModelBinder(parameterInfo, _serializer); binder = new ComplexTypeModelBinder(parameterInfo, _serializer);
}
AddToCache(parameterInfo, token, binder); AddToCache(parameterInfo, token, binder);
...@@ -53,7 +67,9 @@ namespace DotNetCore.CAP.Internal ...@@ -53,7 +67,9 @@ namespace DotNetCore.CAP.Internal
private void AddToCache(ParameterInfo info, object cacheToken, IModelBinder binder) private void AddToCache(ParameterInfo info, object cacheToken, IModelBinder binder)
{ {
if (cacheToken == null) if (cacheToken == null)
{
return; return;
}
_cache.TryAdd(new Key(info, cacheToken), binder); _cache.TryAdd(new Key(info, cacheToken), binder);
} }
......
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Core Community. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the MIT License. See License.txt in the project root for license information.
using System; using System;
using System.Linq; using System.Linq;
......
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Core Community. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the MIT License. See License.txt in the project root for license information.
using System; using System;
using System.Linq.Expressions; using System.Linq.Expressions;
...@@ -41,11 +41,13 @@ namespace Microsoft.Extensions.Internal ...@@ -41,11 +41,13 @@ namespace Microsoft.Extensions.Internal
if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type,
out var coercerExpression, out var coercerExpression,
out var coercerResultType)) out var coercerResultType))
{
if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo))
{ {
info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo);
return true; return true;
} }
}
info = default(CoercedAwaitableInfo); info = default(CoercedAwaitableInfo);
return false; return false;
......
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Core Community. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the MIT License. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
...@@ -30,7 +30,9 @@ namespace Microsoft.Extensions.Internal ...@@ -30,7 +30,9 @@ namespace Microsoft.Extensions.Internal
private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues)
{ {
if (methodInfo == null) if (methodInfo == null)
{
throw new ArgumentNullException(nameof(methodInfo)); throw new ArgumentNullException(nameof(methodInfo));
}
MethodInfo = methodInfo; MethodInfo = methodInfo;
MethodParameters = methodInfo.GetParameters(); MethodParameters = methodInfo.GetParameters();
...@@ -48,7 +50,9 @@ namespace Microsoft.Extensions.Internal ...@@ -48,7 +50,9 @@ namespace Microsoft.Extensions.Internal
_executor = GetExecutor(methodInfo, targetTypeInfo); _executor = GetExecutor(methodInfo, targetTypeInfo);
if (IsMethodAsync) if (IsMethodAsync)
{
_executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo); _executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo);
}
_parameterDefaultValues = parameterDefaultValues; _parameterDefaultValues = parameterDefaultValues;
} }
...@@ -75,7 +79,9 @@ namespace Microsoft.Extensions.Internal ...@@ -75,7 +79,9 @@ namespace Microsoft.Extensions.Internal
object[] parameterDefaultValues) object[] parameterDefaultValues)
{ {
if (parameterDefaultValues == null) if (parameterDefaultValues == null)
{
throw new ArgumentNullException(nameof(parameterDefaultValues)); throw new ArgumentNullException(nameof(parameterDefaultValues));
}
return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues); return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues);
} }
...@@ -127,11 +133,15 @@ namespace Microsoft.Extensions.Internal ...@@ -127,11 +133,15 @@ namespace Microsoft.Extensions.Internal
public object GetDefaultValueForParameter(int index) public object GetDefaultValueForParameter(int index)
{ {
if (_parameterDefaultValues == null) if (_parameterDefaultValues == null)
{
throw new InvalidOperationException( throw new InvalidOperationException(
$"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied."); $"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied.");
}
if (index < 0 || index > MethodParameters.Length - 1) if (index < 0 || index > MethodParameters.Length - 1)
{
throw new ArgumentOutOfRangeException(nameof(index)); throw new ArgumentOutOfRangeException(nameof(index));
}
return _parameterDefaultValues[index]; return _parameterDefaultValues[index];
} }
...@@ -241,6 +251,7 @@ namespace Microsoft.Extensions.Internal ...@@ -241,6 +251,7 @@ namespace Microsoft.Extensions.Internal
var getResultParam = Expression.Parameter(typeof(object), "awaiter"); var getResultParam = Expression.Parameter(typeof(object), "awaiter");
Func<object, object> getResultFunc; Func<object, object> getResultFunc;
if (awaitableInfo.ResultType == typeof(void)) if (awaitableInfo.ResultType == typeof(void))
{
getResultFunc = Expression.Lambda<Func<object, object>>( getResultFunc = Expression.Lambda<Func<object, object>>(
Expression.Block( Expression.Block(
Expression.Call( Expression.Call(
...@@ -249,7 +260,9 @@ namespace Microsoft.Extensions.Internal ...@@ -249,7 +260,9 @@ namespace Microsoft.Extensions.Internal
Expression.Constant(null) Expression.Constant(null)
), ),
getResultParam).Compile(); getResultParam).Compile();
}
else else
{
getResultFunc = Expression.Lambda<Func<object, object>>( getResultFunc = Expression.Lambda<Func<object, object>>(
Expression.Convert( Expression.Convert(
Expression.Call( Expression.Call(
...@@ -257,6 +270,7 @@ namespace Microsoft.Extensions.Internal ...@@ -257,6 +270,7 @@ namespace Microsoft.Extensions.Internal
awaitableInfo.AwaiterGetResultMethod), awaitableInfo.AwaiterGetResultMethod),
typeof(object)), typeof(object)),
getResultParam).Compile(); getResultParam).Compile();
}
// var onCompletedFunc = (object awaiter, Action continuation) => { // var onCompletedFunc = (object awaiter, Action continuation) => {
// ((CustomAwaiterType)awaiter).OnCompleted(continuation); // ((CustomAwaiterType)awaiter).OnCompleted(continuation);
......
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Core Community. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the MIT License. See License.txt in the project root for license information.
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
......
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Core Community. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the MIT License. See License.txt in the project root for license information.
using System; using System;
using System.Linq; using System.Linq;
...@@ -73,12 +73,17 @@ namespace Microsoft.Extensions.Internal ...@@ -73,12 +73,17 @@ namespace Microsoft.Extensions.Internal
{ {
var typeFullName = possibleFSharpAsyncGenericType?.FullName; var typeFullName = possibleFSharpAsyncGenericType?.FullName;
if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal)) if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal))
{
return false; return false;
}
lock (_fsharpValuesCacheLock) lock (_fsharpValuesCacheLock)
{ {
if (_fsharpCoreAssembly != null) if (_fsharpCoreAssembly != null)
{
return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly; return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly;
}
return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType); return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType);
} }
} }
...@@ -90,7 +95,9 @@ namespace Microsoft.Extensions.Internal ...@@ -90,7 +95,9 @@ namespace Microsoft.Extensions.Internal
var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"); var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync");
if (fsharpOptionType == null || fsharpAsyncType == null) if (fsharpOptionType == null || fsharpAsyncType == null)
{
return false; return false;
}
// Get a reference to FSharpOption<TaskCreationOptions>.None // Get a reference to FSharpOption<TaskCreationOptions>.None
var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType
......
// 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, Exception ex) : base(message, ex)
{
}
}
}
\ No newline at end of file
// 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
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 namespace DotNetCore.CAP.Internal
{ {
......
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; using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
[SuppressMessage("ReSharper", "InconsistentNaming")]
internal static class LoggerExtensions internal static class LoggerExtensions
{ {
private static readonly Action<ILogger, int, int, Exception> _serverStarting; private static readonly Action<ILogger, Exception> _serverStarting;
private static readonly Action<ILogger, Exception> _processorsStartingError; private static readonly Action<ILogger, Exception> _processorsStartingError;
private static readonly Action<ILogger, Exception> _serverShuttingDown; private static readonly Action<ILogger, Exception> _serverShuttingDown;
private static readonly Action<ILogger, string, Exception> _expectedOperationCanceledException; private static readonly Action<ILogger, string, Exception> _expectedOperationCanceledException;
private static readonly Action<ILogger, string, string, Exception> _enqueueingSentMessage;
private static readonly Action<ILogger, string, string, Exception> _enqueueingReceivdeMessage;
private static readonly Action<ILogger, string, Exception> _executingConsumerMethod;
private static readonly Action<ILogger, string, Exception> _receivedMessageRetryExecuting;
private static readonly Action<ILogger, string, string, string, Exception> _modelBinderFormattingException; private static readonly Action<ILogger, string, string, string, Exception> _modelBinderFormattingException;
private static readonly Action<ILogger, Exception> _consumerFailedWillRetry;
private static readonly Action<ILogger, Exception> _jobFailed; private static readonly Action<ILogger, double, Exception> _consumerExecuted;
private static readonly Action<ILogger, Exception> _jobFailedWillRetry; private static readonly Action<ILogger, int, Exception> _senderRetrying;
private static readonly Action<ILogger, double, Exception> _jobExecuted; private static readonly Action<ILogger, string, Exception> _exceptionOccuredWhileExecuting;
private static readonly Action<ILogger, int, Exception> _jobRetrying; private static readonly Action<ILogger, double, Exception> _messageHasBeenSent;
private static readonly Action<ILogger, string, Exception> _exceptionOccuredWhileExecutingJob; private static readonly Action<ILogger, int, Exception> _messagePublishException;
private static readonly Action<ILogger, string, Exception> _messageQueueError;
static LoggerExtensions() static LoggerExtensions()
{ {
_serverStarting = LoggerMessage.Define<int, int>( _serverStarting = LoggerMessage.Define(
LogLevel.Debug, LogLevel.Debug,
1, 1,
"Starting the processing server. Detected {MachineProcessorCount} machine processor(s). Initiating {ProcessorCount} job processor(s)."); "Starting the processing server.");
_processorsStartingError = LoggerMessage.Define( _processorsStartingError = LoggerMessage.Define(
LogLevel.Error, LogLevel.Error,
...@@ -46,22 +44,12 @@ namespace DotNetCore.CAP ...@@ -46,22 +44,12 @@ namespace DotNetCore.CAP
3, 3,
"Expected an OperationCanceledException, but found '{ExceptionMessage}'."); "Expected an OperationCanceledException, but found '{ExceptionMessage}'.");
_enqueueingSentMessage = LoggerMessage.Define<string, string>( LoggerMessage.Define<string>(
LogLevel.Debug,
2,
"Enqueuing a topic to the sent message store. NameKey: '{NameKey}' Content: '{Content}'.");
_enqueueingReceivdeMessage = LoggerMessage.Define<string, string>(
LogLevel.Debug,
2,
"Enqueuing a topic to the received message store. NameKey: '{NameKey}. Content: '{Content}'.");
_executingConsumerMethod = LoggerMessage.Define<string>(
LogLevel.Error, LogLevel.Error,
5, 5,
"Consumer method '{methodName}' failed to execute."); "Consumer method '{methodName}' failed to execute.");
_receivedMessageRetryExecuting = LoggerMessage.Define<string>( LoggerMessage.Define<string>(
LogLevel.Error, LogLevel.Error,
5, 5,
"Received message topic method '{topicName}' failed to execute."); "Received message topic method '{topicName}' failed to execute.");
...@@ -72,81 +60,65 @@ namespace DotNetCore.CAP ...@@ -72,81 +60,65 @@ namespace DotNetCore.CAP
"When call subscribe method, a parameter format conversion exception occurs. MethodName:'{MethodName}' ParameterName:'{ParameterName}' Content:'{Content}'." "When call subscribe method, a parameter format conversion exception occurs. MethodName:'{MethodName}' ParameterName:'{ParameterName}' Content:'{Content}'."
); );
_jobRetrying = LoggerMessage.Define<int>( _senderRetrying = LoggerMessage.Define<int>(
LogLevel.Debug, LogLevel.Debug,
3, 3,
"Retrying a job: {Retries}..."); "Retrying send a message: {Retries}...");
_jobExecuted = LoggerMessage.Define<double>( _consumerExecuted = LoggerMessage.Define<double>(
LogLevel.Debug, LogLevel.Debug,
4, 4,
"Job executed. Took: {Seconds} secs."); "Consumer executed. Took: {Seconds} secs.");
_jobFailed = LoggerMessage.Define( _consumerFailedWillRetry = LoggerMessage.Define(
LogLevel.Warning,
1,
"Job failed to execute.");
_jobFailedWillRetry = LoggerMessage.Define(
LogLevel.Warning, LogLevel.Warning,
2, 2,
"Job failed to execute. Will retry."); "Consumer failed to execute. Will retry.");
_exceptionOccuredWhileExecutingJob = LoggerMessage.Define<string>( _exceptionOccuredWhileExecuting = LoggerMessage.Define<string>(
LogLevel.Error, LogLevel.Error,
6, 6,
"An exception occured while trying to execute a message: '{MessageId}'. " + "An exception occured while trying to store a message: '{MessageId}'. ");
"Requeuing for another retry.");
_messageQueueError = LoggerMessage.Define<string>( _messageHasBeenSent = LoggerMessage.Define<double>(
LogLevel.Error, LogLevel.Debug,
7, 4,
"The MessageQueue Client fires an internal error:'{error}'."); "Message published. Took: {Seconds} secs.");
}
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) _messagePublishException = LoggerMessage.Define<int>(
{ LogLevel.Error,
_jobRetrying(logger, retries, null); 6,
"An exception occured while publishing a message: '{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 retries)
{ {
_executingConsumerMethod(logger, methodName, ex); _senderRetrying(logger, 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, Exception ex)
{ {
_enqueueingReceivdeMessage(logger, nameKey, content, null); _messagePublishException(logger, messageId, 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) public static void ProcessorsStartedError(this ILogger logger, Exception ex)
...@@ -166,7 +138,7 @@ namespace DotNetCore.CAP ...@@ -166,7 +138,7 @@ namespace DotNetCore.CAP
public static void ExceptionOccuredWhileExecuting(this ILogger logger, string messageId, Exception ex) public static void ExceptionOccuredWhileExecuting(this ILogger logger, string messageId, Exception ex)
{ {
_exceptionOccuredWhileExecutingJob(logger, messageId, ex); _exceptionOccuredWhileExecuting(logger, messageId, ex);
} }
public static void ModelBinderFormattingException(this ILogger logger, string methodName, string parameterName, public static void ModelBinderFormattingException(this ILogger logger, string methodName, string parameterName,
...@@ -174,10 +146,5 @@ namespace DotNetCore.CAP ...@@ -174,10 +146,5 @@ namespace DotNetCore.CAP
{ {
_modelBinderFormattingException(logger, methodName, parameterName, content, 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
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
{ {
/// <summary> /// <summary>
/// Message context /// Message context
......
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; using DotNetCore.CAP.Infrastructure;
namespace DotNetCore.CAP.Models namespace DotNetCore.CAP.Models
...@@ -14,7 +17,7 @@ namespace DotNetCore.CAP.Models ...@@ -14,7 +17,7 @@ namespace DotNetCore.CAP.Models
public virtual string CallbackName { get; set; } public virtual string CallbackName { get; set; }
} }
public sealed class CapMessageDto: CapMessage public sealed class CapMessageDto : CapMessage
{ {
public CapMessageDto() public CapMessageDto()
{ {
......
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 namespace DotNetCore.CAP.Models
{ {
......
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 public class CapQueue
{ {
......
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 namespace DotNetCore.CAP.Models
{ {
......
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 public enum MessageType
{ {
......
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 namespace DotNetCore.CAP
{ {
...@@ -21,4 +24,4 @@ namespace DotNetCore.CAP ...@@ -21,4 +24,4 @@ namespace DotNetCore.CAP
public MqLogType LogType { get; set; } public MqLogType LogType { get; set; }
} }
} }
\ No newline at end of file
// 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 namespace DotNetCore.CAP
{ {
......
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;
using DotNetCore.CAP.NodeDiscovery; using DotNetCore.CAP.NodeDiscovery;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
...@@ -44,7 +47,10 @@ namespace Microsoft.Extensions.DependencyInjection ...@@ -44,7 +47,10 @@ namespace Microsoft.Extensions.DependencyInjection
public static CapOptions UseDiscovery(this CapOptions capOptions, Action<DiscoveryOptions> options) public static CapOptions UseDiscovery(this CapOptions capOptions, Action<DiscoveryOptions> options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
capOptions.RegisterExtension(new DiscoveryOptionsExtension(options)); capOptions.RegisterExtension(new DiscoveryOptionsExtension(options));
......
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; using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP.NodeDiscovery namespace DotNetCore.CAP.NodeDiscovery
...@@ -15,7 +18,9 @@ namespace DotNetCore.CAP.NodeDiscovery ...@@ -15,7 +18,9 @@ namespace DotNetCore.CAP.NodeDiscovery
public INodeDiscoveryProvider Create(DiscoveryOptions options) public INodeDiscoveryProvider Create(DiscoveryOptions options)
{ {
if (options == null) if (options == null)
{
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
}
return new ConsulNodeDiscoveryProvider(_loggerFactory, options); return new ConsulNodeDiscoveryProvider(_loggerFactory, options);
} }
......
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 internal interface IDiscoveryProviderFactory
{ {
......
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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -36,8 +39,7 @@ namespace DotNetCore.CAP.NodeDiscovery ...@@ -36,8 +39,7 @@ namespace DotNetCore.CAP.NodeDiscovery
foreach (var service in services.Response) foreach (var service in services.Response)
{ {
var serviceInfo = await _consul.Catalog.Service(service.Key); var serviceInfo = await _consul.Catalog.Service(service.Key);
var node = serviceInfo.Response. var node = serviceInfo.Response.SkipWhile(x => !x.ServiceTags.Contains("CAP"))
SkipWhile(x => !x.ServiceTags.Contains("CAP"))
.Select(info => new Node .Select(info => new Node
{ {
Id = info.ServiceID, Id = info.ServiceID,
...@@ -58,7 +60,8 @@ namespace DotNetCore.CAP.NodeDiscovery ...@@ -58,7 +60,8 @@ namespace DotNetCore.CAP.NodeDiscovery
{ {
CapCache.Global.AddOrUpdate("cap.nodes.count", 0, TimeSpan.FromSeconds(20)); 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; return null;
} }
} }
...@@ -73,7 +76,7 @@ namespace DotNetCore.CAP.NodeDiscovery ...@@ -73,7 +76,7 @@ namespace DotNetCore.CAP.NodeDiscovery
Name = _options.NodeName, Name = _options.NodeName,
Address = _options.CurrentNodeHostName, Address = _options.CurrentNodeHostName,
Port = _options.CurrentNodePort, Port = _options.CurrentNodePort,
Tags = new[] { "CAP", "Client", "Dashboard" }, Tags = new[] {"CAP", "Client", "Dashboard"},
Check = new AgentServiceCheck Check = new AgentServiceCheck
{ {
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(30), DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(30),
...@@ -86,7 +89,8 @@ namespace DotNetCore.CAP.NodeDiscovery ...@@ -86,7 +89,8 @@ namespace DotNetCore.CAP.NodeDiscovery
} }
catch (Exception ex) 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; return null;
} }
} }
......
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; using System.Threading.Tasks;
namespace DotNetCore.CAP.NodeDiscovery namespace DotNetCore.CAP.NodeDiscovery
......
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 internal class ConsulProcessingNodeServer : IProcessingServer
{ {
......
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 public class Node
{ {
......
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.Collections.Generic;
using System.Linq; using System.Linq;
...@@ -47,7 +50,10 @@ namespace DotNetCore.CAP ...@@ -47,7 +50,10 @@ namespace DotNetCore.CAP
{ {
var result = new OperateResult {Succeeded = false}; var result = new OperateResult {Succeeded = false};
if (errors != null) if (errors != null)
{
result._errors.AddRange(errors); result._errors.AddRange(errors);
}
return result; return result;
} }
...@@ -59,7 +65,10 @@ namespace DotNetCore.CAP ...@@ -59,7 +65,10 @@ namespace DotNetCore.CAP
Exception = ex Exception = ex
}; };
if (errors != null) if (errors != null)
{
result._errors.AddRange(errors); result._errors.AddRange(errors);
}
return result; return result;
} }
......
namespace DotNetCore.CAP.Processor // 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 IAdditionalProcessor : IProcessor public interface IAdditionalProcessor : IProcessor
{ {
......
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;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Models;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DotNetCore.CAP.Processor 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<Dispatcher> _logger;
private readonly BlockingCollection<CapPublishedMessage> _publishedMessageQueue =
new BlockingCollection<CapPublishedMessage>(new ConcurrentQueue<CapPublishedMessage>());
private readonly TimeSpan _pollingDelay; private readonly BlockingCollection<CapReceivedMessage> _receivedMessageQueue =
private readonly IQueueExecutorFactory _queueExecutorFactory; new BlockingCollection<CapReceivedMessage>(new ConcurrentQueue<CapReceivedMessage>());
public DefaultDispatcher(IQueueExecutorFactory queueExecutorFactory, private readonly IPublishMessageSender _sender;
IOptions<CapOptions> capOptions)
public Dispatcher(ILogger<Dispatcher> logger,
IPublishMessageSender sender,
ISubscriberExecutor executor)
{ {
_queueExecutorFactory = queueExecutorFactory; _logger = logger;
_pollingDelay = TimeSpan.FromSeconds(capOptions.Value.PollingDelay); _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) _publishedMessageQueue.Add(message);
throw new ArgumentNullException(nameof(context)); }
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 try
{ {
var worked = await Step(context); while (!_publishedMessageQueue.IsCompleted)
context.ThrowIfStopping();
Waiting = true;
if (!worked)
{ {
var token = GetTokenToWaitOn(context); if (_publishedMessageQueue.TryTake(out var message, 100, _cts.Token))
await WaitHandleEx.WaitAnyAsync(PulseEvent, token.WaitHandle, _pollingDelay); {
try
{
_sender.SendAsync(message);
}
catch (Exception ex)
{
_logger.ExceptionOccuredWhileExecuting(message.Name, ex);
}
}
} }
} }
finally catch (OperationCanceledException)
{ {
Waiting = false; // expected
} }
} }
protected virtual CancellationToken GetTokenToWaitOn(ProcessingContext context) private void Processing()
{
return context.CancellationToken;
}
private async Task<bool> Step(ProcessingContext context)
{ {
IFetchedMessage fetched; try
using (var scopedContext = context.CreateScope())
{ {
var provider = scopedContext.Provider; foreach (var message in _receivedMessageQueue.GetConsumingEnumerable(_cts.Token))
var connection = provider.GetRequiredService<IStorageConnection>(); {
try
if ((fetched = await connection.FetchNextMessageAsync()) != null) {
using (fetched) _executor.ExecuteAsync(message);
}
catch (Exception ex)
{ {
var queueExecutor = _queueExecutorFactory.GetInstance(fetched.MessageType); _logger.ExceptionOccuredWhileExecuting(message.Name, ex);
await queueExecutor.ExecuteAsync(connection, fetched);
} }
}
}
catch (OperationCanceledException)
{
// expected
} }
return fetched != null;
} }
} }
} }
\ No newline at end of file
namespace DotNetCore.CAP.Processor
{
public interface IDispatcher : IProcessor
{
bool Waiting { get; }
}
}
\ No newline at end of file
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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DotNetCore.CAP.Processor namespace DotNetCore.CAP.Processor
{ {
...@@ -14,39 +16,30 @@ namespace DotNetCore.CAP.Processor ...@@ -14,39 +16,30 @@ namespace DotNetCore.CAP.Processor
private readonly CancellationTokenSource _cts; private readonly CancellationTokenSource _cts;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly IList<IDispatcher> _messageDispatchers;
private readonly CapOptions _options;
private readonly IServiceProvider _provider; private readonly IServiceProvider _provider;
private Task _compositeTask; private Task _compositeTask;
private ProcessingContext _context; private ProcessingContext _context;
private bool _disposed; private bool _disposed;
private IProcessor[] _processors;
public CapProcessingServer( public CapProcessingServer(
ILogger<CapProcessingServer> logger, ILogger<CapProcessingServer> logger,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IServiceProvider provider, IServiceProvider provider)
IOptions<CapOptions> options)
{ {
_logger = logger; _logger = logger;
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_provider = provider; _provider = provider;
_options = options.Value;
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
_messageDispatchers = new List<IDispatcher>();
} }
public void Start() public void Start()
{ {
var processorCount = _options.QueueProcessorCount; _logger.ServerStarting();
_processors = GetProcessors(processorCount);
_logger.ServerStarting(processorCount, _processors.Length);
_context = new ProcessingContext(_provider, _cts.Token); _context = new ProcessingContext(_provider, _cts.Token);
var processorTasks = _processors var processorTasks = GetProcessors()
.Select(InfiniteRetry) .Select(InfiniteRetry)
.Select(p => p.ProcessAsync(_context)); .Select(p => p.ProcessAsync(_context));
_compositeTask = Task.WhenAll(processorTasks); _compositeTask = Task.WhenAll(processorTasks);
...@@ -54,18 +47,16 @@ namespace DotNetCore.CAP.Processor ...@@ -54,18 +47,16 @@ namespace DotNetCore.CAP.Processor
public void Pulse() public void Pulse()
{ {
if (!AllProcessorsWaiting()) _logger.LogTrace("Pulsing the processor.");
return;
_logger.LogTrace("Pulsing the Queuer.");
PublishQueuer.PulseEvent.Set();
} }
public void Dispose() public void Dispose()
{ {
if (_disposed) if (_disposed)
{
return; return;
}
_disposed = true; _disposed = true;
_logger.ServerShuttingDown(); _logger.ServerShuttingDown();
...@@ -78,35 +69,24 @@ namespace DotNetCore.CAP.Processor ...@@ -78,35 +69,24 @@ namespace DotNetCore.CAP.Processor
{ {
var innerEx = ex.InnerExceptions[0]; var innerEx = ex.InnerExceptions[0];
if (!(innerEx is OperationCanceledException)) if (!(innerEx is OperationCanceledException))
{
_logger.ExpectedOperationCanceledException(innerEx); _logger.ExpectedOperationCanceledException(innerEx);
}
} }
} }
private bool AllProcessorsWaiting()
{
return _messageDispatchers.All(processor => processor.Waiting);
}
private IProcessor InfiniteRetry(IProcessor inner) private IProcessor InfiniteRetry(IProcessor inner)
{ {
return new InfiniteRetryProcessor(inner, _loggerFactory); return new InfiniteRetryProcessor(inner, _loggerFactory);
} }
private IProcessor[] GetProcessors(int processorCount) private IProcessor[] GetProcessors()
{ {
var returnedProcessors = new List<IProcessor>(); var returnedProcessors = new List<IProcessor>
for (var i = 0; i < processorCount; i++)
{ {
var messageProcessors = _provider.GetRequiredService<IDispatcher>(); _provider.GetRequiredService<NeedRetryMessageProcessor>(),
_messageDispatchers.Add(messageProcessors); _provider.GetRequiredService<IAdditionalProcessor>()
} };
returnedProcessors.AddRange(_messageDispatchers);
returnedProcessors.Add(_provider.GetRequiredService<PublishQueuer>());
returnedProcessors.Add(_provider.GetRequiredService<SubscribeQueuer>());
returnedProcessors.Add(_provider.GetRequiredService<FailedProcessor>());
returnedProcessors.Add(_provider.GetRequiredService<IAdditionalProcessor>());
return returnedProcessors.ToArray(); return returnedProcessors.ToArray();
} }
......
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 System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
...@@ -20,6 +23,7 @@ namespace DotNetCore.CAP.Processor ...@@ -20,6 +23,7 @@ namespace DotNetCore.CAP.Processor
public async Task ProcessAsync(ProcessingContext context) public async Task ProcessAsync(ProcessingContext context)
{ {
while (!context.IsStopping) while (!context.IsStopping)
{
try try
{ {
await _inner.ProcessAsync(context); await _inner.ProcessAsync(context);
...@@ -32,6 +36,7 @@ namespace DotNetCore.CAP.Processor ...@@ -32,6 +36,7 @@ namespace DotNetCore.CAP.Processor
{ {
_logger.LogWarning(1, ex, "Processor '{ProcessorName}' failed. Retrying...", _inner.ToString()); _logger.LogWarning(1, ex, "Processor '{ProcessorName}' failed. Retrying...", _inner.ToString());
} }
}
} }
public override string ToString() public override string ToString()
......
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 System.Threading.Tasks;
using DotNetCore.CAP.Abstractions;
using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models; using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor.States; using DotNetCore.CAP.Processor.States;
...@@ -10,28 +12,25 @@ using Microsoft.Extensions.Options; ...@@ -10,28 +12,25 @@ using Microsoft.Extensions.Options;
namespace DotNetCore.CAP.Processor namespace DotNetCore.CAP.Processor
{ {
public class FailedProcessor : IProcessor public class NeedRetryMessageProcessor : IProcessor
{ {
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); private readonly TimeSpan _delay = TimeSpan.FromSeconds(1);
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly CapOptions _options; private readonly CapOptions _options;
private readonly IServiceProvider _provider; private readonly IPublishExecutor _publishExecutor;
private readonly IStateChanger _stateChanger; private readonly IStateChanger _stateChanger;
private readonly ISubscriberExecutor _subscriberExecutor; private readonly ISubscriberExecutor _subscriberExecutor;
private readonly IPublishExecutor _publishExecutor;
private readonly TimeSpan _waitingInterval; private readonly TimeSpan _waitingInterval;
public FailedProcessor( public NeedRetryMessageProcessor(
IOptions<CapOptions> options, IOptions<CapOptions> options,
ILogger<FailedProcessor> logger, ILogger<NeedRetryMessageProcessor> logger,
IServiceProvider provider,
IStateChanger stateChanger, IStateChanger stateChanger,
ISubscriberExecutor subscriberExecutor, ISubscriberExecutor subscriberExecutor,
IPublishExecutor publishExecutor) IPublishExecutor publishExecutor)
{ {
_options = options.Value; _options = options.Value;
_logger = logger; _logger = logger;
_provider = provider;
_stateChanger = stateChanger; _stateChanger = stateChanger;
_subscriberExecutor = subscriberExecutor; _subscriberExecutor = subscriberExecutor;
_publishExecutor = publishExecutor; _publishExecutor = publishExecutor;
...@@ -41,32 +40,33 @@ namespace DotNetCore.CAP.Processor ...@@ -41,32 +40,33 @@ namespace DotNetCore.CAP.Processor
public async Task ProcessAsync(ProcessingContext context) public async Task ProcessAsync(ProcessingContext context)
{ {
if (context == null) if (context == null)
{
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
}
using (var scope = _provider.CreateScope()) var connection = context.Provider.GetRequiredService<IStorageConnection>();
{
var provider = scope.ServiceProvider;
var connection = provider.GetRequiredService<IStorageConnection>();
await Task.WhenAll( await Task.WhenAll(
ProcessPublishedAsync(connection, context), ProcessPublishedAsync(connection, context),
ProcessReceivedAsync(connection, context)); ProcessReceivedAsync(connection, context));
await context.WaitAsync(_waitingInterval); await context.WaitAsync(_waitingInterval);
}
} }
private async Task ProcessPublishedAsync(IStorageConnection connection, ProcessingContext context) private async Task ProcessPublishedAsync(IStorageConnection connection, ProcessingContext context)
{ {
var messages = await connection.GetFailedPublishedMessages(); var messages = await connection.GetPublishedMessagesOfNeedRetry();
var hasException = false; var hasException = false;
foreach (var message in messages) foreach (var message in messages)
{ {
if (message.Retries > _options.FailedRetryCount) if (message.Retries > _options.FailedRetryCount)
{
continue; continue;
}
if (!hasException) if (!hasException)
{
try try
{ {
_options.FailedCallback?.Invoke(MessageType.Publish, message.Name, message.Content); _options.FailedCallback?.Invoke(MessageType.Publish, message.Name, message.Content);
...@@ -76,6 +76,7 @@ namespace DotNetCore.CAP.Processor ...@@ -76,6 +76,7 @@ namespace DotNetCore.CAP.Processor
hasException = true; hasException = true;
_logger.LogWarning("Failed call-back method raised an exception:" + ex.Message); _logger.LogWarning("Failed call-back method raised an exception:" + ex.Message);
} }
}
using (var transaction = connection.CreateTransaction()) using (var transaction = connection.CreateTransaction())
{ {
...@@ -88,9 +89,9 @@ namespace DotNetCore.CAP.Processor ...@@ -88,9 +89,9 @@ namespace DotNetCore.CAP.Processor
catch (Exception e) catch (Exception e)
{ {
message.Content = Helper.AddExceptionProperty(message.Content, e); message.Content = Helper.AddExceptionProperty(message.Content, e);
message.Retries++;
transaction.UpdateMessage(message); transaction.UpdateMessage(message);
} }
await transaction.CommitAsync(); await transaction.CommitAsync();
} }
...@@ -102,15 +103,18 @@ namespace DotNetCore.CAP.Processor ...@@ -102,15 +103,18 @@ namespace DotNetCore.CAP.Processor
private async Task ProcessReceivedAsync(IStorageConnection connection, ProcessingContext context) private async Task ProcessReceivedAsync(IStorageConnection connection, ProcessingContext context)
{ {
var messages = await connection.GetFailedReceivedMessages(); var messages = await connection.GetReceivedMessagesOfNeedRetry();
var hasException = false; var hasException = false;
foreach (var message in messages) foreach (var message in messages)
{ {
if (message.Retries > _options.FailedRetryCount) if (message.Retries > _options.FailedRetryCount)
{
continue; continue;
}
if (!hasException) if (!hasException)
{
try try
{ {
_options.FailedCallback?.Invoke(MessageType.Subscribe, message.Name, message.Content); _options.FailedCallback?.Invoke(MessageType.Subscribe, message.Name, message.Content);
...@@ -120,23 +124,10 @@ namespace DotNetCore.CAP.Processor ...@@ -120,23 +124,10 @@ namespace DotNetCore.CAP.Processor
hasException = true; hasException = true;
_logger.LogWarning("Failed call-back method raised an exception:" + ex.Message); _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();
} }
await _subscriberExecutor.ExecuteAsync(message);
context.ThrowIfStopping(); context.ThrowIfStopping();
await context.WaitAsync(_delay); await context.WaitAsync(_delay);
......
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<PublishQueuer> logger,
IOptions<CapOptions> 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<IStorageConnection>();
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
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<SubscribeQueuer> logger,
IOptions<CapOptions> 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<IStorageConnection>();
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
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 namespace DotNetCore.CAP.Processor
{ {
......
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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
......
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 namespace DotNetCore.CAP.Processor
{ {
...@@ -17,9 +20,9 @@ namespace DotNetCore.CAP.Processor ...@@ -17,9 +20,9 @@ namespace DotNetCore.CAP.Processor
static RetryBehavior() static RetryBehavior()
{ {
DefaultRetryCount = 15; DefaultRetryCount = 3;
DefaultRetryInThunk = retries => 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); DefaultRetry = new RetryBehavior(true);
NoRetry = new RetryBehavior(false); NoRetry = new RetryBehavior(false);
...@@ -39,7 +42,12 @@ namespace DotNetCore.CAP.Processor ...@@ -39,7 +42,12 @@ namespace DotNetCore.CAP.Processor
public RetryBehavior(bool retry, int retryCount, Func<int, int> retryInThunk) public RetryBehavior(bool retry, int retryCount, Func<int, int> retryInThunk)
{ {
if (retry) 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; Retry = retry;
RetryCount = retryCount; RetryCount = retryCount;
......
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
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; using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Processor.States namespace DotNetCore.CAP.Processor.States
......
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
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; using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Processor.States namespace DotNetCore.CAP.Processor.States
......
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; using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Processor.States namespace DotNetCore.CAP.Processor.States
......
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; using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Processor.States namespace DotNetCore.CAP.Processor.States
......
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; using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Processor.States namespace DotNetCore.CAP.Processor.States
...@@ -9,9 +12,13 @@ namespace DotNetCore.CAP.Processor.States ...@@ -9,9 +12,13 @@ namespace DotNetCore.CAP.Processor.States
{ {
var now = DateTime.Now; var now = DateTime.Now;
if (state.ExpiresAfter != null) if (state.ExpiresAfter != null)
{
message.ExpiresAt = now.Add(state.ExpiresAfter.Value); message.ExpiresAt = now.Add(state.ExpiresAfter.Value);
}
else else
{
message.ExpiresAt = null; message.ExpiresAt = null;
}
message.StatusName = state.Name; message.StatusName = state.Name;
state.Apply(message, transaction); state.Apply(message, transaction);
...@@ -22,9 +29,13 @@ namespace DotNetCore.CAP.Processor.States ...@@ -22,9 +29,13 @@ namespace DotNetCore.CAP.Processor.States
{ {
var now = DateTime.Now; var now = DateTime.Now;
if (state.ExpiresAfter != null) if (state.ExpiresAfter != null)
{
message.ExpiresAt = now.Add(state.ExpiresAfter.Value); message.ExpiresAt = now.Add(state.ExpiresAfter.Value);
}
else else
{
message.ExpiresAt = null; message.ExpiresAt = null;
}
message.StatusName = state.Name; message.StatusName = state.Name;
state.Apply(message, transaction); state.Apply(message, transaction);
......
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; using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Processor.States namespace DotNetCore.CAP.Processor.States
......
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 namespace DotNetCore.CAP.Processor.States
{ {
......
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")] [assembly: InternalsVisibleTo("DotNetCore.CAP.Test")]
\ No newline at end of file
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<IQueueExecutor>();
return messageType == MessageType.Publish
? queueExecutors.FirstOrDefault(x => x is BasePublishQueueExecutor)
: queueExecutors.FirstOrDefault(x => !(x is BasePublishQueueExecutor));
}
}
}
\ No newline at end of file
using System.Threading; using System.Threading;
using Dapper; using Dapper;
using Microsoft.EntityFrameworkCore;
namespace DotNetCore.CAP.MySql.Test namespace DotNetCore.CAP.MySql.Test
{ {
...@@ -59,8 +58,7 @@ CREATE DATABASE `{databaseName}`;"); ...@@ -59,8 +58,7 @@ CREATE DATABASE `{databaseName}`;");
{ {
connection.Execute($@" connection.Execute($@"
TRUNCATE TABLE `cap.published`; TRUNCATE TABLE `cap.published`;
TRUNCATE TABLE `cap.received`; TRUNCATE TABLE `cap.received`;");
TRUNCATE TABLE `cap.queue`;");
} }
} }
} }
......
...@@ -15,19 +15,19 @@ ...@@ -15,19 +15,19 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="1.50.4" /> <PackageReference Include="Dapper" Version="1.50.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.2" />
<PackageReference Include="MySqlConnector" Version="0.36.0" /> <PackageReference Include="MySqlConnector" Version="0.38.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.8.2" /> <PackageReference Include="Moq" Version="4.8.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
\ No newline at end of file
...@@ -40,27 +40,6 @@ namespace DotNetCore.CAP.MySql.Test ...@@ -40,27 +40,6 @@ namespace DotNetCore.CAP.MySql.Test
Assert.Equal(StatusName.Scheduled, message.StatusName); 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] [Fact]
public async Task StoreReceivedMessageAsync_Test() public async Task StoreReceivedMessageAsync_Test()
{ {
...@@ -110,25 +89,5 @@ namespace DotNetCore.CAP.MySql.Test ...@@ -110,25 +89,5 @@ namespace DotNetCore.CAP.MySql.Test
Assert.Equal("MySqlStorageConnectionTest", message.Name); Assert.Equal("MySqlStorageConnectionTest", message.Name);
Assert.Equal("mygroup", message.Group); 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
...@@ -30,7 +30,6 @@ namespace DotNetCore.CAP.MySql.Test ...@@ -30,7 +30,6 @@ namespace DotNetCore.CAP.MySql.Test
[Theory] [Theory]
[InlineData("cap.published")] [InlineData("cap.published")]
[InlineData("cap.queue")]
[InlineData("cap.received")] [InlineData("cap.received")]
public void DatabaseTable_IsExists(string tableName) public void DatabaseTable_IsExists(string tableName)
{ {
......
...@@ -59,8 +59,7 @@ CREATE DATABASE ""{databaseName}"";"); ...@@ -59,8 +59,7 @@ CREATE DATABASE ""{databaseName}"";");
{ {
connection.Execute($@" connection.Execute($@"
TRUNCATE TABLE ""cap"".""published""; TRUNCATE TABLE ""cap"".""published"";
TRUNCATE TABLE ""cap"".""received""; TRUNCATE TABLE ""cap"".""received"";");
TRUNCATE TABLE ""cap"".""queue"";");
} }
} }
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="1.50.4" /> <PackageReference Include="Dapper" Version="1.50.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.2" />
<PackageReference Include="Npgsql" Version="3.2.7" /> <PackageReference Include="Npgsql" Version="3.2.7" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
......
...@@ -40,26 +40,6 @@ namespace DotNetCore.CAP.PostgreSql.Test ...@@ -40,26 +40,6 @@ namespace DotNetCore.CAP.PostgreSql.Test
Assert.Equal(StatusName.Scheduled, message.StatusName); 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] [Fact]
public async Task StoreReceivedMessageAsync_Test() public async Task StoreReceivedMessageAsync_Test()
{ {
...@@ -109,25 +89,5 @@ namespace DotNetCore.CAP.PostgreSql.Test ...@@ -109,25 +89,5 @@ namespace DotNetCore.CAP.PostgreSql.Test
Assert.Equal("PostgreSqlStorageConnectionTest", message.Name); Assert.Equal("PostgreSqlStorageConnectionTest", message.Name);
Assert.Equal("mygroup", message.Group); 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
...@@ -32,7 +32,6 @@ namespace DotNetCore.CAP.PostgreSql.Test ...@@ -32,7 +32,6 @@ namespace DotNetCore.CAP.PostgreSql.Test
[Theory] [Theory]
[InlineData("cap.published")] [InlineData("cap.published")]
[InlineData("cap.queue")]
[InlineData("cap.received")] [InlineData("cap.received")]
public void DatabaseTable_IsExists(string tableName) public void DatabaseTable_IsExists(string tableName)
{ {
......
...@@ -2,7 +2,6 @@ using System.Data; ...@@ -2,7 +2,6 @@ using System.Data;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Threading; using System.Threading;
using Dapper; using Dapper;
using Microsoft.EntityFrameworkCore;
namespace DotNetCore.CAP.SqlServer.Test namespace DotNetCore.CAP.SqlServer.Test
{ {
......
...@@ -12,19 +12,19 @@ ...@@ -12,19 +12,19 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="1.50.4" /> <PackageReference Include="Dapper" Version="1.50.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.2" />
<PackageReference Include="System.Data.SqlClient" Version="4.4.2" /> <PackageReference Include="System.Data.SqlClient" Version="4.4.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.8.2" /> <PackageReference Include="Moq" Version="4.8.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
...@@ -39,27 +39,7 @@ namespace DotNetCore.CAP.SqlServer.Test ...@@ -39,27 +39,7 @@ namespace DotNetCore.CAP.SqlServer.Test
Assert.Equal("SqlServerStorageConnectionTest", message.Name); Assert.Equal("SqlServerStorageConnectionTest", message.Name);
Assert.Equal(StatusName.Scheduled, message.StatusName); 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] [Fact]
public async Task StoreReceivedMessageAsync_Test() public async Task StoreReceivedMessageAsync_Test()
{ {
...@@ -109,25 +89,5 @@ namespace DotNetCore.CAP.SqlServer.Test ...@@ -109,25 +89,5 @@ namespace DotNetCore.CAP.SqlServer.Test
Assert.Equal("SqlServerStorageConnectionTest", message.Name); Assert.Equal("SqlServerStorageConnectionTest", message.Name);
Assert.Equal("mygroup", message.Group); 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
...@@ -25,7 +25,6 @@ SELECT 'False'"; ...@@ -25,7 +25,6 @@ SELECT 'False'";
[Theory] [Theory]
[InlineData("[Cap].[Published]")] [InlineData("[Cap].[Published]")]
[InlineData("[Cap].[Queue]")]
[InlineData("[Cap].[Received]")] [InlineData("[Cap].[Received]")]
public void DatabaseTable_IsExists(string tableName) public void DatabaseTable_IsExists(string tableName)
{ {
......
...@@ -36,7 +36,7 @@ namespace DotNetCore.CAP.Test ...@@ -36,7 +36,7 @@ namespace DotNetCore.CAP.Test
{ {
// Arrange // Arrange
_mockCallbackPublisher _mockCallbackPublisher
.Setup(x => x.PublishAsync(It.IsAny<CapPublishedMessage>())) .Setup(x => x.PublishCallbackAsync(It.IsAny<CapPublishedMessage>()))
.Returns(Task.CompletedTask).Verifiable(); .Returns(Task.CompletedTask).Verifiable();
_mockContentSerializer _mockContentSerializer
......
...@@ -8,14 +8,14 @@ ...@@ -8,14 +8,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.2" />
<PackageReference Include="System.Data.Common" Version="4.3.0" /> <PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.8.2" /> <PackageReference Include="Moq" Version="4.8.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
using System; //using System;
using System.Threading; //using System.Threading;
using System.Threading.Tasks; //using System.Threading.Tasks;
using DotNetCore.CAP.Models; //using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor; //using DotNetCore.CAP.Processor;
using Microsoft.Extensions.DependencyInjection; //using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; //using Microsoft.Extensions.Options;
using Moq; //using Moq;
using Xunit; //using Xunit;
namespace DotNetCore.CAP.Test //namespace DotNetCore.CAP.Test
{ //{
public class DefaultDispatcherTest // public class DefaultDispatcherTest
{ // {
private CancellationTokenSource _cancellationTokenSource; // private CancellationTokenSource _cancellationTokenSource;
private ProcessingContext _context; // private ProcessingContext _context;
private IServiceProvider _provider; // private IServiceProvider _provider;
private Mock<IStorageConnection> _mockStorageConnection; // private Mock<IStorageConnection> _mockStorageConnection;
private Mock<IQueueExecutorFactory> _mockQueueExecutorFactory;
private Mock<IQueueExecutor> _mockQueueExecutor;
public DefaultDispatcherTest() // public DefaultDispatcherTest()
{ // {
_mockStorageConnection = new Mock<IStorageConnection>(); // _mockStorageConnection = new Mock<IStorageConnection>();
_mockQueueExecutorFactory = new Mock<IQueueExecutorFactory>();
_mockQueueExecutor = new Mock<IQueueExecutor>(); // _cancellationTokenSource = new CancellationTokenSource();
_mockQueueExecutorFactory.Setup(x => x.GetInstance(MessageType.Publish)).Returns(_mockQueueExecutor.Object);
_cancellationTokenSource = new CancellationTokenSource();
var services = new ServiceCollection(); // var services = new ServiceCollection();
services.AddTransient<DefaultDispatcher>(); // services.AddLogging();
services.AddLogging(); // services.Configure<IOptions<CapOptions>>(x => { });
services.Configure<IOptions<CapOptions>>(x => { }); // services.AddOptions();
services.AddOptions(); // services.AddSingleton(_mockStorageConnection.Object);
services.AddSingleton(_mockStorageConnection.Object); // _provider = services.BuildServiceProvider();
services.AddSingleton(_mockQueueExecutorFactory.Object);
_provider = services.BuildServiceProvider();
_context = new ProcessingContext(_provider, _cancellationTokenSource.Token); // _context = new ProcessingContext(_provider, _cancellationTokenSource.Token);
} // }
[Fact] // [Fact]
public void MockTest() // public void MockTest()
{ // {
Assert.NotNull(_provider.GetServices<IStorageConnection>()); // Assert.NotNull(_provider.GetServices<IStorageConnection>());
} // }
[Fact] // [Fact]
public async void ProcessAsync_CancellationTokenCancelled_ThrowsImmediately() // public async void ProcessAsync_CancellationTokenCancelled_ThrowsImmediately()
{ // {
// Arrange // // Arrange
_cancellationTokenSource.Cancel(); // _cancellationTokenSource.Cancel();
var fixture = Create(); // var fixture = Create();
// Act // // Act
await Assert.ThrowsAsync<OperationCanceledException>(() => fixture.ProcessAsync(_context)); // await Assert.ThrowsAsync<OperationCanceledException>(() => fixture.ProcessAsync(_context));
} // }
[Fact] // [Fact]
public async Task ProcessAsync() // public async Task ProcessAsync()
{ // {
// Arrange // // Arrange
var job = new CapPublishedMessage // var job = new CapPublishedMessage
{ // {
}; // };
var mockFetchedJob = Mock.Get(Mock.Of<IFetchedMessage>(fj => fj.MessageId == 42 && fj.MessageType == MessageType.Publish)); // var mockFetchedJob = Mock.Get(Mock.Of<IFetchedMessage>(fj => fj.MessageId == 42 && fj.MessageType == MessageType.Publish));
_mockStorageConnection // _mockStorageConnection
.Setup(m => m.FetchNextMessageAsync()) // .Setup(m => m.FetchNextMessageAsync())
.ReturnsAsync(mockFetchedJob.Object).Verifiable(); // .ReturnsAsync(mockFetchedJob.Object).Verifiable();
_mockQueueExecutor // _mockQueueExecutor
.Setup(x => x.ExecuteAsync(_mockStorageConnection.Object, mockFetchedJob.Object)) // .Setup(x => x.ExecuteAsync(_mockStorageConnection.Object, mockFetchedJob.Object))
.Returns(Task.FromResult(OperateResult.Success)); // .Returns(Task.FromResult(OperateResult.Success));
var fixture = Create(); // var fixture = Create();
// Act // // Act
await fixture.ProcessAsync(_context); // await fixture.ProcessAsync(_context);
// Assert // // Assert
_mockStorageConnection.VerifyAll(); // _mockStorageConnection.VerifyAll();
} // }
private DefaultDispatcher Create() // private DefaultDispatcher Create()
=> _provider.GetService<DefaultDispatcher>(); // => _provider.GetService<DefaultDispatcher>();
} // }
} //}
\ No newline at end of file \ No newline at end of file
...@@ -16,7 +16,7 @@ namespace DotNetCore.CAP.Test ...@@ -16,7 +16,7 @@ namespace DotNetCore.CAP.Test
var fixture = Create(); var fixture = Create();
var message = new CapPublishedMessage var message = new CapPublishedMessage
{ {
StatusName = StatusName.Enqueued StatusName = StatusName.Scheduled
}; };
var state = Mock.Of<IState>(s => s.Name == "s" && s.ExpiresAfter == null); var state = Mock.Of<IState>(s => s.Name == "s" && s.ExpiresAfter == null);
var mockTransaction = new Mock<IStorageTransaction>(); var mockTransaction = new Mock<IStorageTransaction>();
...@@ -39,7 +39,7 @@ namespace DotNetCore.CAP.Test ...@@ -39,7 +39,7 @@ namespace DotNetCore.CAP.Test
var fixture = Create(); var fixture = Create();
var message = new CapPublishedMessage var message = new CapPublishedMessage
{ {
StatusName = StatusName.Enqueued StatusName = StatusName.Scheduled
}; };
var state = Mock.Of<IState>(s => s.Name == "s" && s.ExpiresAfter == TimeSpan.FromHours(1)); var state = Mock.Of<IState>(s => s.Name == "s" && s.ExpiresAfter == TimeSpan.FromHours(1));
var mockTransaction = new Mock<IStorageTransaction>(); var mockTransaction = new Mock<IStorageTransaction>();
......
using System; //using System;
using DotNetCore.CAP.Internal; //using DotNetCore.CAP.Internal;
using Microsoft.Extensions.DependencyInjection; //using Microsoft.Extensions.DependencyInjection;
using Xunit; //using Xunit;
using Moq; //using Moq;
namespace DotNetCore.CAP.Test //namespace DotNetCore.CAP.Test
{ //{
public class QueueExecutorFactoryTest // public class QueueExecutorFactoryTest
{ // {
private IServiceProvider _provider; // private IServiceProvider _provider;
public QueueExecutorFactoryTest() // public QueueExecutorFactoryTest()
{ // {
var services = new ServiceCollection(); // var services = new ServiceCollection();
services.AddLogging(); // services.AddLogging();
services.AddOptions(); // services.AddOptions();
services.AddCap(x => { }); // services.AddCap(x => { });
_provider = services.BuildServiceProvider(); // _provider = services.BuildServiceProvider();
} // }
[Fact] // [Fact]
public void CanCreateInstance() // public void CanCreateInstance()
{ // {
var queueExecutorFactory = _provider.GetService<IQueueExecutorFactory>(); // var queueExecutorFactory = _provider.GetService<IQueueExecutorFactory>();
Assert.NotNull(queueExecutorFactory); // Assert.NotNull(queueExecutorFactory);
var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish); // var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish);
Assert.Null(publishExecutor); // Assert.Null(publishExecutor);
var disPatchExector = queueExecutorFactory.GetInstance(Models.MessageType.Subscribe); // var disPatchExector = queueExecutorFactory.GetInstance(Models.MessageType.Subscribe);
Assert.NotNull(disPatchExector); // Assert.NotNull(disPatchExector);
} // }
[Fact] // [Fact]
public void CanGetSubscribeExector() // public void CanGetSubscribeExector()
{ // {
var queueExecutorFactory = _provider.GetService<IQueueExecutorFactory>(); // var queueExecutorFactory = _provider.GetService<IQueueExecutorFactory>();
Assert.NotNull(queueExecutorFactory); // Assert.NotNull(queueExecutorFactory);
var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish); // var publishExecutor = queueExecutorFactory.GetInstance(Models.MessageType.Publish);
Assert.Null(publishExecutor); // Assert.Null(publishExecutor);
} // }
} // }
} //}
\ No newline at end of file \ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment