@@ -51,8 +51,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.AzureService | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Dashboard", "src\DotNetCore.CAP.Dashboard\DotNetCore.CAP.Dashboard.csproj", "{56FB261C-67AF-4715-9A46-4FA4FAB91B2C}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Dashboard", "src\DotNetCore.CAP.Dashboard\DotNetCore.CAP.Dashboard.csproj", "{56FB261C-67AF-4715-9A46-4FA4FAB91B2C}" | ||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.InMemory", "samples\Sample.Kafka.InMemory\Sample.Kafka.InMemory.csproj", "{1B0371D6-36A4-4C78-A727-8ED732FDBA1D}" | |||||
EndProject | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.SqlServer", "samples\Sample.RabbitMQ.SqlServer\Sample.RabbitMQ.SqlServer.csproj", "{F6C5C676-AF05-46D5-A45D-442137B31898}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.SqlServer", "samples\Sample.RabbitMQ.SqlServer\Sample.RabbitMQ.SqlServer.csproj", "{F6C5C676-AF05-46D5-A45D-442137B31898}" | ||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.PostgreSql", "samples\Sample.Kafka.PostgreSql\Sample.Kafka.PostgreSql.csproj", "{F1EF1D26-8A6B-403E-85B0-250DF44A4A7C}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.PostgreSql", "samples\Sample.Kafka.PostgreSql\Sample.Kafka.PostgreSql.csproj", "{F1EF1D26-8A6B-403E-85B0-250DF44A4A7C}" | ||||
@@ -67,9 +65,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.ConsoleApp", "sample | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.AmazonSQS", "src\DotNetCore.CAP.AmazonSQS\DotNetCore.CAP.AmazonSQS.csproj", "{43475E00-51B7-443D-BC2D-FC21F9D8A0B4}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.AmazonSQS", "src\DotNetCore.CAP.AmazonSQS\DotNetCore.CAP.AmazonSQS.csproj", "{43475E00-51B7-443D-BC2D-FC21F9D8A0B4}" | ||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Pulsar", "src\DotNetCore.CAP.Pulsar\DotNetCore.CAP.Pulsar.csproj", "{73408EA6-1025-463C-88BC-A20769E44BC4}" | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.AmazonSQS.InMemory", "samples\Sample.AmazonSQS.InMemory\Sample.AmazonSQS.InMemory.csproj", "{B187DD15-092D-4B72-9807-50856607D237}" | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Pulsar.InMemory", "samples\Sample.Pulsar.InMemory\Sample.Pulsar.InMemory.csproj", "{AFF0A34A-F938-4F75-9A96-9FC3DC0BF6DF}" | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Pulsar", "src\DotNetCore.CAP.Pulsar\DotNetCore.CAP.Pulsar.csproj", "{33C48DD1-5B7D-475B-B849-FFE1D9A4FBD1}" | |||||
EndProject | EndProject | ||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
@@ -125,10 +123,6 @@ Global | |||||
{56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU | {56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
{56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU | {56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
{56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Release|Any CPU.Build.0 = Release|Any CPU | {56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
{1B0371D6-36A4-4C78-A727-8ED732FDBA1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{1B0371D6-36A4-4C78-A727-8ED732FDBA1D}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{1B0371D6-36A4-4C78-A727-8ED732FDBA1D}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{1B0371D6-36A4-4C78-A727-8ED732FDBA1D}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{F6C5C676-AF05-46D5-A45D-442137B31898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | {F6C5C676-AF05-46D5-A45D-442137B31898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
{F6C5C676-AF05-46D5-A45D-442137B31898}.Debug|Any CPU.Build.0 = Debug|Any CPU | {F6C5C676-AF05-46D5-A45D-442137B31898}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
{F6C5C676-AF05-46D5-A45D-442137B31898}.Release|Any CPU.ActiveCfg = Release|Any CPU | {F6C5C676-AF05-46D5-A45D-442137B31898}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
@@ -157,14 +151,14 @@ Global | |||||
{43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Debug|Any CPU.Build.0 = Debug|Any CPU | {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
{43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Release|Any CPU.ActiveCfg = Release|Any CPU | {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
{43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Release|Any CPU.Build.0 = Release|Any CPU | {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
{73408EA6-1025-463C-88BC-A20769E44BC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{73408EA6-1025-463C-88BC-A20769E44BC4}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{73408EA6-1025-463C-88BC-A20769E44BC4}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{73408EA6-1025-463C-88BC-A20769E44BC4}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{AFF0A34A-F938-4F75-9A96-9FC3DC0BF6DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{AFF0A34A-F938-4F75-9A96-9FC3DC0BF6DF}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{AFF0A34A-F938-4F75-9A96-9FC3DC0BF6DF}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{AFF0A34A-F938-4F75-9A96-9FC3DC0BF6DF}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{B187DD15-092D-4B72-9807-50856607D237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{B187DD15-092D-4B72-9807-50856607D237}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{B187DD15-092D-4B72-9807-50856607D237}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{B187DD15-092D-4B72-9807-50856607D237}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{33C48DD1-5B7D-475B-B849-FFE1D9A4FBD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{33C48DD1-5B7D-475B-B849-FFE1D9A4FBD1}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{33C48DD1-5B7D-475B-B849-FFE1D9A4FBD1}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{33C48DD1-5B7D-475B-B849-FFE1D9A4FBD1}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -182,7 +176,6 @@ Global | |||||
{4473DE19-E8D2-4B57-80A8-C8AAA2BFA20F} = {3A6B6931-A123-477A-9469-8B468B5385AF} | {4473DE19-E8D2-4B57-80A8-C8AAA2BFA20F} = {3A6B6931-A123-477A-9469-8B468B5385AF} | ||||
{63B2A464-FBEA-42FB-8EFA-98AFA39FC920} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | {63B2A464-FBEA-42FB-8EFA-98AFA39FC920} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | ||||
{56FB261C-67AF-4715-9A46-4FA4FAB91B2C} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | {56FB261C-67AF-4715-9A46-4FA4FAB91B2C} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | ||||
{1B0371D6-36A4-4C78-A727-8ED732FDBA1D} = {3A6B6931-A123-477A-9469-8B468B5385AF} | |||||
{F6C5C676-AF05-46D5-A45D-442137B31898} = {3A6B6931-A123-477A-9469-8B468B5385AF} | {F6C5C676-AF05-46D5-A45D-442137B31898} = {3A6B6931-A123-477A-9469-8B468B5385AF} | ||||
{F1EF1D26-8A6B-403E-85B0-250DF44A4A7C} = {3A6B6931-A123-477A-9469-8B468B5385AF} | {F1EF1D26-8A6B-403E-85B0-250DF44A4A7C} = {3A6B6931-A123-477A-9469-8B468B5385AF} | ||||
{F8EF381A-FE83-40B3-A63D-09D83851B0FB} = {10C0818D-9160-4B80-BB86-DDE925B64D43} | {F8EF381A-FE83-40B3-A63D-09D83851B0FB} = {10C0818D-9160-4B80-BB86-DDE925B64D43} | ||||
@@ -190,8 +183,8 @@ Global | |||||
{75CC45E6-BF06-40F4-977D-10DCC05B2EFA} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} | {75CC45E6-BF06-40F4-977D-10DCC05B2EFA} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} | ||||
{2B0F467E-ABBD-4A51-BF38-D4F609DB6266} = {3A6B6931-A123-477A-9469-8B468B5385AF} | {2B0F467E-ABBD-4A51-BF38-D4F609DB6266} = {3A6B6931-A123-477A-9469-8B468B5385AF} | ||||
{43475E00-51B7-443D-BC2D-FC21F9D8A0B4} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | {43475E00-51B7-443D-BC2D-FC21F9D8A0B4} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | ||||
{73408EA6-1025-463C-88BC-A20769E44BC4} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | |||||
{AFF0A34A-F938-4F75-9A96-9FC3DC0BF6DF} = {3A6B6931-A123-477A-9469-8B468B5385AF} | |||||
{B187DD15-092D-4B72-9807-50856607D237} = {3A6B6931-A123-477A-9469-8B468B5385AF} | |||||
{33C48DD1-5B7D-475B-B849-FFE1D9A4FBD1} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(ExtensibilityGlobals) = postSolution | GlobalSection(ExtensibilityGlobals) = postSolution | ||||
SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} | SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} | ||||
@@ -2,7 +2,7 @@ | |||||
<img height="140" src="https://cap.dotnetcore.xyz/img/logo.svg"> | <img height="140" src="https://cap.dotnetcore.xyz/img/logo.svg"> | ||||
</p> | </p> | ||||
# CAP [中文](https://github.com/dotnetcore/CAP/blob/master/README.zh-cn.md) | |||||
# CAP [中文](https://github.com/dotnetcore/CAP/blob/master/README.zh-cn.md) | |||||
[![Travis branch](https://img.shields.io/travis/dotnetcore/CAP/master.svg?label=travis-ci)](https://travis-ci.org/dotnetcore/CAP) | [![Travis branch](https://img.shields.io/travis/dotnetcore/CAP/master.svg?label=travis-ci)](https://travis-ci.org/dotnetcore/CAP) | ||||
[![AppVeyor](https://ci.appveyor.com/api/projects/status/v8gfh6pe2u2laqoa/branch/master?svg=true)](https://ci.appveyor.com/project/yang-xiaodong/cap/branch/master) | [![AppVeyor](https://ci.appveyor.com/api/projects/status/v8gfh6pe2u2laqoa/branch/master?svg=true)](https://ci.appveyor.com/project/yang-xiaodong/cap/branch/master) | ||||
[![NuGet](https://img.shields.io/nuget/v/DotNetCore.CAP.svg)](https://www.nuget.org/packages/DotNetCore.CAP/) | [![NuGet](https://img.shields.io/nuget/v/DotNetCore.CAP.svg)](https://www.nuget.org/packages/DotNetCore.CAP/) | ||||
@@ -159,7 +159,7 @@ namespace BusinessCode.Service | |||||
{ | { | ||||
public interface ISubscriberService | public interface ISubscriberService | ||||
{ | { | ||||
public void CheckReceivedMessage(DateTime datetime); | |||||
void CheckReceivedMessage(DateTime datetime); | |||||
} | } | ||||
public class SubscriberService: ISubscriberService, ICapSubscribe | public class SubscriberService: ISubscriberService, ICapSubscribe | ||||
@@ -187,6 +187,21 @@ public void ConfigureServices(IServiceCollection services) | |||||
}); | }); | ||||
} | } | ||||
``` | ``` | ||||
#### Use partials for topic subscriptions | |||||
To group topic subscriptions on class level you're able to define a subscription on a method as a partial. Subscriptions on the message queue will then be a combination of the topic defined on the class and the topic defined on the method. In the following example the `Create(..)` function will be invoked when receiving a message on `customers.create` | |||||
```c# | |||||
[CapSubscribe("customers")] | |||||
public class CustomersSubscriberService : ICapSubscribe | |||||
{ | |||||
[CapSubscribe("create", isPartial: true)] | |||||
public void Create(Customer customer) | |||||
{ | |||||
} | |||||
} | |||||
``` | |||||
#### Subscribe Group | #### Subscribe Group | ||||
@@ -174,7 +174,7 @@ namespace xxx.Service | |||||
{ | { | ||||
public interface ISubscriberService | public interface ISubscriberService | ||||
{ | { | ||||
public void CheckReceivedMessage(DateTime datetime); | |||||
void CheckReceivedMessage(DateTime datetime); | |||||
} | } | ||||
public class SubscriberService: ISubscriberService, ICapSubscribe | public class SubscriberService: ISubscriberService, ICapSubscribe | ||||
@@ -32,7 +32,7 @@ The consumer method is executed when the Consumer receives the message and will | |||||
## Data Cleanup | ## Data Cleanup | ||||
There is an `ExpiresAt` field in the database message table indicating the expiration time of the message. When the message is sent successfully, status will be changed to `Successed`, and `ExpiresAt` will be set to **1 hour** later. | |||||
There is an `ExpiresAt` field in the database message table indicating the expiration time of the message. When the message is sent successfully, status will be changed to `Successed`, and `ExpiresAt` will be set to **1 day** later. | |||||
Consuming failure will change the message status to `Failed` and `ExpiresAt` will be set to **15 days** later. | Consuming failure will change the message status to `Failed` and `ExpiresAt` will be set to **15 days** later. | ||||
@@ -3,7 +3,7 @@ using System.Threading.Tasks; | |||||
using DotNetCore.CAP; | using DotNetCore.CAP; | ||||
using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||
namespace Sample.Kafka.InMemory.Controllers | |||||
namespace Sample.AmazonSQS.InMemory.Controllers | |||||
{ | { | ||||
[Route("api/[controller]")] | [Route("api/[controller]")] | ||||
public class ValuesController : Controller, ICapSubscribe | public class ValuesController : Controller, ICapSubscribe | ||||
@@ -18,13 +18,13 @@ namespace Sample.Kafka.InMemory.Controllers | |||||
[Route("~/without/transaction")] | [Route("~/without/transaction")] | ||||
public async Task<IActionResult> WithoutTransaction() | public async Task<IActionResult> WithoutTransaction() | ||||
{ | { | ||||
await _capBus.PublishAsync("sample.azure.mysql2", DateTime.Now); | |||||
await _capBus.PublishAsync("sample.aws.in-memory", DateTime.Now); | |||||
return Ok(); | return Ok(); | ||||
} | } | ||||
[CapSubscribe("sample.azure.mysql2")] | |||||
public void Test2T2(DateTime value) | |||||
[CapSubscribe("sample.aws.in-memory")] | |||||
public void SubscribeInMemoryTopic(DateTime value) | |||||
{ | { | ||||
Console.WriteLine("Subscriber output message: " + value); | Console.WriteLine("Subscriber output message: " + value); | ||||
} | } |
@@ -1,7 +1,7 @@ | |||||
using Microsoft.AspNetCore.Hosting; | using Microsoft.AspNetCore.Hosting; | ||||
using Microsoft.Extensions.Hosting; | using Microsoft.Extensions.Hosting; | ||||
namespace Sample.Kafka.InMemory | |||||
namespace Sample.AmazonSQS.InMemory | |||||
{ | { | ||||
public class Program | public class Program | ||||
{ | { |
@@ -5,10 +5,9 @@ | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\..\src\DotNetCore.CAP.AmazonSQS\DotNetCore.CAP.AmazonSQS.csproj" /> | |||||
<ProjectReference Include="..\..\src\DotNetCore.CAP.Dashboard\DotNetCore.CAP.Dashboard.csproj" /> | <ProjectReference Include="..\..\src\DotNetCore.CAP.Dashboard\DotNetCore.CAP.Dashboard.csproj" /> | ||||
<ProjectReference Include="..\..\src\DotNetCore.CAP.InMemoryStorage\DotNetCore.CAP.InMemoryStorage.csproj" /> | <ProjectReference Include="..\..\src\DotNetCore.CAP.InMemoryStorage\DotNetCore.CAP.InMemoryStorage.csproj" /> | ||||
<ProjectReference Include="..\..\src\DotNetCore.CAP.Kafka\DotNetCore.CAP.Kafka.csproj" /> | |||||
<ProjectReference Include="..\..\src\DotNetCore.CAP.Pulsar\DotNetCore.CAP.Pulsar.csproj" /> | |||||
<ProjectReference Include="..\..\src\DotNetCore.CAP\DotNetCore.CAP.csproj" /> | <ProjectReference Include="..\..\src\DotNetCore.CAP\DotNetCore.CAP.csproj" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -1,7 +1,8 @@ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Amazon; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
namespace Sample.Kafka.InMemory | |||||
namespace Sample.AmazonSQS.InMemory | |||||
{ | { | ||||
public class Startup | public class Startup | ||||
{ | { | ||||
@@ -10,7 +11,7 @@ namespace Sample.Kafka.InMemory | |||||
services.AddCap(x => | services.AddCap(x => | ||||
{ | { | ||||
x.UseInMemoryStorage(); | x.UseInMemoryStorage(); | ||||
x.UseKafka("localhost:9092"); | |||||
x.UseAmazonSQS(RegionEndpoint.CNNorthWest1); | |||||
x.UseDashboard(); | x.UseDashboard(); | ||||
}); | }); | ||||
@@ -2,7 +2,7 @@ | |||||
"Logging": { | "Logging": { | ||||
"IncludeScopes": false, | "IncludeScopes": false, | ||||
"LogLevel": { | "LogLevel": { | ||||
"Default": "Debug" | |||||
"Default": "Error" | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -6,6 +6,7 @@ using System.Collections.Generic; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Text; | using System.Text; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | |||||
using Amazon.SimpleNotificationService; | using Amazon.SimpleNotificationService; | ||||
using Amazon.SimpleNotificationService.Model; | using Amazon.SimpleNotificationService.Model; | ||||
using Amazon.SQS; | using Amazon.SQS; | ||||
@@ -103,12 +104,27 @@ namespace DotNetCore.CAP.AmazonSQS | |||||
public void Commit(object sender) | public void Commit(object sender) | ||||
{ | { | ||||
_sqsClient.DeleteMessageAsync(_queueUrl, (string)sender); | |||||
try | |||||
{ | |||||
_sqsClient.DeleteMessageAsync(_queueUrl, (string)sender); | |||||
} | |||||
catch (InvalidIdFormatException ex) | |||||
{ | |||||
InvalidIdFormatLog(ex.Message); | |||||
} | |||||
} | } | ||||
public void Reject(object sender) | public void Reject(object sender) | ||||
{ | { | ||||
_sqsClient.ChangeMessageVisibilityAsync(_queueUrl, (string)sender, 3000); | |||||
try | |||||
{ | |||||
// Visible again in 3 seconds | |||||
_sqsClient.ChangeMessageVisibilityAsync(_queueUrl, (string)sender, 3); | |||||
} | |||||
catch (MessageNotInflightException ex) | |||||
{ | |||||
MessageNotInflightLog(ex.Message); | |||||
} | |||||
} | } | ||||
public void Dispose() | public void Dispose() | ||||
@@ -162,5 +178,35 @@ namespace DotNetCore.CAP.AmazonSQS | |||||
} | } | ||||
} | } | ||||
} | } | ||||
#region private methods | |||||
private Task InvalidIdFormatLog(string exceptionMessage) | |||||
{ | |||||
var logArgs = new LogMessageEventArgs | |||||
{ | |||||
LogType = MqLogType.InvalidIdFormat, | |||||
Reason = exceptionMessage | |||||
}; | |||||
OnLog?.Invoke(null, logArgs); | |||||
return Task.CompletedTask; | |||||
} | |||||
private Task MessageNotInflightLog(string exceptionMessage) | |||||
{ | |||||
var logArgs = new LogMessageEventArgs | |||||
{ | |||||
LogType = MqLogType.MessageNotInflight, | |||||
Reason = exceptionMessage | |||||
}; | |||||
OnLog?.Invoke(null, logArgs); | |||||
return Task.CompletedTask; | |||||
} | |||||
#endregion | |||||
} | } | ||||
} | } |
@@ -2,7 +2,7 @@ | |||||
namespace DotNetCore.CAP.AmazonSQS | namespace DotNetCore.CAP.AmazonSQS | ||||
{ | { | ||||
public static class TopicNormalizer | |||||
internal static class TopicNormalizer | |||||
{ | { | ||||
public static string NormalizeForAws(this string origin) | public static string NormalizeForAws(this string origin) | ||||
{ | { | ||||
@@ -45,7 +45,7 @@ | |||||
{ | { | ||||
<td rowspan="@rowCount">@subscriber.Key</td> | <td rowspan="@rowCount">@subscriber.Key</td> | ||||
} | } | ||||
<td>@column.Attribute.Name</td> | |||||
<td>@column.TopicName</td> | |||||
<td> | <td> | ||||
<span style="color: #00bcd4">@column.ImplTypeInfo.Name</span>: | <span style="color: #00bcd4">@column.ImplTypeInfo.Name</span>: | ||||
<div class="job-snippet-code"> | <div class="job-snippet-code"> | ||||
@@ -200,7 +200,7 @@ WriteLiteral(" <td>"); | |||||
#line 48 "..\..\Pages\SubscriberPage.cshtml" | #line 48 "..\..\Pages\SubscriberPage.cshtml" | ||||
Write(column.Attribute.Name); | |||||
Write(column.TopicName); | |||||
#line default | #line default | ||||
@@ -27,8 +27,7 @@ namespace DotNetCore.CAP.MongoDB | |||||
public MongoDBDataStorage( | public MongoDBDataStorage( | ||||
IOptions<CapOptions> capOptions, | IOptions<CapOptions> capOptions, | ||||
IOptions<MongoDBOptions> options, | IOptions<MongoDBOptions> options, | ||||
IMongoClient client, | |||||
ILogger<MongoDBDataStorage> logger) | |||||
IMongoClient client) | |||||
{ | { | ||||
_capOptions = capOptions; | _capOptions = capOptions; | ||||
_options = options; | _options = options; | ||||
@@ -194,7 +193,7 @@ namespace DotNetCore.CAP.MongoDB | |||||
public async Task<IEnumerable<MediumMessage>> GetReceivedMessagesOfNeedRetry() | public async Task<IEnumerable<MediumMessage>> GetReceivedMessagesOfNeedRetry() | ||||
{ | { | ||||
var fourMinAgo = DateTime.Now.AddMinutes(-4); | var fourMinAgo = DateTime.Now.AddMinutes(-4); | ||||
var collection = _database.GetCollection<ReceivedMessage>(_options.Value.PublishedCollection); | |||||
var collection = _database.GetCollection<ReceivedMessage>(_options.Value.ReceivedCollection); | |||||
var queryResult = await collection | var queryResult = await collection | ||||
.Find(x => x.Retries < _capOptions.Value.FailedRetryCount | .Find(x => x.Retries < _capOptions.Value.FailedRetryCount | ||||
&& x.Added < fourMinAgo | && x.Added < fourMinAgo | ||||
@@ -217,4 +216,4 @@ namespace DotNetCore.CAP.MongoDB | |||||
return new MongoDBMonitoringApi(_client, _options); | return new MongoDBMonitoringApi(_client, _options); | ||||
} | } | ||||
} | } | ||||
} | |||||
} |
@@ -1,10 +1,13 @@ | |||||
using System; | |||||
// Copyright (c) .NET Core Community. All rights reserved. | |||||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||||
using System; | |||||
using System.ComponentModel; | using System.ComponentModel; | ||||
using System.Data; | using System.Data; | ||||
namespace DotNetCore.CAP.MySql | namespace DotNetCore.CAP.MySql | ||||
{ | { | ||||
internal static class IDbConnectionExtensions | |||||
internal static class DbConnectionExtensions | |||||
{ | { | ||||
public static int ExecuteNonQuery(this IDbConnection connection, string sql, IDbTransaction transaction = null, | public static int ExecuteNonQuery(this IDbConnection connection, string sql, IDbTransaction transaction = null, | ||||
params object[] sqlParams) | params object[] sqlParams) |
@@ -124,8 +124,8 @@ SELECT | |||||
{ | { | ||||
Id = reader.GetInt64(index++), | Id = reader.GetInt64(index++), | ||||
Version = reader.GetString(index++), | Version = reader.GetString(index++), | ||||
Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, | |||||
Name = reader.GetString(index++), | Name = reader.GetString(index++), | ||||
Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, | |||||
Content = reader.GetString(index++), | Content = reader.GetString(index++), | ||||
Retries = reader.GetInt32(index++), | Retries = reader.GetInt32(index++), | ||||
Added = reader.GetDateTime(index++), | Added = reader.GetDateTime(index++), | ||||
@@ -269,4 +269,4 @@ WHERE `Key` >= @minKey | |||||
return mediumMessage; | return mediumMessage; | ||||
} | } | ||||
} | } | ||||
} | |||||
} |
@@ -1,10 +1,13 @@ | |||||
using System; | |||||
// Copyright (c) .NET Core Community. All rights reserved. | |||||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||||
using System; | |||||
using System.ComponentModel; | using System.ComponentModel; | ||||
using System.Data; | using System.Data; | ||||
namespace DotNetCore.CAP.PostgreSql | namespace DotNetCore.CAP.PostgreSql | ||||
{ | { | ||||
internal static class IDbConnectionExtensions | |||||
internal static class DbConnectionExtensions | |||||
{ | { | ||||
public static int ExecuteNonQuery(this IDbConnection connection, string sql, IDbTransaction transaction = null, | public static int ExecuteNonQuery(this IDbConnection connection, string sql, IDbTransaction transaction = null, | ||||
params object[] sqlParams) | params object[] sqlParams) |
@@ -108,8 +108,8 @@ namespace DotNetCore.CAP.PostgreSql | |||||
{ | { | ||||
Id = reader.GetInt64(index++), | Id = reader.GetInt64(index++), | ||||
Version = reader.GetString(index++), | Version = reader.GetString(index++), | ||||
Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, | |||||
Name = reader.GetString(index++), | Name = reader.GetString(index++), | ||||
Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, | |||||
Content = reader.GetString(index++), | Content = reader.GetString(index++), | ||||
Retries = reader.GetInt32(index++), | Retries = reader.GetInt32(index++), | ||||
Added = reader.GetDateTime(index++), | Added = reader.GetDateTime(index++), | ||||
@@ -1,10 +1,13 @@ | |||||
using System; | |||||
// Copyright (c) .NET Core Community. All rights reserved. | |||||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||||
using System; | |||||
using System.ComponentModel; | using System.ComponentModel; | ||||
using System.Data; | using System.Data; | ||||
namespace DotNetCore.CAP.SqlServer | namespace DotNetCore.CAP.SqlServer | ||||
{ | { | ||||
internal static class IDbConnectionExtensions | |||||
internal static class DbConnectionExtensions | |||||
{ | { | ||||
public static int ExecuteNonQuery(this IDbConnection connection, string sql, IDbTransaction transaction = null, | public static int ExecuteNonQuery(this IDbConnection connection, string sql, IDbTransaction transaction = null, | ||||
params object[] sqlParams) | params object[] sqlParams) |
@@ -119,8 +119,8 @@ SELECT | |||||
{ | { | ||||
Id = reader.GetInt64(index++), | Id = reader.GetInt64(index++), | ||||
Version = reader.GetString(index++), | Version = reader.GetString(index++), | ||||
Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, | |||||
Name = reader.GetString(index++), | Name = reader.GetString(index++), | ||||
Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, | |||||
Content = reader.GetString(index++), | Content = reader.GetString(index++), | ||||
Retries = reader.GetInt32(index++), | Retries = reader.GetInt32(index++), | ||||
Added = reader.GetDateTime(index++), | Added = reader.GetDateTime(index++), | ||||
@@ -271,4 +271,4 @@ select [Key], [Count] from aggr with (nolock) where [Key] >= @minKey and [Key] < | |||||
return await Task.FromResult(mediumMessage); | return await Task.FromResult(mediumMessage); | ||||
} | } | ||||
} | } | ||||
} | |||||
} |
@@ -10,13 +10,13 @@ using DotNetCore.CAP.Internal; | |||||
namespace DotNetCore.CAP | namespace DotNetCore.CAP | ||||
{ | { | ||||
public class CapSubscribeAttribute : TopicAttribute | public class CapSubscribeAttribute : TopicAttribute | ||||
{ | |||||
public CapSubscribeAttribute(string name) | |||||
: base(name) | |||||
{ | |||||
} | |||||
{ | |||||
public CapSubscribeAttribute(string name, bool isPartial = false) | |||||
: base(name, isPartial) | |||||
{ | |||||
} | |||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
return Name; | return Name; | ||||
@@ -20,7 +20,33 @@ namespace DotNetCore.CAP.Internal | |||||
public TopicAttribute Attribute { get; set; } | public TopicAttribute Attribute { get; set; } | ||||
public TopicAttribute ClassAttribute { get; set; } | |||||
public IList<ParameterDescriptor> Parameters { get; set; } | public IList<ParameterDescriptor> Parameters { get; set; } | ||||
private string _topicName; | |||||
/// <summary> | |||||
/// Topic name based on both <see cref="Attribute"/> and <see cref="ClassAttribute"/>. | |||||
/// </summary> | |||||
public string TopicName | |||||
{ | |||||
get | |||||
{ | |||||
if (_topicName == null) | |||||
{ | |||||
if (ClassAttribute != null && Attribute.IsPartial) | |||||
{ | |||||
// Allows class level attribute name to end with a '.' and allows methods level attribute to start with a '.'. | |||||
_topicName = $"{ClassAttribute.Name.TrimEnd('.')}.{Attribute.Name.TrimStart('.')}"; | |||||
} | |||||
else | |||||
{ | |||||
_topicName = Attribute.Name; | |||||
} | |||||
} | |||||
return _topicName; | |||||
} | |||||
} | |||||
} | } | ||||
public class ParameterDescriptor | public class ParameterDescriptor | ||||
@@ -79,7 +79,7 @@ namespace DotNetCore.CAP.Internal | |||||
RegisterMessageProcessor(client); | RegisterMessageProcessor(client); | ||||
client.Subscribe(matchGroup.Value.Select(x => x.Attribute.Name)); | |||||
client.Subscribe(matchGroup.Value.Select(x => x.TopicName)); | |||||
client.Listening(_pollingDelay, _cts.Token); | client.Listening(_pollingDelay, _cts.Token); | ||||
} | } | ||||
@@ -271,6 +271,12 @@ namespace DotNetCore.CAP.Internal | |||||
case MqLogType.ExceptionReceived: | case MqLogType.ExceptionReceived: | ||||
_logger.LogError("AzureServiceBus subscriber received an error. --> " + logmsg.Reason); | _logger.LogError("AzureServiceBus subscriber received an error. --> " + logmsg.Reason); | ||||
break; | break; | ||||
case MqLogType.InvalidIdFormat: | |||||
_logger.LogError("AmazonSQS subscriber delete inflight message failed, invalid id. --> " + logmsg.Reason); | |||||
break; | |||||
case MqLogType.MessageNotInflight: | |||||
_logger.LogError("AmazonSQS subscriber change message's visibility failed, message isn't in flight. --> " + logmsg.Reason); | |||||
break; | |||||
default: | default: | ||||
throw new ArgumentOutOfRangeException(); | throw new ArgumentOutOfRangeException(); | ||||
} | } | ||||
@@ -116,17 +116,24 @@ namespace DotNetCore.CAP.Internal | |||||
protected IEnumerable<ConsumerExecutorDescriptor> GetTopicAttributesDescription(TypeInfo typeInfo, TypeInfo serviceTypeInfo = null) | protected IEnumerable<ConsumerExecutorDescriptor> GetTopicAttributesDescription(TypeInfo typeInfo, TypeInfo serviceTypeInfo = null) | ||||
{ | { | ||||
var topicClassAttribute = typeInfo.GetCustomAttribute<TopicAttribute>(true); | |||||
foreach (var method in typeInfo.DeclaredMethods) | foreach (var method in typeInfo.DeclaredMethods) | ||||
{ | { | ||||
var topicAttr = method.GetCustomAttributes<TopicAttribute>(true); | |||||
var topicAttributes = topicAttr as IList<TopicAttribute> ?? topicAttr.ToList(); | |||||
var topicMethodAttributes = method.GetCustomAttributes<TopicAttribute>(true); | |||||
// Ignore partial attributes when no topic attribute is defined on class. | |||||
if (topicClassAttribute is null) | |||||
{ | |||||
topicMethodAttributes = topicMethodAttributes.Where(x => !x.IsPartial); | |||||
} | |||||
if (!topicAttributes.Any()) | |||||
if (!topicMethodAttributes.Any()) | |||||
{ | { | ||||
continue; | continue; | ||||
} | } | ||||
foreach (var attr in topicAttributes) | |||||
foreach (var attr in topicMethodAttributes) | |||||
{ | { | ||||
SetSubscribeAttribute(attr); | SetSubscribeAttribute(attr); | ||||
@@ -138,21 +145,14 @@ namespace DotNetCore.CAP.Internal | |||||
IsFromCap = parameter.GetCustomAttributes(typeof(FromCapAttribute)).Any() | IsFromCap = parameter.GetCustomAttributes(typeof(FromCapAttribute)).Any() | ||||
}).ToList(); | }).ToList(); | ||||
yield return InitDescriptor(attr, method, typeInfo, serviceTypeInfo, parameters); | |||||
yield return InitDescriptor(attr, method, typeInfo, serviceTypeInfo, parameters, topicClassAttribute); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
protected virtual void SetSubscribeAttribute(TopicAttribute attribute) | protected virtual void SetSubscribeAttribute(TopicAttribute attribute) | ||||
{ | { | ||||
if (attribute.Group == null) | |||||
{ | |||||
attribute.Group = _capOptions.DefaultGroup + "." + _capOptions.Version; | |||||
} | |||||
else | |||||
{ | |||||
attribute.Group = attribute.Group + "." + _capOptions.Version; | |||||
} | |||||
attribute.Group = (attribute.Group ?? _capOptions.DefaultGroup) + "." + _capOptions.Version; | |||||
} | } | ||||
private static ConsumerExecutorDescriptor InitDescriptor( | private static ConsumerExecutorDescriptor InitDescriptor( | ||||
@@ -160,11 +160,13 @@ namespace DotNetCore.CAP.Internal | |||||
MethodInfo methodInfo, | MethodInfo methodInfo, | ||||
TypeInfo implType, | TypeInfo implType, | ||||
TypeInfo serviceTypeInfo, | TypeInfo serviceTypeInfo, | ||||
IList<ParameterDescriptor> parameters) | |||||
IList<ParameterDescriptor> parameters, | |||||
TopicAttribute classAttr = null) | |||||
{ | { | ||||
var descriptor = new ConsumerExecutorDescriptor | var descriptor = new ConsumerExecutorDescriptor | ||||
{ | { | ||||
Attribute = attr, | Attribute = attr, | ||||
ClassAttribute = classAttr, | |||||
MethodInfo = methodInfo, | MethodInfo = methodInfo, | ||||
ImplTypeInfo = implType, | ImplTypeInfo = implType, | ||||
ServiceTypeInfo = serviceTypeInfo, | ServiceTypeInfo = serviceTypeInfo, | ||||
@@ -176,7 +178,7 @@ namespace DotNetCore.CAP.Internal | |||||
private ConsumerExecutorDescriptor MatchUsingName(string key, IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor) | private ConsumerExecutorDescriptor MatchUsingName(string key, IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor) | ||||
{ | { | ||||
return executeDescriptor.FirstOrDefault(x => x.Attribute.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase)); | |||||
return executeDescriptor.FirstOrDefault(x => x.TopicName.Equals(key, StringComparison.InvariantCultureIgnoreCase)); | |||||
} | } | ||||
private ConsumerExecutorDescriptor MatchAsteriskUsingRegex(string key, IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor) | private ConsumerExecutorDescriptor MatchAsteriskUsingRegex(string key, IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor) | ||||
@@ -184,10 +186,10 @@ namespace DotNetCore.CAP.Internal | |||||
var group = executeDescriptor.First().Attribute.Group; | var group = executeDescriptor.First().Attribute.Group; | ||||
if (!_asteriskList.TryGetValue(group, out var tmpList)) | if (!_asteriskList.TryGetValue(group, out var tmpList)) | ||||
{ | { | ||||
tmpList = executeDescriptor.Where(x => x.Attribute.Name.IndexOf('*') >= 0) | |||||
tmpList = executeDescriptor.Where(x => x.TopicName.IndexOf('*') >= 0) | |||||
.Select(x => new RegexExecuteDescriptor<ConsumerExecutorDescriptor> | .Select(x => new RegexExecuteDescriptor<ConsumerExecutorDescriptor> | ||||
{ | { | ||||
Name = ("^" + x.Attribute.Name + "$").Replace("*", "[0-9_a-zA-Z]+").Replace(".", "\\."), | |||||
Name = ("^" + x.TopicName + "$").Replace("*", "[0-9_a-zA-Z]+").Replace(".", "\\."), | |||||
Descriptor = x | Descriptor = x | ||||
}).ToList(); | }).ToList(); | ||||
_asteriskList.TryAdd(group, tmpList); | _asteriskList.TryAdd(group, tmpList); | ||||
@@ -210,10 +212,10 @@ namespace DotNetCore.CAP.Internal | |||||
if (!_poundList.TryGetValue(group, out var tmpList)) | if (!_poundList.TryGetValue(group, out var tmpList)) | ||||
{ | { | ||||
tmpList = executeDescriptor | tmpList = executeDescriptor | ||||
.Where(x => x.Attribute.Name.IndexOf('#') >= 0) | |||||
.Where(x => x.TopicName.IndexOf('#') >= 0) | |||||
.Select(x => new RegexExecuteDescriptor<ConsumerExecutorDescriptor> | .Select(x => new RegexExecuteDescriptor<ConsumerExecutorDescriptor> | ||||
{ | { | ||||
Name = ("^" + x.Attribute.Name.Replace(".", "\\.") + "$").Replace("#", "[0-9_a-zA-Z\\.]+"), | |||||
Name = ("^" + x.TopicName.Replace(".", "\\.") + "$").Replace("#", "[0-9_a-zA-Z\\.]+"), | |||||
Descriptor = x | Descriptor = x | ||||
}).ToList(); | }).ToList(); | ||||
_poundList.TryAdd(group, tmpList); | _poundList.TryAdd(group, tmpList); | ||||
@@ -12,9 +12,10 @@ namespace DotNetCore.CAP.Internal | |||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] | ||||
public abstract class TopicAttribute : Attribute | public abstract class TopicAttribute : Attribute | ||||
{ | { | ||||
protected TopicAttribute(string name) | |||||
protected TopicAttribute(string name, bool isPartial = false) | |||||
{ | { | ||||
Name = name; | Name = name; | ||||
IsPartial = isPartial; | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -22,6 +23,13 @@ namespace DotNetCore.CAP.Internal | |||||
/// </summary> | /// </summary> | ||||
public string Name { get; } | public string Name { get; } | ||||
/// <summary> | |||||
/// Defines wether this attribute defines a topic subscription partial. | |||||
/// The defined topic will be combined with a topic subscription defined on class level, | |||||
/// which results for example in subscription on "class.method". | |||||
/// </summary> | |||||
public bool IsPartial { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Default group name is CapOptions setting.(Assembly name) | /// Default group name is CapOptions setting.(Assembly name) | ||||
/// kafka --> groups.id | /// kafka --> groups.id | ||||
@@ -18,7 +18,11 @@ namespace DotNetCore.CAP.Transport | |||||
ServerConnError, | ServerConnError, | ||||
//AzureServiceBus | //AzureServiceBus | ||||
ExceptionReceived | |||||
ExceptionReceived, | |||||
//Amazon SQS | |||||
InvalidIdFormat, | |||||
MessageNotInflight | |||||
} | } | ||||
public class LogMessageEventArgs : EventArgs | public class LogMessageEventArgs : EventArgs | ||||
@@ -29,15 +29,18 @@ namespace DotNetCore.CAP.Test | |||||
var selector = _provider.GetRequiredService<IConsumerServiceSelector>(); | var selector = _provider.GetRequiredService<IConsumerServiceSelector>(); | ||||
var candidates = selector.SelectCandidates(); | var candidates = selector.SelectCandidates(); | ||||
Assert.Equal(6, candidates.Count); | |||||
Assert.Equal(8, candidates.Count); | |||||
} | } | ||||
[Fact] | |||||
public void CanFindSpecifiedTopic() | |||||
[Theory] | |||||
[InlineData("Candidates.Foo")] | |||||
[InlineData("Candidates.Foo3")] | |||||
[InlineData("Candidates.Foo4")] | |||||
public void CanFindSpecifiedTopic(string topic) | |||||
{ | { | ||||
var selector = _provider.GetRequiredService<IConsumerServiceSelector>(); | var selector = _provider.GetRequiredService<IConsumerServiceSelector>(); | ||||
var candidates = selector.SelectCandidates(); | var candidates = selector.SelectCandidates(); | ||||
var bestCandidates = selector.SelectBestCandidate("Candidates.Foo", candidates); | |||||
var bestCandidates = selector.SelectBestCandidate(topic, candidates); | |||||
Assert.NotNull(bestCandidates); | Assert.NotNull(bestCandidates); | ||||
Assert.NotNull(bestCandidates.MethodInfo); | Assert.NotNull(bestCandidates.MethodInfo); | ||||
@@ -116,7 +119,7 @@ namespace DotNetCore.CAP.Test | |||||
public class CandidatesTopic : TopicAttribute | public class CandidatesTopic : TopicAttribute | ||||
{ | { | ||||
public CandidatesTopic(string topicName) : base(topicName) | |||||
public CandidatesTopic(string topicName, bool isPartial = false) : base(topicName, isPartial) | |||||
{ | { | ||||
} | } | ||||
} | } | ||||
@@ -129,6 +132,7 @@ namespace DotNetCore.CAP.Test | |||||
{ | { | ||||
} | } | ||||
[CandidatesTopic("Candidates")] | |||||
public class CandidatesFooTest : IFooTest, ICapSubscribe | public class CandidatesFooTest : IFooTest, ICapSubscribe | ||||
{ | { | ||||
[CandidatesTopic("Candidates.Foo")] | [CandidatesTopic("Candidates.Foo")] | ||||
@@ -144,6 +148,20 @@ namespace DotNetCore.CAP.Test | |||||
Console.WriteLine("GetFoo2() method has bee excuted."); | Console.WriteLine("GetFoo2() method has bee excuted."); | ||||
} | } | ||||
[CandidatesTopic("Foo3", isPartial: true)] | |||||
public Task GetFoo3() | |||||
{ | |||||
Console.WriteLine("GetFoo3() method has bee excuted."); | |||||
return Task.CompletedTask; | |||||
} | |||||
[CandidatesTopic(".Foo4", isPartial: true)] | |||||
public Task GetFoo4() | |||||
{ | |||||
Console.WriteLine("GetFoo4() method has bee excuted."); | |||||
return Task.CompletedTask; | |||||
} | |||||
[CandidatesTopic("*.*.Asterisk")] | [CandidatesTopic("*.*.Asterisk")] | ||||
[CandidatesTopic("*.Asterisk")] | [CandidatesTopic("*.Asterisk")] | ||||
public void GetFooAsterisk() | public void GetFooAsterisk() | ||||