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