Repository: dotnetcore/CAP
Branch: master
Commit: 87a1a393b2be
Files: 495
Total size: 2.3 MB
Directory structure:
gitextract__fz6t0_d/
├── .flubu
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── feature_request.yml
│ │ └── question.yml
│ ├── pull_request_template.md
│ └── workflows/
│ └── deploy-docs-and-dashboard.yml
├── .gitignore
├── CAP.sln
├── CAP.sln.DotSettings
├── LICENSE.txt
├── README.md
├── README.zh-cn.md
├── appveyor.yml
├── build/
│ ├── BuildScript.Util.cs
│ ├── BuildScript.Version.cs
│ ├── BuildScript.cs
│ ├── BuildScript.csproj
│ ├── BuildVersion.cs
│ └── version.props
├── code-of-conduct.md
├── docs/
│ ├── content/
│ │ ├── CNAME
│ │ ├── about/
│ │ │ ├── contact-us.md
│ │ │ ├── license.md
│ │ │ └── release-notes.md
│ │ ├── index.md
│ │ └── user-guide/
│ │ ├── en/
│ │ │ ├── cap/
│ │ │ │ ├── configuration.md
│ │ │ │ ├── filter.md
│ │ │ │ ├── idempotence.md
│ │ │ │ ├── messaging.md
│ │ │ │ ├── serialization.md
│ │ │ │ └── transactions.md
│ │ │ ├── getting-started/
│ │ │ │ ├── contributing.md
│ │ │ │ ├── introduction.md
│ │ │ │ └── quick-start.md
│ │ │ ├── monitoring/
│ │ │ │ ├── consul.md
│ │ │ │ ├── dashboard.md
│ │ │ │ ├── diagnostics.md
│ │ │ │ ├── kubernetes.md
│ │ │ │ └── opentelemetry.md
│ │ │ ├── samples/
│ │ │ │ ├── eshoponcontainers.md
│ │ │ │ ├── faq.md
│ │ │ │ └── github.md
│ │ │ ├── storage/
│ │ │ │ ├── general.md
│ │ │ │ ├── in-memory-storage.md
│ │ │ │ ├── mongodb.md
│ │ │ │ ├── mysql.md
│ │ │ │ ├── postgresql.md
│ │ │ │ └── sqlserver.md
│ │ │ └── transport/
│ │ │ ├── aws-sqs.md
│ │ │ ├── azure-service-bus.md
│ │ │ ├── general.md
│ │ │ ├── in-memory-queue.md
│ │ │ ├── kafka.md
│ │ │ ├── nats.md
│ │ │ ├── pulsar.md
│ │ │ ├── rabbitmq.md
│ │ │ └── redis-streams.md
│ │ └── zh/
│ │ ├── cap/
│ │ │ ├── configuration.md
│ │ │ ├── filter.md
│ │ │ ├── idempotence.md
│ │ │ ├── messaging.md
│ │ │ ├── serialization.md
│ │ │ └── transactions.md
│ │ ├── getting-started/
│ │ │ ├── contributing.md
│ │ │ ├── introduction.md
│ │ │ └── quick-start.md
│ │ ├── monitoring/
│ │ │ ├── consul.md
│ │ │ ├── dashboard.md
│ │ │ ├── diagnostics.md
│ │ │ ├── kubernetes.md
│ │ │ └── opentelemetry.md
│ │ ├── samples/
│ │ │ ├── castle.dynamicproxy.md
│ │ │ ├── eshoponcontainers.md
│ │ │ ├── faq.md
│ │ │ └── github.md
│ │ ├── storage/
│ │ │ ├── general.md
│ │ │ ├── in-memory-storage.md
│ │ │ ├── mongodb.md
│ │ │ ├── mysql.md
│ │ │ ├── postgresql.md
│ │ │ └── sqlserver.md
│ │ └── transport/
│ │ ├── aws-sqs.md
│ │ ├── azure-service-bus.md
│ │ ├── general.md
│ │ ├── in-memory-queue.md
│ │ ├── kafka.md
│ │ ├── nats.md
│ │ ├── pulsar.md
│ │ ├── rabbitmq.md
│ │ └── redis-streams.md
│ ├── mkdocs.yml
│ └── readme.md
├── samples/
│ ├── Sample.AmazonSQS.InMemory/
│ │ ├── Controllers/
│ │ │ └── ValuesController.cs
│ │ ├── Program.cs
│ │ ├── Sample.AmazonSQS.InMemory.csproj
│ │ └── appsettings.json
│ ├── Sample.AzureServiceBus.InMemory/
│ │ ├── Contracts/
│ │ │ ├── DomainEvents/
│ │ │ │ └── Contract.cs
│ │ │ └── IntegrationEvents/
│ │ │ └── Contract.cs
│ │ ├── Program.cs
│ │ ├── Sample.AzureServiceBus.InMemory.csproj
│ │ ├── SampleSubscriber.cs
│ │ ├── appsettings.Development.json
│ │ └── appsettings.json
│ ├── Sample.ConsoleApp/
│ │ ├── EventSubscriber.cs
│ │ ├── Program.cs
│ │ └── Sample.ConsoleApp.csproj
│ ├── Sample.Dashboard.Auth/
│ │ ├── Controllers/
│ │ │ └── ValuesController.cs
│ │ ├── MyDashboardAuthenticationHandler.cs
│ │ ├── Program.cs
│ │ ├── Sample.Dashboard.Auth.csproj
│ │ ├── Startup.cs
│ │ └── appsettings.json
│ ├── Sample.Dashboard.Jwt/
│ │ ├── Data/
│ │ │ └── User.cs
│ │ ├── Dockerfile
│ │ ├── Program.cs
│ │ ├── Sample.Dashboard.Jwt.csproj
│ │ ├── appsettings.json
│ │ └── wwwroot/
│ │ ├── css/
│ │ │ └── site.css
│ │ ├── index.html
│ │ └── js/
│ │ └── site.js
│ ├── Sample.Kafka.PostgreSql/
│ │ ├── AppDbContext.cs
│ │ ├── Controllers/
│ │ │ └── ValuesController.cs
│ │ ├── Program.cs
│ │ ├── Sample.Kafka.PostgreSql.csproj
│ │ └── appsettings.json
│ ├── Sample.Pulsar.InMemory/
│ │ ├── Controllers/
│ │ │ └── ValuesController.cs
│ │ ├── Program.cs
│ │ ├── Sample.Pulsar.InMemory.csproj
│ │ └── appsettings.json
│ ├── Sample.RabbitMQ.MongoDB/
│ │ ├── Controllers/
│ │ │ └── ValuesController.cs
│ │ ├── Program.cs
│ │ ├── Sample.RabbitMQ.MongoDB.csproj
│ │ ├── appsettings.json
│ │ └── docker-compose.yml
│ ├── Sample.RabbitMQ.MySql/
│ │ ├── AppDbContext.cs
│ │ ├── Controllers/
│ │ │ └── ValuesController.cs
│ │ ├── Program.cs
│ │ ├── Sample.RabbitMQ.MySql.csproj
│ │ └── appsettings.json
│ ├── Sample.RabbitMQ.SqlServer/
│ │ ├── AppDbContext.cs
│ │ ├── Controllers/
│ │ │ └── ValuesController.cs
│ │ ├── Messages/
│ │ │ ├── TestMessage.cs
│ │ │ ├── VeryFastProcessingReceiver.cs
│ │ │ └── XSlowProcessingReceiver.cs
│ │ ├── Migrations/
│ │ │ ├── 20191205032949_FirstMigration.Designer.cs
│ │ │ ├── 20191205032949_FirstMigration.cs
│ │ │ └── AppDbContextModelSnapshot.cs
│ │ ├── Program.cs
│ │ ├── Sample.RabbitMQ.SqlServer.csproj
│ │ ├── TypedConsumers/
│ │ │ ├── QueueHandler.cs
│ │ │ ├── QueueHandlerTopicAttribute.cs
│ │ │ ├── QueueHandlersExtensions.cs
│ │ │ └── TypedConsumerServiceSelector.cs
│ │ └── appsettings.json
│ └── Samples.Redis.SqlServer/
│ ├── Controllers/
│ │ └── HomeController.cs
│ ├── Dockerfile
│ ├── Program.cs
│ ├── Samples.Redis.SqlServer.csproj
│ ├── appsettings.json
│ └── docker-compose.yml
├── src/
│ ├── Directory.Build.props
│ ├── DotNetCore.CAP/
│ │ ├── BrokerConnectionException.cs
│ │ ├── CAP.Attribute.cs
│ │ ├── CAP.Builder.cs
│ │ ├── CAP.Options.cs
│ │ ├── CAP.ServiceCollectionExtensions.cs
│ │ ├── Diagnostics/
│ │ │ ├── CapDiagnosticListenerNames.cs
│ │ │ ├── EventCounterSource.Cap.cs
│ │ │ ├── EventData.Cap.P.cs
│ │ │ └── EventData.Cap.S.cs
│ │ ├── DotNetCore.CAP.csproj
│ │ ├── IBootstrapper.cs
│ │ ├── ICapOptionsExtension.cs
│ │ ├── ICapPublisher.cs
│ │ ├── ICapSubscribe.cs
│ │ ├── ICapTransaction.Base.cs
│ │ ├── ICapTransaction.cs
│ │ ├── Internal/
│ │ │ ├── ConsumerContext.cs
│ │ │ ├── ConsumerExecutedResult.cs
│ │ │ ├── ConsumerExecutorDescriptor.cs
│ │ │ ├── Filter/
│ │ │ │ ├── ExceptionContext.cs
│ │ │ │ ├── ExecutedContext.cs
│ │ │ │ ├── ExecutingContext.cs
│ │ │ │ ├── FilterContext.cs
│ │ │ │ ├── ISubscribeFilter.cs
│ │ │ │ └── SubscribeFilter.cs
│ │ │ ├── Helper.cs
│ │ │ ├── IBootstrapper.Default.cs
│ │ │ ├── ICapPublisher.Default.cs
│ │ │ ├── IConsumerRegister.Default.cs
│ │ │ ├── IConsumerRegister.cs
│ │ │ ├── IConsumerServiceSelector.Assembly.cs
│ │ │ ├── IConsumerServiceSelector.Default.cs
│ │ │ ├── IConsumerServiceSelector.cs
│ │ │ ├── IMessageSender.Default.cs
│ │ │ ├── IMessageSender.cs
│ │ │ ├── IProcessingServer.cs
│ │ │ ├── ISnowflakeId.Default.cs
│ │ │ ├── ISnowflakeId.cs
│ │ │ ├── ISubscribeExector.Default.cs
│ │ │ ├── ISubscribeExecutor.cs
│ │ │ ├── ISubscribeInvoker.Default.cs
│ │ │ ├── ISubscribeInvoker.cs
│ │ │ ├── LoggerExtensions.cs
│ │ │ ├── MethodMatcherCache.cs
│ │ │ ├── NoopTransaction.cs
│ │ │ ├── ObjectMethodExecutor/
│ │ │ │ ├── AwaitableInfo.cs
│ │ │ │ ├── CoercedAwaitableInfo.cs
│ │ │ │ ├── ObjectMethodExecutor.cs
│ │ │ │ ├── ObjectMethodExecutorAwaitable.cs
│ │ │ │ └── ObjectMethodExecutorFSharpSupport.cs
│ │ │ ├── PublisherSentFailedException.cs
│ │ │ ├── ScheduledMediumMessageQueue.cs
│ │ │ ├── StatusName.cs
│ │ │ ├── SubscriberExecutionFailedException.cs
│ │ │ └── TopicAttribute.cs
│ │ ├── Messages/
│ │ │ ├── FailedInfo.cs
│ │ │ ├── Headers.cs
│ │ │ ├── Message.cs
│ │ │ ├── MessageType.cs
│ │ │ └── TransportMessage.cs
│ │ ├── Monitoring/
│ │ │ ├── IMonitoringApi.cs
│ │ │ ├── MessageDto.cs
│ │ │ ├── MessageQueryDto.cs
│ │ │ ├── PagedQueryResult.cs
│ │ │ └── StatisticsDto.cs
│ │ ├── OperateResult.cs
│ │ ├── Persistence/
│ │ │ ├── IDataStorage.cs
│ │ │ ├── IStorageInitializer.cs
│ │ │ └── MediumMessage.cs
│ │ ├── Processor/
│ │ │ ├── IDispatcher.Default.cs
│ │ │ ├── IProcessingServer.Cap.cs
│ │ │ ├── IProcessor.Collector.cs
│ │ │ ├── IProcessor.Delayed.cs
│ │ │ ├── IProcessor.InfiniteRetry.cs
│ │ │ ├── IProcessor.NeedRetry.cs
│ │ │ ├── IProcessor.TransportCheck.cs
│ │ │ ├── IProcessor.cs
│ │ │ └── ProcessingContext.cs
│ │ ├── Serialization/
│ │ │ ├── ISerializer.JsonUtf8.cs
│ │ │ └── ISerializer.cs
│ │ ├── SubscriberNotFoundException.cs
│ │ └── Transport/
│ │ ├── BrokerAddress.cs
│ │ ├── IConsumerClient.cs
│ │ ├── IConsumerClientFactory.cs
│ │ ├── IDispatcher.cs
│ │ ├── ITransport.cs
│ │ └── MqLogType.cs
│ ├── DotNetCore.CAP.AmazonSQS/
│ │ ├── AmazonPolicyExtensions.cs
│ │ ├── AmazonSQSConsumerClient.cs
│ │ ├── AmazonSQSConsumerClientFactory.cs
│ │ ├── CAP.AmazonSQSOptions.cs
│ │ ├── CAP.AmazonSQSOptionsExtension.cs
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── DotNetCore.CAP.AmazonSQS.csproj
│ │ ├── ITransport.AmazonSQS.cs
│ │ ├── SQSReceivedMessage.cs
│ │ └── TopicNormalizer.cs
│ ├── DotNetCore.CAP.AzureServiceBus/
│ │ ├── AzureServiceBusConsumerClient.cs
│ │ ├── AzureServiceBusConsumerClientFactory.cs
│ │ ├── AzureServiceBusConsumerCommitInput.cs
│ │ ├── AzureServiceBusHeaders.cs
│ │ ├── CAP.AzureServiceBusOptions.cs
│ │ ├── CAP.AzureServiceBusOptionsExtension.cs
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── DotNetCore.CAP.AzureServiceBus.csproj
│ │ ├── Helpers/
│ │ │ └── ServiceBusHelpers.cs
│ │ ├── ITransport.AzureServiceBus.cs
│ │ ├── Producer/
│ │ │ ├── IServiceBusProducerDescriptor.cs
│ │ │ ├── IServiceBusProducerDescriptorFactory.cs
│ │ │ └── ServiceBusProducerDescriptorBuilder.cs
│ │ └── ServiceBusProcessorFacade.cs
│ ├── DotNetCore.CAP.Dashboard/
│ │ ├── CAP.BuilderExtension.cs
│ │ ├── CAP.DashboardOptions.cs
│ │ ├── CAP.DashboardOptionsExtensions.cs
│ │ ├── CAP.MetricsEventListener.cs
│ │ ├── CapCache.cs
│ │ ├── CircularBuffer.cs
│ │ ├── DotNetCore.CAP.Dashboard.csproj
│ │ ├── GatewayProxy/
│ │ │ ├── GatewayProxyAgent.cs
│ │ │ ├── IRequestMapper.Default.cs
│ │ │ ├── IRequestMapper.cs
│ │ │ └── Requester/
│ │ │ ├── HttpClientBuilder.cs
│ │ │ ├── HttpClientHttpRequester.cs
│ │ │ ├── IHttpClient.cs
│ │ │ ├── IHttpClientBuilder.cs
│ │ │ ├── IHttpClientCache.cs
│ │ │ ├── IHttpRequester.cs
│ │ │ └── MemoryHttpClientCache.cs
│ │ ├── HtmlHelper.cs
│ │ ├── NodeDiscovery/
│ │ │ ├── CAP.ConsulDiscoveryOptions.cs
│ │ │ ├── CAP.ConsulDiscoveryOptionsExtensions.cs
│ │ │ ├── INodeDiscoveryProvider.Consul.cs
│ │ │ ├── INodeDiscoveryProvider.cs
│ │ │ ├── IProcessingServer.Consul.cs
│ │ │ └── Node.cs
│ │ ├── RouteActionProvider.cs
│ │ └── wwwroot/
│ │ ├── .gitignore
│ │ ├── .vscode/
│ │ │ └── settings.json
│ │ ├── README.md
│ │ ├── dist/
│ │ │ ├── assets/
│ │ │ │ ├── Nodes.40464ac3.css
│ │ │ │ ├── Nodes.e12132f5.js
│ │ │ │ ├── Published.429cfade.css
│ │ │ │ ├── Published.a8d638e6.js
│ │ │ │ ├── Received.37160321.css
│ │ │ │ ├── Received.e36ea621.js
│ │ │ │ ├── Subscriber.300bed2c.css
│ │ │ │ ├── Subscriber.d66f9645.js
│ │ │ │ ├── index.2d8714a6.js
│ │ │ │ ├── index.856c0890.css
│ │ │ │ └── index.909977fe.js
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ ├── assets/
│ │ │ │ ├── language/
│ │ │ │ │ ├── en-us.js
│ │ │ │ │ └── zh-cn.js
│ │ │ │ └── uPlot.esm.js
│ │ │ ├── components/
│ │ │ │ ├── Footer.vue
│ │ │ │ └── Navigation.vue
│ │ │ ├── main.js
│ │ │ ├── pages/
│ │ │ │ ├── Home.vue
│ │ │ │ ├── Nodes.vue
│ │ │ │ ├── Published.vue
│ │ │ │ ├── Received.vue
│ │ │ │ └── Subscriber.vue
│ │ │ ├── router/
│ │ │ │ └── index.js
│ │ │ └── store/
│ │ │ └── store.js
│ │ └── vite.config.js
│ ├── DotNetCore.CAP.Dashboard.K8s/
│ │ ├── DotNetCore.CAP.Dashboard.K8s.csproj
│ │ ├── K8sDiscoveryOptions.cs
│ │ ├── K8sDiscoveryOptionsExtensions.cs
│ │ ├── K8sNodeDiscoveryProvider.cs
│ │ └── ServiceCollectionExtensions.cs
│ ├── DotNetCore.CAP.InMemoryStorage/
│ │ ├── CAP.InMemoryCapOptionsExtension.cs
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── DotNetCore.CAP.InMemoryStorage.csproj
│ │ ├── ICapTransaction.InMemory.cs
│ │ ├── IDataStorage.InMemory.cs
│ │ ├── IMonitoringApi.InMemory.cs
│ │ ├── IStorageInitializer.InMemory.cs
│ │ └── MemoryMessage.cs
│ ├── DotNetCore.CAP.Kafka/
│ │ ├── CAP.KafkaCapOptionsExtension.cs
│ │ ├── CAP.KafkaOptions.cs
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── DotNetCore.CAP.Kafka.csproj
│ │ ├── IConnectionPool.Default.cs
│ │ ├── IConnectionPool.cs
│ │ ├── ITransport.Kafka.cs
│ │ ├── KafkaConsumerClient.cs
│ │ ├── KafkaConsumerClientFactory.cs
│ │ └── KafkaHeaders.cs
│ ├── DotNetCore.CAP.MongoDB/
│ │ ├── CAP.MongoDBCapOptionsExtension.cs
│ │ ├── CAP.MongoDBOptions.cs
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── DotNetCore.CAP.MongoDB.csproj
│ │ ├── ICapTransaction.MongoDB.cs
│ │ ├── IClientSessionHandle.CAP.cs
│ │ ├── IDataStorage.MongoDB.cs
│ │ ├── IMonitoringApi.MongoDB.cs
│ │ ├── IStorageInitializer.MongoDB.cs
│ │ └── StorageMessage.cs
│ ├── DotNetCore.CAP.MySql/
│ │ ├── CAP.EFOptions.cs
│ │ ├── CAP.MySqlCapOptionsExtension.cs
│ │ ├── CAP.MySqlOptions.cs
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── DotNetCore.CAP.MySql.csproj
│ │ ├── ICapTransaction.MySql.cs
│ │ ├── IDataStorage.MySql.cs
│ │ ├── IDbConnection.Extensions.cs
│ │ ├── IDbContextTransaction.CAP.cs
│ │ ├── IMonitoringApi.MySql.cs
│ │ ├── IStorageInitializer.MySql.cs
│ │ └── ServerVersion.cs
│ ├── DotNetCore.CAP.NATS/
│ │ ├── CAP.NATSCapOptionsExtension.cs
│ │ ├── CAP.NATSOptions.cs
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── DotNetCore.CAP.NATS.csproj
│ │ ├── IConnectionPool.Default.cs
│ │ ├── IConnectionPool.cs
│ │ ├── ITransport.NATS.cs
│ │ ├── NATSConsumerClient.cs
│ │ └── NATSConsumerClientFactory.cs
│ ├── DotNetCore.CAP.OpenTelemetry/
│ │ ├── CapInstrumentation.cs
│ │ ├── DiagnosticListener.cs
│ │ ├── DiagnosticSourceSubscriber.cs
│ │ ├── DotNetCore.CAP.OpenTelemetry.csproj
│ │ └── TracerProviderBuilder.Extension.cs
│ ├── DotNetCore.CAP.PostgreSql/
│ │ ├── CAP.EFOptions.cs
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── CAP.PostgreSqlCapOptionsExtension.cs
│ │ ├── CAP.PostgreSqlOptions.cs
│ │ ├── DotNetCore.CAP.PostgreSql.csproj
│ │ ├── ICapTransaction.PostgreSql.cs
│ │ ├── IDataStorage.PostgreSql.cs
│ │ ├── IDbConnection.Extensions.cs
│ │ ├── IDbContextTransaction.CAP.cs
│ │ ├── IMonitoringApi.PostgreSql.cs
│ │ └── IStorageInitializer.PostgreSql.cs
│ ├── DotNetCore.CAP.Pulsar/
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── CAP.PulsarCapOptionsExtension.cs
│ │ ├── CAP.PulsarOptions.cs
│ │ ├── DotNetCore.CAP.Pulsar.csproj
│ │ ├── IConnectionFactory.Default.cs
│ │ ├── IConnectionFactory.cs
│ │ ├── ITransport.Pulsar.cs
│ │ ├── PulsarConsumerClient.cs
│ │ ├── PulsarConsumerClientFactory.cs
│ │ └── PulsarHeaders.cs
│ ├── DotNetCore.CAP.RabbitMQ/
│ │ ├── CAP.Options.Extensions.cs
│ │ ├── CAP.RabbiMQOptions.cs
│ │ ├── CAP.RabbitMQCapOptionsExtension.cs
│ │ ├── DotNetCore.CAP.RabbitMQ.csproj
│ │ ├── IConnectionChannelPool.Default.cs
│ │ ├── IConnectionChannelPool.cs
│ │ ├── ITransport.RabbitMQ.cs
│ │ ├── RabbitMQBasicConsumer.cs
│ │ ├── RabbitMQConsumerClient.cs
│ │ └── RabbitMQConsumerClientFactory.cs
│ ├── DotNetCore.CAP.RedisStreams/
│ │ ├── CapOptions.Redis.Extensions.cs
│ │ ├── CapOptions.Redis.cs
│ │ ├── DotNetCore.CAP.RedisStreams.csproj
│ │ ├── ICapOptionsExtension.Redis.cs
│ │ ├── IConnectionPool.Default.cs
│ │ ├── IConnectionPool.LazyConnection.cs
│ │ ├── IConnectionPool.cs
│ │ ├── IConsumerClient.Redis.cs
│ │ ├── IConsumerClientFactory.Redis.cs
│ │ ├── IRedis.Events.Logger.cs
│ │ ├── IRedis.Events.cs
│ │ ├── IRedisStream.Manager.Default.cs
│ │ ├── IRedisStream.Manager.Extensions.cs
│ │ ├── IRedisStream.Manager.cs
│ │ ├── ITransport.Redis.cs
│ │ ├── RedisErrorExtensions.cs
│ │ ├── TransportMessage.Redis.Exceptions.cs
│ │ └── TransportMessage.Redis.cs
│ └── DotNetCore.CAP.SqlServer/
│ ├── CAP.EFOptions.cs
│ ├── CAP.Options.Extensions.cs
│ ├── CAP.SqlServerCapOptionsExtension.cs
│ ├── CAP.SqlServerOptions.cs
│ ├── Diagnostics/
│ │ ├── DiagnosticObserver.cs
│ │ ├── DiagnosticProcessorObserver.cs
│ │ └── IProcessingServer.DiagnosticRegister.cs
│ ├── DotNetCore.CAP.SqlServer.csproj
│ ├── ICapTransaction.SqlServer.cs
│ ├── IDataStorage.SqlServer.cs
│ ├── IDbConnection.Extensions.cs
│ ├── IDbContextTransaction.CAP.cs
│ ├── IMonitoringApi.SqlServer.cs
│ └── IStorageInitializer.SqlServer.cs
└── test/
├── DotNetCore.CAP.AzureServiceBus.Test/
│ ├── DotNetCore.CAP.AzureServiceBus.Test.csproj
│ ├── Helpers/
│ │ └── ServiceBusHelperTests.cs
│ ├── Producer/
│ │ └── ServiceBusProducerBuilderTests.cs
│ └── ServiceBusTransportTests.cs
├── DotNetCore.CAP.MultiModuleSubscriberTests/
│ ├── DotNetCore.CAP.MultiModuleSubscriberTests.csproj
│ └── SubscriberClass.cs
├── DotNetCore.CAP.MySql.Test/
│ ├── ConnectionUtil.cs
│ ├── DatabaseTestHost.cs
│ ├── DotNetCore.CAP.MySql.Test.csproj
│ ├── MySqlStorageConnectionTest.cs
│ ├── MySqlStorageTest.cs
│ └── TestHost.cs
└── DotNetCore.CAP.Test/
├── CAP.BuilderTest.cs
├── ConsumerServiceSelectorTest.cs
├── CustomConsumerSubscribeTest.cs
├── DispatcherTests.cs
├── DotNetCore.CAP.Test.csproj
├── FakeInMemoryQueue/
│ ├── CAP.FakeQueueOptionsExtension.cs
│ ├── CAP.Options.Extensions.cs
│ ├── ITransport.FakeInMemory.cs
│ ├── InMemoryConsumerClient.cs
│ ├── InMemoryConsumerClientFactory.cs
│ └── InMemoryQueue.cs
├── GenericConsumerServiceSelector.cs
├── HelperTest.cs
├── Helpers/
│ ├── ObservableCollectionExtensions.cs
│ ├── TestBootstrapService.cs
│ ├── TestHostedServiceExtensions.cs
│ ├── TestLogger.cs
│ ├── TestLoggingExtensions.cs
│ ├── TestLoggingProvider.cs
│ ├── TestMessageCollector.cs
│ ├── TestServiceCollectionExtensions.cs
│ └── TestThreadSafeMessageSender.cs
├── IntegrationTests/
│ ├── CancellationTokenSubscriberTest.cs
│ └── IntegrationTestBase.cs
├── MessageExtensionTest.cs
├── MessageTest.cs
├── SnowflakeIdTest.cs
├── SubscribeInvokerTest.cs
├── SubscribeInvokerWithCancellation.cs
└── SubscriberCollisionTests/
├── NewMethodTests.cs
├── OldMethodTests.cs
└── SubscriberClass.cs
================================================
FILE CONTENTS
================================================
================================================
FILE: .flubu
================================================
build/BuildScript.csproj
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: 🐞 Bug Report
description: Report a reproducible issue or unexpected behavior in CAP
title: "[Bug] "
labels: [bug]
assignees: []
body:
- type: markdown
attributes:
value: |
## Thanks for reporting a CAP issue!
Before submitting:
- Make sure you've read the documentation at [https://cap.dotnetcore.xyz](https://cap.dotnetcore.xyz).
- It's **recommended** to submit a PR directly for typos or trivial fixes.
- If this is a **feature request**, please use the *Feature Request* template instead.
- type: input
id: summary
attributes:
label: Summary
description: A short, clear summary of the bug.
placeholder: e.g. CAP fails to publish message after transaction commit
validations:
required: true
- type: textarea
id: reproduce_steps
attributes:
label: Steps to Reproduce
description: Describe how to reproduce the issue step by step.
placeholder: |
1. Configure CAP with MySQL and RabbitMQ
2. Publish message inside a transaction
3. Commit transaction
4. Observe error or missing message
validations:
required: true
- type: textarea
id: expected_behavior
attributes:
label: Expected Behavior
description: What should have happened?
placeholder: The message should be published and consumed successfully.
- type: textarea
id: actual_behavior
attributes:
label: Actual Behavior
description: What actually happened?
placeholder: Message not sent / consumer not triggered / error occurred
- type: textarea
id: logs
attributes:
label: Log Output
description: Paste the relevant CAP logs or exception stack traces here.
render: shell
placeholder: |
[Error] DotNetCore.CAP.Internal.SubscriberExecutor: Exception occurred while processing message
System.NullReferenceException: Object reference not set to an instance of an object
at DotNetCore.CAP...
- type: textarea
id: config
attributes:
label: CAP Configuration
description: Paste your CAP configuration or initialization code.
render: yaml
placeholder: |
{
"UseDashboard": true,
"UseMySql": "Server=localhost;Database=capdb;Uid=root;Pwd=123456;",
"UseRabbitMQ": "HostName=localhost"
}
- type: dropdown
id: transport
attributes:
label: Transport Used
description: Which message broker are you using?
options:
- RabbitMQ
- Kafka
- Pulsar
- Azure Service Bus
- InMemory
- Other
- type: dropdown
id: storage
attributes:
label: Storage Provider
description: Which storage provider are you using?
options:
- MySQL
- PostgreSQL
- SQL Server
- MongoDB
- InMemory
- Other
- type: input
id: environment
attributes:
label: Environment
description: OS, .NET version, and CAP version.
placeholder: e.g. Windows 11 / .NET 8.0 / CAP 7.2.1
- type: textarea
id: additional
attributes:
label: Additional Context
description: Add any other information, configuration, or screenshots that may help.
placeholder: e.g. This issue only occurs under heavy load or after multiple retries.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: ✨ Feature Request
description: Suggest a new feature or improvement for CAP
title: "[Feature] "
labels: [enhancement]
assignees: []
body:
- type: markdown
attributes:
value: |
## Thanks for contributing ideas to CAP!
Before submitting:
- Please make sure your idea isn’t already discussed in [Issues](https://github.com/dotnetcore/CAP/issues) or [Discussions](https://github.com/dotnetcore/CAP/discussions).
- For minor code changes (typos, docs, small improvements), submitting a PR directly is recommended.
- Be specific — describe the **problem**, not just the solution.
- type: input
id: summary
attributes:
label: Summary
description: A short, clear summary of your proposed feature.
placeholder: e.g. Add native support for Azure Event Hubs
validations:
required: true
- type: textarea
id: motivation
attributes:
label: Motivation / Problem
description: Explain why this feature is needed. What problem does it solve or what limitation does it remove?
placeholder: |
CAP currently doesn't support Azure Event Hubs as a transport layer.
This feature would allow easier integration with Azure cloud environments.
validations:
required: true
- type: textarea
id: proposed_solution
attributes:
label: Proposed Solution
description: Describe how you think the feature should work or be implemented.
placeholder: |
Add a new `UseEventHubs()` extension similar to `UseRabbitMQ()` and `UseKafka()`.
It should handle publish and consume operations via Azure SDK.
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Have you considered any workarounds or alternative approaches?
placeholder: |
We can currently bridge Event Hubs via Kafka protocol, but it adds complexity.
- type: textarea
id: impact
attributes:
label: Expected Impact
description: Describe how this feature would improve CAP or its usability.
placeholder: |
This would expand CAP’s support for Azure ecosystems and help enterprise users.
- type: dropdown
id: scope
attributes:
label: Feature Scope
description: What area of CAP does this feature affect most?
options:
- Core
- Transport (e.g., RabbitMQ, Kafka, Pulsar)
- Storage (e.g., MySQL, PostgreSQL, MongoDB)
- Dashboard / UI
- Documentation
- Other
- type: textarea
id: additional
attributes:
label: Additional Context
description: Add any other relevant details, links, or references.
placeholder: e.g. Related issue numbers, design documents, or PR references.
================================================
FILE: .github/ISSUE_TEMPLATE/question.yml
================================================
name: ❓ Question or Help
description: Ask a question or request usage help about CAP
title: "[Question] "
labels: [question]
assignees: []
body:
- type: markdown
attributes:
value: |
## Need help or have a question?
Before submitting, please:
- Read the documentation: [https://cap.dotnetcore.xyz](https://cap.dotnetcore.xyz)
- Check existing [Discussions](https://github.com/dotnetcore/CAP/discussions) and [Issues](https://github.com/dotnetcore/CAP/issues)
- Use this form for **usage questions**, **configuration issues**, or **how-to** discussions.
- If you think this is a **bug**, please use the *Bug Report* template instead.
- type: input
id: topic
attributes:
label: Question Summary
description: A short summary of what you need help with.
placeholder: e.g. How to retry messages when using RabbitMQ?
validations:
required: true
- type: textarea
id: context
attributes:
label: Context
description: Explain your question, what you have tried, and what you expected.
placeholder: |
I'm using CAP with MySQL and RabbitMQ.
The message was not retried after exception.
Is there a configuration to enable retry?
- type: textarea
id: code
attributes:
label: Relevant Code or Configuration
description: Provide related code snippets, config, or setup details.
render: csharp
placeholder: |
builder.Services.AddCap(x =>
{
x.UseRabbitMQ("localhost");
x.UseMySql("Server=127.0.0.1;Database=capdb;Uid=root;Pwd=123456;");
});
- type: dropdown
id: environment
attributes:
label: Environment
description: What environment are you using CAP in?
options:
- .NET 8.0
- .NET 7.0
- .NET 6.0
- Other
- type: textarea
id: additional
attributes:
label: Additional Information
description: Add any other context or reference links that may help answer your question.
placeholder: e.g. Related GitHub issue, article, or code sample link.
================================================
FILE: .github/pull_request_template.md
================================================
### Description:
_Provide a more detailed description of the changes made in this PR. Explain the reason behind the changes and their expected impact on the project._
#### Issue(s) addressed:
- [Link to the related issue(s), if any]
#### Changes:
- _List the major changes made in this PR, preferably in bullet points._
#### Affected components:
- _List the components of the project that are affected by this PR._
#### How to test:
_Provide a step-by-step guide on how to test your changes. Include any relevant information like environment setup, dependencies, etc._
### Additional notes (optional):
_Provide any additional notes or context that may be relevant to the changes._
### Checklist:
- [ ] I have tested my changes locally
- [ ] I have added necessary documentation (if applicable)
- [ ] I have updated the relevant tests (if applicable)
- [ ] My changes follow the project's code style guidelines
### Reviewers:
- _Mention any reviewers you would like to review your PR. This can be helpful if you know someone who is familiar with the part of the codebase you're working on._
================================================
FILE: .github/workflows/deploy-docs-and-dashboard.yml
================================================
name: docs & dashboard-dist
on:
push:
branches:
- master
jobs:
changes:
runs-on: ubuntu-latest
outputs:
docs: ${{ steps.filter.outputs.docs }}
dashboard: ${{ steps.filter.outputs.dashboard }}
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
with:
persist-credentials: false
- name: Check for changes 🎯
uses: dorny/paths-filter@v2
id: filter
with:
filters: |
docs:
- 'docs/**'
dashboard:
- 'src/DotNetCore.CAP.Dashboard/wwwroot/src/**'
build-dashboard-and-push:
needs: changes
if: ${{ needs.changes.outputs.dashboard == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/DotNetCore.CAP.Dashboard/wwwroot
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
with:
persist-credentials: false
- name: Use Node.js 🥽
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies 🧵
run: npm install
- name: Build to dist 🧨
run: npm run build
- name: Commit & Push dist changes 🚀
uses: actions-js/push@master
with:
branch: master
github_token: ${{ secrets.GITHUB_TOKEN }}
build-docs-and-deploy:
needs: changes
if: ${{ needs.changes.outputs.docs == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
with:
persist-credentials: false
- name: Deploy docs 🚀
uses: mhausenblas/mkdocs-deploy-gh-pages@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }}
CONFIG_FILE: docs/mkdocs.yml
================================================
FILE: .gitignore
================================================
node_modules
[Oo]bj/
[Bb]in/
TestResults/
.nuget/
_ReSharper.*/
packages/
artifacts/
PublishProfiles/
*.user
*.suo
*.cache
*.docstates
_ReSharper.*
*.[Rr]e[Ss]harper
*.DotSettings.user
nuget.exe
*k10.csproj
*.psess
*.vsp
*.pidb
*.userprefs
*DS_Store
*.ncrunchsolution
*.*sdf
*.ipch
*.sln.ide
.vs
.build/
/.idea/.idea.CAP
/.idea
Properties
/pack.bat
.vscode/*
site/
*.lock
================================================
FILE: CAP.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9B2AE124-6636-4DE9-83A3-70360DABD0C4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{57A8A8E5-5715-41BF-A0A6-46B819933FBC}"
ProjectSection(SolutionItems) = preProject
.flubu = .flubu
.gitignore = .gitignore
appveyor.yml = appveyor.yml
.github\workflows\deploy-docs-and-dashboard.yml = .github\workflows\deploy-docs-and-dashboard.yml
.github\ISSUE_TEMPLATE = .github\ISSUE_TEMPLATE
LICENSE.txt = LICENSE.txt
README.md = README.md
README.zh-cn.md = README.zh-cn.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP", "src\DotNetCore.CAP\DotNetCore.CAP.csproj", "{E8AF8611-0EA4-4B19-BC48-87C57A87DC66}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{3A6B6931-A123-477A-9469-8B468B5385AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Kafka", "src\DotNetCore.CAP.Kafka\DotNetCore.CAP.Kafka.csproj", "{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.RabbitMQ", "src\DotNetCore.CAP.RabbitMQ\DotNetCore.CAP.RabbitMQ.csproj", "{9961B80E-0718-4280-B2A0-271B003DE26B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{10C0818D-9160-4B80-BB86-DDE925B64D43}"
ProjectSection(SolutionItems) = preProject
src\Directory.Build.props = src\Directory.Build.props
build\version.props = build\version.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.SqlServer", "src\DotNetCore.CAP.SqlServer\DotNetCore.CAP.SqlServer.csproj", "{3B577468-6792-4EF1-9237-15180B176A24}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MySql", "src\DotNetCore.CAP.MySql\DotNetCore.CAP.MySql.csproj", "{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MySql.Test", "test\DotNetCore.CAP.MySql.Test\DotNetCore.CAP.MySql.Test.csproj", "{80A84F62-1558-427B-BA74-B47AA8A665B5}"
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}") = "DotNetCore.CAP.PostgreSql", "src\DotNetCore.CAP.PostgreSql\DotNetCore.CAP.PostgreSql.csproj", "{82C403AB-ED68-4084-9A1D-11334F9F08F9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MongoDB", "src\DotNetCore.CAP.MongoDB\DotNetCore.CAP.MongoDB.csproj", "{77C0AC02-C44B-49D5-B969-7D5305FC20A5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.MongoDB", "samples\Sample.RabbitMQ.MongoDB\Sample.RabbitMQ.MongoDB.csproj", "{4473DE19-E8D2-4B57-80A8-C8AAA2BFA20F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.AzureServiceBus", "src\DotNetCore.CAP.AzureServiceBus\DotNetCore.CAP.AzureServiceBus.csproj", "{63B2A464-FBEA-42FB-8EFA-98AFA39FC920}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Dashboard", "src\DotNetCore.CAP.Dashboard\DotNetCore.CAP.Dashboard.csproj", "{56FB261C-67AF-4715-9A46-4FA4FAB91B2C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.SqlServer", "samples\Sample.RabbitMQ.SqlServer\Sample.RabbitMQ.SqlServer.csproj", "{F6C5C676-AF05-46D5-A45D-442137B31898}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.PostgreSql", "samples\Sample.Kafka.PostgreSql\Sample.Kafka.PostgreSql.csproj", "{F1EF1D26-8A6B-403E-85B0-250DF44A4A7C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildScript", "build\BuildScript.csproj", "{F8EF381A-FE83-40B3-A63D-09D83851B0FB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.InMemoryStorage", "src\DotNetCore.CAP.InMemoryStorage\DotNetCore.CAP.InMemoryStorage.csproj", "{93176BAE-914B-4BED-9DE3-01FFB4F27FC5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Test", "test\DotNetCore.CAP.Test\DotNetCore.CAP.Test.csproj", "{75CC45E6-BF06-40F4-977D-10DCC05B2EFA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.ConsoleApp", "samples\Sample.ConsoleApp\Sample.ConsoleApp.csproj", "{2B0F467E-ABBD-4A51-BF38-D4F609DB6266}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.AmazonSQS", "src\DotNetCore.CAP.AmazonSQS\DotNetCore.CAP.AmazonSQS.csproj", "{43475E00-51B7-443D-BC2D-FC21F9D8A0B4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.AmazonSQS.InMemory", "samples\Sample.AmazonSQS.InMemory\Sample.AmazonSQS.InMemory.csproj", "{B187DD15-092D-4B72-9807-50856607D237}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.NATS", "src\DotNetCore.CAP.NATS\DotNetCore.CAP.NATS.csproj", "{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Redis.SqlServer", "samples\Samples.Redis.SqlServer\Samples.Redis.SqlServer.csproj", "{375AF85D-8C81-47C6-BE5B-D0874D4971EA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.RedisStreams", "src\DotNetCore.CAP.RedisStreams\DotNetCore.CAP.RedisStreams.csproj", "{54458B54-49CC-454C-82B2-4AED681D9D07}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Dashboard.Auth", "samples\Sample.Dashboard.Auth\Sample.Dashboard.Auth.csproj", "{6E059983-DE89-4D53-88F5-D9083BCE257F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.MultiModuleSubscriberTests", "test\DotNetCore.CAP.MultiModuleSubscriberTests\DotNetCore.CAP.MultiModuleSubscriberTests.csproj", "{23684403-7DA8-489A-8A1E-8056D7683E18}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Pulsar", "src\DotNetCore.CAP.Pulsar\DotNetCore.CAP.Pulsar.csproj", "{AB7A10CB-2C7E-49CE-AA21-893772FF6546}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Pulsar.InMemory", "samples\Sample.Pulsar.InMemory\Sample.Pulsar.InMemory.csproj", "{B1D95CCD-0123-41D4-8CCB-9F834ED8D5C5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.OpenTelemetry", "src\DotNetCore.CAP.OpenTelemetry\DotNetCore.CAP.OpenTelemetry.csproj", "{83DDB126-A00B-4064-86E7-568322CA67EC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.AzureServiceBus.InMemory", "samples\Sample.AzureServiceBus.InMemory\Sample.AzureServiceBus.InMemory.csproj", "{0C734FB2-7D75-4FF3-B564-1E50E6280B14}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.AzureServiceBus.Test", "test\DotNetCore.CAP.AzureServiceBus.Test\DotNetCore.CAP.AzureServiceBus.Test.csproj", "{D9681967-DAC2-43EF-999F-3727F1046711}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Dashboard.Jwt", "samples\Sample.Dashboard.Jwt\Sample.Dashboard.Jwt.csproj", "{F739A8C9-565F-4B1D-8F91-FEE056C03FBD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Dashboard.K8s", "src\DotNetCore.CAP.Dashboard.K8s\DotNetCore.CAP.Dashboard.K8s.csproj", "{48655118-CEC3-4BD9-B510-64C1195C2729}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8AF8611-0EA4-4B19-BC48-87C57A87DC66}.Release|Any CPU.Build.0 = Release|Any CPU
{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD}.Release|Any CPU.Build.0 = Release|Any CPU
{9961B80E-0718-4280-B2A0-271B003DE26B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9961B80E-0718-4280-B2A0-271B003DE26B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9961B80E-0718-4280-B2A0-271B003DE26B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9961B80E-0718-4280-B2A0-271B003DE26B}.Release|Any CPU.Build.0 = Release|Any CPU
{3B577468-6792-4EF1-9237-15180B176A24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B577468-6792-4EF1-9237-15180B176A24}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B577468-6792-4EF1-9237-15180B176A24}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B577468-6792-4EF1-9237-15180B176A24}.Release|Any CPU.Build.0 = Release|Any CPU
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B}.Release|Any CPU.Build.0 = Release|Any CPU
{80A84F62-1558-427B-BA74-B47AA8A665B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80A84F62-1558-427B-BA74-B47AA8A665B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80A84F62-1558-427B-BA74-B47AA8A665B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{80A84F62-1558-427B-BA74-B47AA8A665B5}.Release|Any CPU.Build.0 = Release|Any CPU
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873}.Release|Any CPU.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
{77C0AC02-C44B-49D5-B969-7D5305FC20A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77C0AC02-C44B-49D5-B969-7D5305FC20A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77C0AC02-C44B-49D5-B969-7D5305FC20A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77C0AC02-C44B-49D5-B969-7D5305FC20A5}.Release|Any CPU.Build.0 = Release|Any CPU
{4473DE19-E8D2-4B57-80A8-C8AAA2BFA20F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4473DE19-E8D2-4B57-80A8-C8AAA2BFA20F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4473DE19-E8D2-4B57-80A8-C8AAA2BFA20F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4473DE19-E8D2-4B57-80A8-C8AAA2BFA20F}.Release|Any CPU.Build.0 = Release|Any CPU
{63B2A464-FBEA-42FB-8EFA-98AFA39FC920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63B2A464-FBEA-42FB-8EFA-98AFA39FC920}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63B2A464-FBEA-42FB-8EFA-98AFA39FC920}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63B2A464-FBEA-42FB-8EFA-98AFA39FC920}.Release|Any CPU.Build.0 = Release|Any CPU
{56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Debug|Any CPU.ActiveCfg = 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.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.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.Build.0 = Release|Any CPU
{F1EF1D26-8A6B-403E-85B0-250DF44A4A7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1EF1D26-8A6B-403E-85B0-250DF44A4A7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1EF1D26-8A6B-403E-85B0-250DF44A4A7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1EF1D26-8A6B-403E-85B0-250DF44A4A7C}.Release|Any CPU.Build.0 = Release|Any CPU
{F8EF381A-FE83-40B3-A63D-09D83851B0FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8EF381A-FE83-40B3-A63D-09D83851B0FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8EF381A-FE83-40B3-A63D-09D83851B0FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8EF381A-FE83-40B3-A63D-09D83851B0FB}.Release|Any CPU.Build.0 = Release|Any CPU
{93176BAE-914B-4BED-9DE3-01FFB4F27FC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93176BAE-914B-4BED-9DE3-01FFB4F27FC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93176BAE-914B-4BED-9DE3-01FFB4F27FC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93176BAE-914B-4BED-9DE3-01FFB4F27FC5}.Release|Any CPU.Build.0 = Release|Any CPU
{75CC45E6-BF06-40F4-977D-10DCC05B2EFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75CC45E6-BF06-40F4-977D-10DCC05B2EFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75CC45E6-BF06-40F4-977D-10DCC05B2EFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75CC45E6-BF06-40F4-977D-10DCC05B2EFA}.Release|Any CPU.Build.0 = Release|Any CPU
{2B0F467E-ABBD-4A51-BF38-D4F609DB6266}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B0F467E-ABBD-4A51-BF38-D4F609DB6266}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B0F467E-ABBD-4A51-BF38-D4F609DB6266}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B0F467E-ABBD-4A51-BF38-D4F609DB6266}.Release|Any CPU.Build.0 = Release|Any CPU
{43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Debug|Any CPU.ActiveCfg = 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.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
{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07}.Release|Any CPU.Build.0 = Release|Any CPU
{375AF85D-8C81-47C6-BE5B-D0874D4971EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{375AF85D-8C81-47C6-BE5B-D0874D4971EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{375AF85D-8C81-47C6-BE5B-D0874D4971EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{375AF85D-8C81-47C6-BE5B-D0874D4971EA}.Release|Any CPU.Build.0 = Release|Any CPU
{54458B54-49CC-454C-82B2-4AED681D9D07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{54458B54-49CC-454C-82B2-4AED681D9D07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{54458B54-49CC-454C-82B2-4AED681D9D07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54458B54-49CC-454C-82B2-4AED681D9D07}.Release|Any CPU.Build.0 = Release|Any CPU
{6E059983-DE89-4D53-88F5-D9083BCE257F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6E059983-DE89-4D53-88F5-D9083BCE257F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6E059983-DE89-4D53-88F5-D9083BCE257F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E059983-DE89-4D53-88F5-D9083BCE257F}.Release|Any CPU.Build.0 = Release|Any CPU
{23684403-7DA8-489A-8A1E-8056D7683E18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23684403-7DA8-489A-8A1E-8056D7683E18}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23684403-7DA8-489A-8A1E-8056D7683E18}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23684403-7DA8-489A-8A1E-8056D7683E18}.Release|Any CPU.Build.0 = Release|Any CPU
{AB7A10CB-2C7E-49CE-AA21-893772FF6546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AB7A10CB-2C7E-49CE-AA21-893772FF6546}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB7A10CB-2C7E-49CE-AA21-893772FF6546}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AB7A10CB-2C7E-49CE-AA21-893772FF6546}.Release|Any CPU.Build.0 = Release|Any CPU
{B1D95CCD-0123-41D4-8CCB-9F834ED8D5C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1D95CCD-0123-41D4-8CCB-9F834ED8D5C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1D95CCD-0123-41D4-8CCB-9F834ED8D5C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1D95CCD-0123-41D4-8CCB-9F834ED8D5C5}.Release|Any CPU.Build.0 = Release|Any CPU
{83DDB126-A00B-4064-86E7-568322CA67EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{83DDB126-A00B-4064-86E7-568322CA67EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83DDB126-A00B-4064-86E7-568322CA67EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{83DDB126-A00B-4064-86E7-568322CA67EC}.Release|Any CPU.Build.0 = Release|Any CPU
{0C734FB2-7D75-4FF3-B564-1E50E6280B14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C734FB2-7D75-4FF3-B564-1E50E6280B14}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C734FB2-7D75-4FF3-B564-1E50E6280B14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C734FB2-7D75-4FF3-B564-1E50E6280B14}.Release|Any CPU.Build.0 = Release|Any CPU
{D9681967-DAC2-43EF-999F-3727F1046711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9681967-DAC2-43EF-999F-3727F1046711}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9681967-DAC2-43EF-999F-3727F1046711}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9681967-DAC2-43EF-999F-3727F1046711}.Release|Any CPU.Build.0 = Release|Any CPU
{F739A8C9-565F-4B1D-8F91-FEE056C03FBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F739A8C9-565F-4B1D-8F91-FEE056C03FBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F739A8C9-565F-4B1D-8F91-FEE056C03FBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F739A8C9-565F-4B1D-8F91-FEE056C03FBD}.Release|Any CPU.Build.0 = Release|Any CPU
{48655118-CEC3-4BD9-B510-64C1195C2729}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48655118-CEC3-4BD9-B510-64C1195C2729}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48655118-CEC3-4BD9-B510-64C1195C2729}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48655118-CEC3-4BD9-B510-64C1195C2729}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E8AF8611-0EA4-4B19-BC48-87C57A87DC66} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{C42CDE33-0878-4BA0-96F2-4CB7C8FDEAAD} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{9961B80E-0718-4280-B2A0-271B003DE26B} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{3B577468-6792-4EF1-9237-15180B176A24} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{FA15685A-778A-4D2A-A2FE-27FAD2FFA65B} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{80A84F62-1558-427B-BA74-B47AA8A665B5} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}
{9F3F9BFE-7B6A-4A7A-A6E6-8B517D611873} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{82C403AB-ED68-4084-9A1D-11334F9F08F9} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{77C0AC02-C44B-49D5-B969-7D5305FC20A5} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{4473DE19-E8D2-4B57-80A8-C8AAA2BFA20F} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{63B2A464-FBEA-42FB-8EFA-98AFA39FC920} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{56FB261C-67AF-4715-9A46-4FA4FAB91B2C} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{F6C5C676-AF05-46D5-A45D-442137B31898} = {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}
{93176BAE-914B-4BED-9DE3-01FFB4F27FC5} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{75CC45E6-BF06-40F4-977D-10DCC05B2EFA} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}
{2B0F467E-ABBD-4A51-BF38-D4F609DB6266} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{43475E00-51B7-443D-BC2D-FC21F9D8A0B4} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{B187DD15-092D-4B72-9807-50856607D237} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{375AF85D-8C81-47C6-BE5B-D0874D4971EA} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{54458B54-49CC-454C-82B2-4AED681D9D07} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{6E059983-DE89-4D53-88F5-D9083BCE257F} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{23684403-7DA8-489A-8A1E-8056D7683E18} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}
{AB7A10CB-2C7E-49CE-AA21-893772FF6546} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{B1D95CCD-0123-41D4-8CCB-9F834ED8D5C5} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{83DDB126-A00B-4064-86E7-568322CA67EC} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
{0C734FB2-7D75-4FF3-B564-1E50E6280B14} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{D9681967-DAC2-43EF-999F-3727F1046711} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0}
{F739A8C9-565F-4B1D-8F91-FEE056C03FBD} = {3A6B6931-A123-477A-9469-8B468B5385AF}
{48655118-CEC3-4BD9-B510-64C1195C2729} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB}
EndGlobalSection
EndGlobal
================================================
FILE: CAP.sln.DotSettings
================================================
DBNATSSNSTrueTrueTrue
================================================
FILE: LICENSE.txt
================================================
MIT License
Copyright (c) 2016 Savorboard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# CAP [中文](https://github.com/dotnetcore/CAP/blob/master/README.zh-cn.md)
[](https://github.com/dotnetcore/CAP/actions/workflows/deploy-docs-and-dashboard.yml)
[](https://ci.appveyor.com/project/yang-xiaodong/cap/branch/master)
[](https://www.nuget.org/packages/DotNetCore.CAP/)
[](https://www.nuget.org/packages/DotNetCore.CAP/)
[](https://github.com/dotnetcore)
[](https://raw.githubusercontent.com/dotnetcore/CAP/master/LICENSE.txt)
CAP is a .NET library that provides a lightweight, easy-to-use, and efficient solution for distributed transactions and event bus integration.
When building SOA or Microservice-based systems, services often need to be integrated via events. However, simply using a message queue cannot guarantee reliability. CAP leverages a local message table, integrated with your current database, to solve exceptions that can occur during distributed system communications. This ensures that event messages are never lost.
You can also use CAP as a standalone EventBus. It offers a simplified approach to event publishing and subscribing without requiring you to inherit or implement any specific interfaces.
## Key Features
* **Core Functionality**
* **Distributed Transactions**: Guarantees data consistency across microservices using a local message table (Outbox Pattern).
* **Event Bus**: High-performance, lightweight event bus for decoupled communication.
* **Guaranteed Delivery**: Ensures messages are never lost, with automatic retries for failed messages.
* **Advanced Messaging**
* **Delayed Messages**: Native support for publishing messages with a delay, without relying on message queue features.
* **Flexible Subscriptions**: Supports attribute-based, wildcard (`*`, `#`), and partial topic subscriptions.
* **Consumer Groups & Fan-Out**: Easily implement competing consumer or fan-out patterns for load balancing or broadcasting.
* **Parallel & Serial Processing**: Configure consumers for high-throughput parallel processing or ordered sequential execution.
* **Backpressure Mechanism**: Automatically manages processing speed to prevent memory overload (OOM) under high load.
* **Extensibility & Integration**
* **Pluggable Architecture**: Supports a wide range of message queues (RabbitMQ, Kafka, Azure Service Bus, etc.) and databases (SQL Server, PostgreSQL, MongoDB, etc.).
* **Heterogeneous Systems**: Provides mechanisms to integrate with non-CAP or legacy systems.
* **Customizable Filters & Serialization**: Intercept the processing pipeline with custom filters and support various serialization formats.
* **Monitoring & Observability**
* **Real-time Dashboard**: A built-in web dashboard to monitor messages, view status, and manually retry.
* **Service Discovery**: Integrates with Consul and Kubernetes for node discovery in a distributed environment.
* **OpenTelemetry Support**: Built-in instrumentation for distributed tracing, providing end-to-end visibility.
## Architecture Overview

> CAP implements the **Outbox Pattern** as described in the [eShop on .NET ebook](https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/subscribe-events#designing-atomicity-and-resiliency-when-publishing-to-the-event-bus).
## Getting Started
### 1. Installation
Install the main CAP package into your project using NuGet.
```shell
PM> Install-Package DotNetCore.CAP
```
Next, install the desired transport and storage providers.
**Transports (Message Queues):**
```shell
PM> Install-Package DotNetCore.CAP.Kafka
PM> Install-Package DotNetCore.CAP.RabbitMQ
PM> Install-Package DotNetCore.CAP.AzureServiceBus
PM> Install-Package DotNetCore.CAP.AmazonSQS
PM> Install-Package DotNetCore.CAP.NATS
PM> Install-Package DotNetCore.CAP.RedisStreams
PM> Install-Package DotNetCore.CAP.Pulsar
```
**Storage (Databases):**
The event log table will be integrated into the database you select.
```shell
PM> Install-Package DotNetCore.CAP.SqlServer
PM> Install-Package DotNetCore.CAP.MySql
PM> Install-Package DotNetCore.CAP.PostgreSql
PM> Install-Package DotNetCore.CAP.MongoDB // Requires MongoDB 4.0+ cluster
```
### 2. Configuration
Configure CAP in your `Startup.cs` or `Program.cs`.
```csharp
public void ConfigureServices(IServiceCollection services)
{
// If you are using EF as the ORM
services.AddDbContext();
// If you are using MongoDB
services.AddSingleton(new MongoClient("..."));
services.AddCap(x =>
{
// Using Entity Framework
// CAP can auto-discover the connection string
x.UseEntityFramework();
// Using ADO.NET
x.UseSqlServer("Your ConnectionString");
x.UseMySql("Your ConnectionString");
x.UsePostgreSql("Your ConnectionString");
// Using MongoDB (requires a 4.0+ cluster)
x.UseMongoDB("Your ConnectionString");
// Choose your message transport
x.UseRabbitMQ("HostName");
x.UseKafka("ConnectionString");
x.UseAzureServiceBus("ConnectionString");
x.UseAmazonSQS(options => { /* ... */ });
x.UseNATS("ConnectionString");
x.UsePulsar("ConnectionString");
x.UseRedisStreams("ConnectionString");
});
}
```
### 3. Publish Messages
Inject `ICapPublisher` into your controller or service to publish events. As of version 7.0, you can also publish delayed messages.
```csharp
public class PublishController : Controller
{
private readonly ICapPublisher _capBus;
public PublishController(ICapPublisher capPublisher)
{
_capBus = capPublisher;
}
[Route("~/adonet/transaction")]
public IActionResult AdonetWithTransaction()
{
using (var connection = new MySqlConnection(ConnectionString))
{
// Start a transaction with auto-commit enabled
using (var transaction = connection.BeginTransaction(_capBus, autoCommit: true))
{
// Your business logic...
_capBus.Publish("xxx.services.show.time", DateTime.Now);
}
}
return Ok();
}
[Route("~/ef/transaction")]
public IActionResult EntityFrameworkWithTransaction([FromServices] AppDbContext dbContext)
{
using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: true))
{
// Your business logic...
_capBus.Publish("xxx.services.show.time", DateTime.Now);
}
return Ok();
}
[Route("~/publish/delay")]
public async Task PublishWithDelay()
{
// Publish a message with a 30-second delay
await _capBus.PublishDelayAsync(TimeSpan.FromSeconds(30), "xxx.services.show.time", DateTime.Now);
return Ok();
}
}
```
### 4. Subscribe to Messages
#### In a Controller Action
Add the `[CapSubscribe]` attribute to a controller action to subscribe to a topic.
```csharp
public class SubscriptionController : Controller
{
[CapSubscribe("xxx.services.show.time")]
public void CheckReceivedMessage(DateTime messageTime)
{
Console.WriteLine($"Message received: {messageTime}");
}
}
```
#### In a Business Logic Service
If your subscriber is not in a controller, the class must implement the `ICapSubscribe` interface.
```csharp
namespace BusinessCode.Service
{
public interface ISubscriberService
{
void CheckReceivedMessage(DateTime datetime);
}
public class SubscriberService : ISubscriberService, ICapSubscribe
{
[CapSubscribe("xxx.services.show.time")]
public void CheckReceivedMessage(DateTime datetime)
{
// Handle the message
}
}
}
```
Remember to register your service in `Startup.cs`:
```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient();
services.AddCap(x =>
{
// ...
});
}
```
#### Asynchronous Subscriptions
For async operations, your subscription method should return a `Task` and can accept a `CancellationToken`.
```csharp
public class AsyncSubscriber : ICapSubscribe
{
[CapSubscribe("topic.name")]
public async Task ProcessAsync(Message message, CancellationToken cancellationToken)
{
await SomeOperationAsync(message, cancellationToken);
}
}
```
#### Partial Topic Subscriptions
Group topic subscriptions by defining a partial topic on the class level. The final topic will be a combination of the class-level and method-level topics. In this example, the `Create` method subscribes to `customers.create`.
```csharp
[CapSubscribe("customers")]
public class CustomersSubscriberService : ICapSubscribe
{
[CapSubscribe("create", isPartial: true)]
public void Create(Customer customer)
{
// ...
}
}
```
#### Subscription Groups
Subscription groups are similar to consumer groups in Kafka. They allow you to load-balance message processing across multiple instances of a service.
By default, CAP uses the assembly name as the group name. If multiple subscribers in the same group subscribe to the same topic, only one will receive the message (competing consumers). If they are in different groups, all will receive the message (fan-out).
You can specify a group directly in the attribute:
```csharp
[CapSubscribe("xxx.services.show.time", Group = "group1")]
public void ShowTime1(DateTime datetime)
{
// ...
}
[CapSubscribe("xxx.services.show.time", Group = "group2")]
public void ShowTime2(DateTime datetime)
{
// ...
}
```
You can also set a default group name in the configuration:
```csharp
services.AddCap(x =>
{
x.DefaultGroup = "my-default-group";
});
```
### Azure Service Bus Emulator Support
The [Azure Service Bus Emulator](https://learn.microsoft.com/en-us/azure/service-bus-messaging/overview-emulator) uses separate ports for AMQP messaging (5672) and the HTTP Admin API (5300). Because CAP uses a single connection string for both the `ServiceBusClient` (AMQP) and the `ServiceBusAdministrationClient` (HTTP), it cannot target both ports simultaneously.
To work around this, set `AutoProvision` to `false` to skip automatic creation of topics, subscriptions, and rules via the Admin API. You must pre-create the required entities in the emulator's configuration instead.
```csharp
services.AddCap(x =>
{
x.UseAzureServiceBus(opt =>
{
opt.ConnectionString = "Endpoint=sb://localhost;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;";
opt.AutoProvision = false;
});
});
```
> **Note:** When `AutoProvision` is `false`, topics, subscriptions, and subscription filter rules must already exist before the application starts. This option is also useful when entities are managed externally (e.g., via Infrastructure as Code).
## Dashboard
CAP provides a real-time dashboard to view sent and received messages and their status.
```shell
PM> Install-Package DotNetCore.CAP.Dashboard
```
The dashboard is accessible by default at `http://localhost:xxx/cap`. You can customize the path via options: `x.UseDashboard(opt => { opt.PathMatch = "/my-cap"; });`.
For distributed environments, the dashboard supports service discovery to view data from all nodes.
- **Consul:** [View Consul config docs](https://cap.dotnetcore.xyz/user-guide/en/monitoring/consul/)
- **Kubernetes:** Use the `DotNetCore.CAP.Dashboard.K8s` package. [View Kubernetes config docs](https://cap.dotnetcore.xyz/user-guide/en/monitoring/kubernetes/)
## Contribute
We welcome contributions! Participating in discussions, reporting issues, and submitting pull requests are all great ways to help. Please read our [contributing guidelines](CONTRIBUTING.md) (we can create this file if it doesn't exist) to get started.
### License
CAP is licensed under the [MIT License](https://github.com/dotnetcore/CAP/blob/master/LICENSE.txt).
================================================
FILE: README.zh-cn.md
================================================
================================================
FILE: samples/Sample.Dashboard.Jwt/wwwroot/js/site.js
================================================
// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
function logIn() {
const userName = $('#userName').val();
const password = $('#password').val();
$.ajax({
method: "POST",
url: "security/createToken",
data: JSON.stringify({ userName, password }),
contentType: "application/json; charset=utf-8",
dataType: "json"
})
.done(token => {
window.open(`/cap/index.html?access_token=${token}`, '_blank')
});
}
================================================
FILE: samples/Sample.Kafka.PostgreSql/AppDbContext.cs
================================================
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using DotNetCore.CAP;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
namespace Sample.Kafka.PostgreSql
{
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}";
}
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet Persons { get; set; }
}
#pragma warning disable EF1001 // Internal EF Core API usage.
public class CapNpgsqlRelationalConnection : NpgsqlRelationalConnection
{
private readonly ICapPublisher _cap;
protected CapNpgsqlRelationalConnection(RelationalConnectionDependencies dependencies, DbDataSource dataSource) : base(dependencies, dataSource)
{
_cap = dependencies.CurrentContext.Context.GetService();
}
#pragma warning restore EF1001
public override void CommitTransaction()
{
if (_cap.Transaction != null)
{
_cap.Transaction.Commit();
}
else
{
base.CommitTransaction();
}
}
public override Task CommitTransactionAsync(CancellationToken cancellationToken = default)
{
if (_cap.Transaction != null)
{
return _cap.Transaction.CommitAsync(cancellationToken);
}
else
{
return base.CommitTransactionAsync(cancellationToken);
}
}
public override void RollbackTransaction()
{
if (_cap.Transaction != null)
{
_cap.Transaction.Rollback();
}
else
{
base.RollbackTransaction();
}
}
public override Task RollbackTransactionAsync(CancellationToken cancellationToken = default)
{
if (_cap.Transaction != null)
{
return _cap.Transaction.RollbackAsync(cancellationToken);
}
else
{
return base.RollbackTransactionAsync(cancellationToken);
}
}
}
}
================================================
FILE: samples/Sample.Kafka.PostgreSql/Controllers/ValuesController.cs
================================================
using System;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Npgsql;
namespace Sample.Kafka.PostgreSql.Controllers
{
[Route("api/[controller]")]
public class ValuesController(ICapPublisher producer) : Controller, ICapSubscribe
{
[Route("~/control/start")]
public async Task Start([FromServices] IBootstrapper bootstrapper)
{
await bootstrapper.BootstrapAsync();
return Ok();
}
[Route("~/control/stop")]
public async Task Stop([FromServices] IBootstrapper bootstrapper)
{
await bootstrapper.DisposeAsync();
return Ok();
}
[Route("~/delay/{delaySeconds:int}")]
public async Task Delay(int delaySeconds)
{
await producer.PublishDelayAsync(TimeSpan.FromSeconds(delaySeconds), "sample.kafka.postgrsql", DateTime.Now);
return Ok();
}
[Route("~/without/transaction")]
public async Task WithoutTransaction()
{
await producer.PublishAsync("sample.kafka.postgrsql", DateTime.Now);
return Ok();
}
[Route("~/adonet/transaction")]
public IActionResult AdonetWithTransaction()
{
using (var connection = new NpgsqlConnection(AppConstants.DbConnectionString))
{
using (var transaction = connection.BeginTransaction(producer, autoCommit: false))
{
//your business code
connection.Execute("INSERT INTO \"Persons\"(\"Name\",\"Age\",\"CreateTime\") VALUES('Lucy',25, NOW())", transaction: (IDbTransaction)transaction.DbTransaction);
producer.Publish("sample.kafka.postgrsql", DateTime.Now);
transaction.Commit();
}
}
producer.Publish("sample.kafka.postgrsql", DateTime.Now);
return Ok();
}
[Route("~/ef/transaction")]
public IActionResult EntityFrameworkWithTransaction([FromServices] AppDbContext dbContext)
{
using (dbContext.Database.BeginTransaction(producer, false))
{
dbContext.Persons.Add(new Person() { Name = "ef.transaction", Age = 11 });
dbContext.SaveChanges();
producer.Publish("sample.kafka.postgrsql", DateTime.UtcNow);
dbContext.Database.CommitTransaction();
}
return Ok();
}
[CapSubscribe("sample.kafka.postgrsql")]
public void Test2(DateTime value)
{
Console.WriteLine("Subscriber output message: " + value);
}
}
}
================================================
FILE: samples/Sample.Kafka.PostgreSql/Program.cs
================================================
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Sample.Kafka.PostgreSql;
var builder = WebApplication.CreateBuilder(args);
// Configure services
builder.Services.AddDbContext((sp, opt) =>
{
opt.UseNpgsql(AppConstants.DbConnectionString)
.ReplaceService();
});
builder.Services.AddCap(x =>
{
//x.UseEntityFramework();
//docker run --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword -d postgres
x.UsePostgreSql(AppConstants.DbConnectionString);
/* //Run Kafka Docker Container (Powershell)
docker run -d `
--name kafka `
-p 9092:9092 `
-e KAFKA_NODE_ID=1 `
-e KAFKA_PROCESS_ROLES=broker,controller `
-e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092,CONTROLLER://:9093 `
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092 `
-e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER `
-e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT `
-e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 `
-e KAFKA_LOG_DIRS=/var/lib/kafka/data `
-e KAFKA_AUTO_CREATE_TOPICS_ENABLE=true `
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 `
-e KAFKA_OFFSETS_TOPIC_MIN_ISR=1 `
-e KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1 `
-e KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1 `
apache/kafka:3.7.0
*/
x.UseKafka("127.0.0.1:9092");
x.UseDashboard();
});
builder.Services.AddControllers();
var app = builder.Build();
// Configure middleware pipeline
app.UseRouting();
app.MapControllers();
app.Run();
public static class AppConstants
{
public const string DbConnectionString = "User ID=postgres;Password=mysecretpassword;Host=127.0.0.1;Port=5432;Database=postgres;";
}
================================================
FILE: samples/Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj
================================================
net8.0NU1701NU1701
================================================
FILE: samples/Sample.Kafka.PostgreSql/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"DotNetCore.CAP": "Debug"
}
}
}
================================================
FILE: samples/Sample.Pulsar.InMemory/Controllers/ValuesController.cs
================================================
using System;
using System.Threading.Tasks;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Mvc;
namespace Sample.Pulsar.InMemory.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller, ICapSubscribe
{
private readonly ICapPublisher _capBus;
public ValuesController(ICapPublisher producer)
{
_capBus = producer;
}
[Route("~/without/transaction")]
public async Task WithoutTransaction()
{
await _capBus.PublishAsync("persistent://public/default/captesttopic", DateTime.Now);
return Ok();
}
[CapSubscribe("persistent://public/default/captesttopic")]
public void Test2T2(string value)
{
Console.WriteLine("Subscriber output message: " + value);
}
}
}
================================================
FILE: samples/Sample.Pulsar.InMemory/Program.cs
================================================
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Configure services
string pulsarUri = builder.Configuration.GetValue("AppSettings:PulsarUri", "pulsar://localhost:6650");
builder.Services.AddCap(x =>
{
x.UseInMemoryStorage();
x.UsePulsar(pulsarUri);
x.UseDashboard();
});
builder.Services.AddControllers();
var app = builder.Build();
// Configure middleware pipeline
app.UseRouting();
app.MapControllers();
app.Run();
================================================
FILE: samples/Sample.Pulsar.InMemory/Sample.Pulsar.InMemory.csproj
================================================
net10.0
================================================
FILE: samples/Sample.Pulsar.InMemory/appsettings.json
================================================
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug"
}
},
"AppSettings": {
"PulsarUri": "pulsar://localhost:6650",
"PulsarTopic": "persistent://public/default/captesttopic"
}
}
================================================
FILE: samples/Sample.RabbitMQ.MongoDB/Controllers/ValuesController.cs
================================================
using System;
using System.Threading.Tasks;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using MongoDB.Driver;
namespace Sample.RabbitMQ.MongoDB.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IMongoClient _client;
private readonly ICapPublisher _capBus;
public ValuesController(IMongoClient client, ICapPublisher capBus)
{
_client = client;
_capBus = capBus;
}
[Route("~/without/transaction")]
public IActionResult WithoutTransaction()
{
_capBus.PublishAsync("sample.rabbitmq.mongodb", DateTime.Now);
return Ok();
}
[Route("~/delay/{delaySeconds:int}")]
public async Task Delay(int delaySeconds)
{
await _capBus.PublishDelayAsync(TimeSpan.FromSeconds(delaySeconds), "sample.rabbitmq.mongodb", DateTime.Now);
return Ok();
}
[Route("~/transaction/not/autocommit")]
public IActionResult PublishNotAutoCommit()
{
//NOTE: before your test, your need to create database and collection at first
//注意:MongoDB 不能在事务中创建数据库和集合,所以你需要单独创建它们,模拟一条记录插入则会自动创建
//var mycollection = _client.GetDatabase("test").GetCollection("test.collection");
//mycollection.InsertOne(new BsonDocument { { "test", "test" } });
using (var session = _client.StartTransaction(_capBus, autoCommit: false))
{
var collection = _client.GetDatabase("test").GetCollection("test.collection");
collection.InsertOne(session, new BsonDocument { { "hello", "world" } });
_capBus.Publish("sample.rabbitmq.mongodb", DateTime.Now);
session.CommitTransaction();
}
return Ok();
}
[Route("~/transaction/autocommit")]
public IActionResult PublishWithoutTrans()
{
//NOTE: before your test, your need to create database and collection at first
//注意:MongoDB 不能在事务中创建数据库和集合,所以你需要单独创建它们,模拟一条记录插入则会自动创建
//var mycollection = _client.GetDatabase("test").GetCollection("test.collection");
//mycollection.InsertOne(new BsonDocument { { "test", "test" } });
using (var session = _client.StartTransaction(_capBus, autoCommit: true))
{
var collection = _client.GetDatabase("test").GetCollection("test.collection");
collection.InsertOne(session, new BsonDocument { { "hello", "world" } });
_capBus.Publish("sample.rabbitmq.mongodb", DateTime.Now);
}
return Ok();
}
[NonAction]
[CapSubscribe("sample.rabbitmq.mongodb")]
public void ReceiveMessage(DateTime time)
{
Console.WriteLine($@"{DateTime.Now}, Subscriber invoked, Sent time:{time}");
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.MongoDB/Program.cs
================================================
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
var builder = WebApplication.CreateBuilder(args);
// Configure services
builder.Services.AddSingleton(new MongoClient(builder.Configuration.GetConnectionString("MongoDB")));
builder.Services.AddCap(x =>
{
x.UseMongoDB(builder.Configuration.GetConnectionString("MongoDB"));
x.UseRabbitMQ("");
x.UseDashboard();
});
builder.Services.AddControllers();
var app = builder.Build();
// Configure middleware pipeline
app.UseRouting();
app.MapControllers();
app.Run();
================================================
FILE: samples/Sample.RabbitMQ.MongoDB/Sample.RabbitMQ.MongoDB.csproj
================================================
net10.0
================================================
FILE: samples/Sample.RabbitMQ.MongoDB/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Debug"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MongoDB": "mongodb://192.168.2.120:27017,192.168.2.120:27018,192.168.2.120:27019/?replicaSet=rs0"
},
"RabbitMQ": {
"HostName": "localhost",
"Port": 5672,
"UserName": "",
"Password": ""
}
}
================================================
FILE: samples/Sample.RabbitMQ.MongoDB/docker-compose.yml
================================================
version: "3.9"
services:
mongodb:
hostname: cap-mongodb
container_name: cap-mongodb
image: mongo:latest
environment:
MONGO_INITDB_DATABASE: test
MONGO_REPLICA_SET_NAME: rs0
volumes:
- ./mongo-initdb.d:/docker-entrypoint-initdb.d
expose:
- 27017
ports:
- "27017:27017"
healthcheck:
test: test $$(echo "rs.initiate().ok || rs.slaveOk().ok || rs.status().ok" | mongo --quiet) -eq 1
interval: 10s
start_period: 30s
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs0" ]
networks:
- cap-net
rabbitmq:
image: rabbitmq:latest
container_name: cap-rabbitmq
ports:
- 5672:5672
- 15672:15672
volumes:
- ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
- ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
networks:
- cap-net
networks:
cap-net:
name: cap-net
driver: bridge
================================================
FILE: samples/Sample.RabbitMQ.MySql/AppDbContext.cs
================================================
using Microsoft.EntityFrameworkCore;
namespace Sample.RabbitMQ.MySql
{
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}";
}
}
public class AppDbContext : DbContext
{
public const string ConnectionString = "Server=127.0.0.1;Database=cap;Uid=root;Pwd=my-secret-pw;";
public DbSet Persons { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql(ConnectionString, ServerVersion.AutoDetect(ConnectionString));
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs
================================================
using System;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Mvc;
using MySqlConnector;
namespace Sample.RabbitMQ.MySql.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly ICapPublisher _capBus;
public ValuesController(ICapPublisher capPublisher)
{
_capBus = capPublisher;
}
[Route("~/control/start")]
public async Task Start([FromServices] IBootstrapper bootstrapper)
{
await bootstrapper.BootstrapAsync();
return Ok();
}
[Route("~/control/stop")]
public async Task Stop([FromServices] IBootstrapper bootstrapper)
{
await bootstrapper.DisposeAsync();
return Ok();
}
[Route("~/without/transaction")]
public async Task WithoutTransactionAsync()
{
await _capBus.PublishAsync("sample.rabbitmq.mysql", DateTime.Now, cancellationToken: HttpContext.RequestAborted);
return Ok();
}
[Route("~/delay/{delaySeconds:int}")]
public async Task Delay(int delaySeconds)
{
await _capBus.PublishDelayAsync(TimeSpan.FromSeconds(delaySeconds), "sample.rabbitmq.test", $"publish time:{DateTime.Now}, delay seconds:{delaySeconds}");
return Ok();
}
[Route("~/adonet/transaction")]
public async Task AdonetWithTransaction()
{
using (var connection = new MySqlConnection(AppDbContext.ConnectionString))
{
using var transaction = await connection.BeginTransactionAsync(_capBus, true);
await connection.ExecuteAsync("insert into test(name) values('test')", transaction: (IDbTransaction)transaction.DbTransaction);
await _capBus.PublishAsync("sample.rabbitmq.mysql", DateTime.Now);
}
return Ok();
}
[Route("~/ef/transaction")]
public async Task EntityFrameworkWithTransaction([FromServices] AppDbContext dbContext)
{
using (var trans = await dbContext.Database.BeginTransactionAsync(_capBus, autoCommit: false))
{
await dbContext.Persons.AddAsync(new Person() { Name = "ef.transaction" });
await _capBus.PublishAsync("sample.rabbitmq.mysql", DateTime.Now);
await dbContext.SaveChangesAsync();
await trans.CommitAsync();
}
return Ok();
}
[NonAction]
[CapSubscribe("sample.rabbitmq.mysql")]
public void Subscriber(DateTime time)
{
Console.WriteLine("Publishing time:" + time);
}
[NonAction]
[CapSubscribe("sample.rabbitmq.test")]
public void Subscriber2(string message)
{
Console.WriteLine("Publishing message:" + message);
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.MySql/Program.cs
================================================
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Sample.RabbitMQ.MySql;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddDbContext();
builder.Services.AddCap(x =>
{
x.UseEntityFramework();
x.UseRabbitMQ("localhost");
x.UseDashboard();
});
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline
app.UseRouting();
app.MapControllers();
app.Run();
================================================
FILE: samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj
================================================
net8.0
================================================
FILE: samples/Sample.RabbitMQ.MySql/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"DotNetCore.CAP": "Debug",
"Microsoft.AspNetCore.Hosting.Diagnostics": "Warning",
"Microsoft.AspNetCore.Routing": "Warning"
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs
================================================
using Microsoft.EntityFrameworkCore;
namespace Sample.RabbitMQ.SqlServer
{
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}";
}
}
public class AppDbContext : DbContext
{
public const string ConnectionString = "Server=127.0.0.1;Database=tempdb;User Id=sa;Password=yourStrong(!)Password;TrustServerCertificate=True";
public DbSet Persons { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(ConnectionString);
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs
================================================
using System;
using System.Data.Common;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using NameGenerator.Generators;
using Sample.RabbitMQ.SqlServer.Messages;
namespace Sample.RabbitMQ.SqlServer.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly ICapPublisher _capBus;
public ValuesController(ICapPublisher capPublisher)
{
_capBus = capPublisher;
}
[Route("~/control/start")]
public async Task Start([FromServices] IBootstrapper bootstrapper)
{
await bootstrapper.BootstrapAsync();
return Ok();
}
[Route("~/control/stop")]
public async Task Stop([FromServices] IBootstrapper bootstrapper)
{
await bootstrapper.DisposeAsync();
return Ok();
}
[Route("~/without/transaction")]
public async Task WithoutTransaction()
{
await _capBus.PublishAsync("sample.rabbitmq.sqlserver", new Person()
{
Id = 123,
Name = "Bar"
});
return Ok();
}
[Route("~/delay/{delaySeconds:int}")]
public async Task Delay(int delaySeconds)
{
await _capBus.PublishDelayAsync(TimeSpan.FromSeconds(delaySeconds), "sample.rabbitmq.sqlserver",
new Person()
{
Id = 123,
Name = "Bar"
});
return Ok();
}
[Route("~/delay/transaction/{delaySeconds:int}")]
public async Task DelayWithTransaction(int delaySeconds)
{
using (var connection = new SqlConnection(AppDbContext.ConnectionString))
{
using (var transaction = connection.BeginTransaction(_capBus, true))
{
//your business code
connection.Execute("INSERT INTO Persons(Name,Age,CreateTime) VALUES(@Name,@Age, GETDATE())", new
{
Name = new RealNameGenerator().Generate(),
Age = Random.Shared.Next(10, 99),
}, transaction: transaction);
await _capBus.PublishDelayAsync(TimeSpan.FromSeconds(delaySeconds), "sample.rabbitmq.sqlserver",
new Person()
{
Id = 123,
Name = "Bar"
});
}
}
return Ok();
}
[Route("~/adonet/transaction")]
public async Task AdonetWithTransaction()
{
var person = new Person
{
Name = new RealNameGenerator().Generate(),
Age = Random.Shared.Next(10, 99),
};
using (var connection = new SqlConnection(AppDbContext.ConnectionString))
{
using var transaction = await connection.BeginTransactionAsync(_capBus, false);
await _capBus.PublishAsync("sample.rabbitmq.sqlserver", person);
await connection.ExecuteAsync("INSERT INTO Persons(Name,Age,CreateTime) VALUES(@Name,@Age, GETDATE())",
param: new { person.Name, person.Age },
transaction: transaction);
await _capBus.PublishDelayAsync(TimeSpan.FromSeconds(5), "sample.rabbitmq.sqlserver", person);
await ((DbTransaction)transaction).CommitAsync();
}
person.Name = new RealNameGenerator().Generate();
await _capBus.PublishAsync("sample.rabbitmq.sqlserver", person);
return Ok();
}
[Route("~/ef/transaction")]
public IActionResult EntityFrameworkWithTransaction([FromServices] AppDbContext dbContext)
{
using (dbContext.Database.BeginTransaction(_capBus, autoCommit: true))
{
dbContext.Persons.Add(new Person() { Name = "ef.transaction" });
dbContext.SaveChanges();
_capBus.Publish("sample.rabbitmq.sqlserver", new Person()
{
Id = 123,
Name = "Bar"
});
}
return Ok();
}
[Route("~/typed/subscribe")]
public async Task TypePublish()
{
// Add the following code to startup.cs
//services
// .AddSingleton()
// .AddQueueHandlers(typeof(Startup).Assembly);
await using (var connection = new SqlConnection(AppDbContext.ConnectionString))
{
using var transaction = connection.BeginTransaction(_capBus);
// This is where you would do other work that is going to persist data to your database
var message = TestMessage.Create($"This is message text created at {DateTime.Now:O}.");
await _capBus.PublishAsync(typeof(TestMessage).FullName, message);
transaction.Commit();
}
return Content("ok");
}
[NonAction]
[CapSubscribe("sample.rabbitmq.sqlserver")]
public void Subscriber(Person p)
{
Console.WriteLine($@"{DateTime.Now} Subscriber invoked, Info: {p}");
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/Messages/TestMessage.cs
================================================
namespace Sample.RabbitMQ.SqlServer.Messages
{
public class TestMessage
{
public static TestMessage Create(string text) => new()
{
Text = text
};
public string Text { get; private init; }
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/Messages/VeryFastProcessingReceiver.cs
================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Sample.RabbitMQ.SqlServer.TypedConsumers;
namespace Sample.RabbitMQ.SqlServer.Messages
{
[QueueHandlerTopic("fasttopic")]
public class VeryFastProcessingReceiver : QueueHandler
{
private readonly ILogger _logger;
public VeryFastProcessingReceiver(ILogger logger)
{
_logger = logger;
}
public async Task Handle(TestMessage value)
{
_logger.LogInformation($"Starting FAST processing handler {DateTime.Now:O}: {value.Text}");
await Task.Delay(50);
_logger.LogInformation($"Ending FAST processing handler {DateTime.Now:O}: {value.Text}");
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/Messages/XSlowProcessingReceiver.cs
================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Sample.RabbitMQ.SqlServer.TypedConsumers;
namespace Sample.RabbitMQ.SqlServer.Messages
{
[QueueHandlerTopic("slowtopic")]
public class XSlowProcessingReceiver : QueueHandler
{
private readonly ILogger _logger;
public XSlowProcessingReceiver(ILogger logger)
{
_logger = logger;
}
public async Task Handle(TestMessage value)
{
_logger.LogInformation($"Starting SLOW processing handler {DateTime.Now:O}: {value.Text}");
await Task.Delay(10000);
_logger.LogInformation($"Ending SLOW processing handler {DateTime.Now:O}: {value.Text}");
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/Migrations/20191205032949_FirstMigration.Designer.cs
================================================
//
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Sample.RabbitMQ.SqlServer;
namespace Sample.RabbitMQ.SqlServer.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20191205032949_FirstMigration")]
partial class FirstMigration
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Sample.RabbitMQ.SqlServer.Person", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property("Name")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Persons");
});
#pragma warning restore 612, 618
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/Migrations/20191205032949_FirstMigration.cs
================================================
using Microsoft.EntityFrameworkCore.Migrations;
namespace Sample.RabbitMQ.SqlServer.Migrations
{
public partial class FirstMigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Persons",
columns: table => new
{
Id = table.Column(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Persons", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Persons");
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/Migrations/AppDbContextModelSnapshot.cs
================================================
//
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Sample.RabbitMQ.SqlServer;
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", "3.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Sample.RabbitMQ.SqlServer.Person", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property("Name")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Persons");
});
#pragma warning restore 612, 618
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/Program.cs
================================================
using Dapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.DependencyInjection;
using Sample.RabbitMQ.SqlServer;
var builder = WebApplication.CreateBuilder(args);
// Configure services
//docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=yourStrong(!)Password" -e "MSSQL_PID=Evaluation" -p 1433:1433 \
// --name sqlpreview --hostname sqlpreview -d mcr.microsoft.com/mssql/server:2022-preview-ubuntu-22.04
builder.Services.AddDbContext();
//builder.Services
// .AddSingleton()
// .AddQueueHandlers(typeof(Program).Assembly);
// Initialize database schema
await using (var connection = new SqlConnection(AppDbContext.ConnectionString))
{
await connection.ExecuteAsync("""
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[Persons]') AND type IN ('U'))
DROP TABLE [dbo].[Persons]
CREATE TABLE [dbo].[Persons] (
[Id] int IDENTITY(1,1) NOT NULL,
[Name] varchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Age] int NULL,
[CreateTime] datetime2(7) DEFAULT getdate() NULL
)
""");
}
builder.Services.AddCap(x =>
{
x.UseEntityFramework();
x.UseRabbitMQ("127.0.0.1");
x.UseDashboard();
//x.EnablePublishParallelSend = true;
//x.FailedThresholdCallback = failed =>
//{
// var logger = failed.ServiceProvider.GetRequiredService>();
// logger.LogError($@"A message of type {failed.MessageType} failed after executing {x.FailedRetryCount} several times,
// requiring manual troubleshooting. Message name: {failed.Message.GetName()}");
//};
//x.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
builder.Services.AddControllers();
var app = builder.Build();
// Configure middleware pipeline
app.UseRouting();
app.MapControllers();
app.Run();
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj
================================================
net10.0
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/TypedConsumers/QueueHandler.cs
================================================
namespace Sample.RabbitMQ.SqlServer.TypedConsumers
{
public abstract class QueueHandler { }
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/TypedConsumers/QueueHandlerTopicAttribute.cs
================================================
using System;
namespace Sample.RabbitMQ.SqlServer.TypedConsumers
{
[AttributeUsage(AttributeTargets.Class)]
public class QueueHandlerTopicAttribute : Attribute
{
public string Topic { get; }
public QueueHandlerTopicAttribute(string topic)
{
Topic = topic;
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/TypedConsumers/QueueHandlersExtensions.cs
================================================
using System;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
namespace Sample.RabbitMQ.SqlServer.TypedConsumers
{
internal static class QueueHandlersExtensions
{
private static readonly Type queueHandlerType = typeof(QueueHandler);
public static IServiceCollection AddQueueHandlers(this IServiceCollection services, params Assembly[] assemblies)
{
assemblies ??= new[] { Assembly.GetEntryAssembly() };
foreach (var type in assemblies.Distinct().SelectMany(x => x.GetTypes().Where(FilterHandlers)))
{
services.AddTransient(queueHandlerType, type);
}
return services;
}
private static bool FilterHandlers(Type t)
{
var topic = t.GetCustomAttribute();
return queueHandlerType.IsAssignableFrom(t) && topic != null && t.IsClass && !t.IsAbstract;
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/TypedConsumers/TypedConsumerServiceSelector.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using DotNetCore.CAP;
using DotNetCore.CAP.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Sample.RabbitMQ.SqlServer.TypedConsumers
{
internal class TypedConsumerServiceSelector : ConsumerServiceSelector
{
private readonly CapOptions _capOptions;
public TypedConsumerServiceSelector(IServiceProvider serviceProvider) : base(serviceProvider)
{
_capOptions = serviceProvider.GetRequiredService>().Value;
}
protected override IEnumerable FindConsumersFromInterfaceTypes(IServiceProvider provider)
{
var executorDescriptorList = new List(30);
using var scoped = provider.CreateScope();
var consumerServices = scoped.ServiceProvider.GetServices();
foreach (var service in consumerServices)
{
var typeInfo = service.GetType().GetTypeInfo();
if (!typeof(QueueHandler).GetTypeInfo().IsAssignableFrom(typeInfo))
{
continue;
}
executorDescriptorList.AddRange(GetMyDescription(typeInfo));
}
return executorDescriptorList;
}
private IEnumerable GetMyDescription(TypeInfo typeInfo)
{
var method = typeInfo.DeclaredMethods.FirstOrDefault(x => x.Name == "Handle");
if (method == null) yield break;
var topicAttr = typeInfo.GetCustomAttributes(true);
var topicAttributes = topicAttr as IList ?? topicAttr.ToList();
if (topicAttributes.Count == 0) yield break;
foreach (var attr in topicAttributes)
{
var topic = attr.Topic == null
? _capOptions.DefaultGroupName + "." + _capOptions.Version
: attr.Topic + "." + _capOptions.Version;
if (!string.IsNullOrEmpty(_capOptions.GroupNamePrefix))
{
topic = $"{_capOptions.GroupNamePrefix}.{topic}";
}
var parameters = method.GetParameters().Select(p => new ParameterDescriptor
{
Name = p.Name,
ParameterType = p.ParameterType,
IsFromCap = p.GetCustomAttributes(typeof(FromCapAttribute)).Any()
}).ToList();
var capName = parameters.FirstOrDefault(x => !x.IsFromCap)?.ParameterType.FullName;
if (string.IsNullOrEmpty(capName)) continue;
yield return new ConsumerExecutorDescriptor
{
Attribute = new CapSubscribeAttribute(capName)
{
Group = topic
},
Parameters = parameters,
MethodInfo = method,
ImplTypeInfo = typeInfo,
TopicNamePrefix = _capOptions.TopicNamePrefix,
ServiceTypeInfo = typeInfo
};
}
}
}
}
================================================
FILE: samples/Sample.RabbitMQ.SqlServer/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
================================================
FILE: samples/Samples.Redis.SqlServer/Controllers/HomeController.cs
================================================
using DotNetCore.CAP;
using DotNetCore.CAP.Messages;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace Samples.Redis.SqlServer.Controllers;
[ApiController]
[Route("[controller]/[action]")]
public class HomeController : ControllerBase
{
private readonly ILogger _logger;
private readonly ICapPublisher _publisher;
private readonly IOptions _options;
public HomeController(ILogger logger, ICapPublisher publisher, IOptions options)
{
_logger = logger;
_publisher = publisher;
this._options = options;
}
[HttpGet]
public async Task Publish([FromQuery] string message = "test-message")
{
await _publisher.PublishAsync(message, new Person() { Age = 11, Name = "James" });
}
[CapSubscribe("test-message")]
[CapSubscribe("test-message-1")]
[CapSubscribe("test-message-2")]
[CapSubscribe("test-message-3")]
[NonAction]
public void Subscribe(Person p, [FromCap] CapHeader header)
{
_logger.LogInformation($"{header[Headers.MessageName]} subscribed with value --> " + p);
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return "Name:" + Name + ", Age:" + Age;
}
}
================================================
FILE: samples/Samples.Redis.SqlServer/Dockerfile
================================================
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 80
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["src/Directory.Build.props", "src/"]
COPY ["samples/Samples.Redis.SqlServer/Samples.Redis.SqlServer.csproj", "samples/Samples.Redis.SqlServer/"]
COPY ["src/DotNetCore.CAP.RedisStreams/DotNetCore.CAP.RedisStreams.csproj", "src/DotNetCore.CAP.RedisStreams/"]
COPY ["src/DotNetCore.CAP/DotNetCore.CAP.csproj", "src/DotNetCore.CAP/"]
COPY ["src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj", "src/DotNetCore.CAP.SqlServer/"]
RUN dotnet restore "./samples/Samples.Redis.SqlServer/Samples.Redis.SqlServer.csproj"
COPY . .
WORKDIR "/src/samples/Samples.Redis.SqlServer"
RUN dotnet build "./Samples.Redis.SqlServer.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Samples.Redis.SqlServer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Samples.Redis.SqlServer.dll"]
================================================
FILE: samples/Samples.Redis.SqlServer/Program.cs
================================================
using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddControllers();
builder.Services
.AddEndpointsApiExplorer();
builder.Services
.AddSwaggerGen();
builder.Services
.AddCap(options =>
{
options.UseRedis(redis =>
{
redis.Configuration = ConfigurationOptions.Parse("redis-node-0:6379,password=cap");
redis.OnConsumeError = context =>
{
throw new InvalidOperationException("");
};
});
options.UseSqlServer("Server=db;Database=master;User=sa;Password=P@ssw0rd;Encrypt=False");
options.UseDashboard();
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
================================================
FILE: samples/Samples.Redis.SqlServer/Samples.Redis.SqlServer.csproj
================================================
net10.0eb622624-fbc4-46d0-b006-dfe8e66df5bbLinuxenable..\....\..\docker-compose.dcproj
================================================
FILE: samples/Samples.Redis.SqlServer/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
================================================
FILE: samples/Samples.Redis.SqlServer/docker-compose.yml
================================================
services:
redis-node-0:
image: docker.io/bitnami/redis-cluster:7.0
volumes:
- redis-cluster_data-0:/bitnami/redis/data
environment:
- 'REDIS_PASSWORD=cap'
- 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
redis-node-1:
image: docker.io/bitnami/redis-cluster:7.0
volumes:
- redis-cluster_data-1:/bitnami/redis/data
environment:
- 'REDIS_PASSWORD=cap'
- 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
redis-node-2:
image: docker.io/bitnami/redis-cluster:7.0
volumes:
- redis-cluster_data-2:/bitnami/redis/data
environment:
- 'REDIS_PASSWORD=cap'
- 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
redis-node-3:
image: docker.io/bitnami/redis-cluster:7.0
volumes:
- redis-cluster_data-3:/bitnami/redis/data
environment:
- 'REDIS_PASSWORD=cap'
- 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
redis-node-4:
image: docker.io/bitnami/redis-cluster:7.0
volumes:
- redis-cluster_data-4:/bitnami/redis/data
environment:
- 'REDIS_PASSWORD=cap'
- 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
redis-node-5:
image: docker.io/bitnami/redis-cluster:7.0
ports:
- 6379:6379
volumes:
- redis-cluster_data-5:/bitnami/redis/data
depends_on:
- redis-node-0
- redis-node-1
- redis-node-2
- redis-node-3
- redis-node-4
environment:
- 'REDIS_PASSWORD=cap'
- 'REDISCLI_AUTH=cap'
- 'REDIS_CLUSTER_REPLICAS=1'
- 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
- 'REDIS_CLUSTER_CREATOR=yes'
db:
image: "mcr.microsoft.com/mssql/server"
ports:
- 1433:1433
environment:
SA_PASSWORD: "P@ssw0rd"
ACCEPT_EULA: "Y"
redis-sample:
build:
context: ../..
dockerfile: samples/Samples.Redis.SqlServer/Dockerfile
environment:
- ASPNETCORE_URLS:http://*:8080
- ASPNETCORE_ENVIRONMENT=Development
ports:
- 8080:8080
depends_on:
- db
- redis-node-5
volumes:
redis-cluster_data-0:
driver: local
redis-cluster_data-1:
driver: local
redis-cluster_data-2:
driver: local
redis-cluster_data-3:
driver: local
redis-cluster_data-4:
driver: local
redis-cluster_data-5:
driver: local
================================================
FILE: src/Directory.Build.props
================================================
CAPenableNCC;Savorboardhttps://github.com/dotnetcore/CAPgit$(MSBuildThisFileDirectory)logo.pnghttps://github.com/dotnetcore/CAPMITCAP;EventBus;Outbox Patterns;Distributed transactionDistributed transaction solution in micro-service base on eventually consistency, also an eventbus with Outbox pattern.snupkgtruetrue$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
================================================
FILE: src/DotNetCore.CAP/BrokerConnectionException.cs
================================================
// Copyright (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;
///
/// Represents an error that occurs when CAP cannot establish or maintain a connection to the message broker.
/// This exception is thrown when connection issues prevent message publishing or consuming operations.
///
///
/// Common causes include:
///
/// The broker service is offline or unreachable.
/// Network connectivity issues prevent communication with the broker.
/// Authentication or authorization failures when connecting to the broker.
/// Broker-side resource limits or configuration issues.
///
/// When this exception occurs, CAP will attempt to reconnect based on configured retry policies.
///
public class BrokerConnectionException : Exception
{
///
/// Initializes a new instance of the class with a reference to the inner exception
/// that caused this connection error.
///
///
/// The underlying exception that caused the connection failure.
/// This typically contains specific details about the connection error from the broker client library.
///
public BrokerConnectionException(Exception innerException)
: base("Broker Unreachable", innerException)
{
}
}
================================================
FILE: src/DotNetCore.CAP/CAP.Attribute.cs
================================================
// Copyright (c) .NET Core Community. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Messages;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP;
///
/// Represents an attribute that is applied to a method to indicate that it is a subscriber for a specific CAP topic.
///
public class CapSubscribeAttribute : TopicAttribute
{
///
/// Initializes a new instance of the class with the specified topic name and partial flag.
///
/// The name of the CAP topic.
/// A flag indicating whether the subscriber is a partial subscriber.
public CapSubscribeAttribute(string name, bool isPartial = false)
: base(name, isPartial)
{
}
///
/// Returns a string that represents the current .
///
/// A string that represents the current .
public override string ToString()
{
return Name;
}
}
[AttributeUsage(AttributeTargets.Parameter)]
public class FromCapAttribute : Attribute
{
}
public class CapHeader : ReadOnlyDictionary
{
internal IDictionary? ResponseHeader { get; set; }
public CapHeader(IDictionary dictionary) : base(dictionary)
{
}
///
/// When a callbackName is specified from publish message, use this method to add an additional header.
///
/// The response header key.
/// The response header value.
public void AddResponseHeader(string key, string? value)
{
ResponseHeader ??= new Dictionary();
ResponseHeader[key] = value;
}
///
/// When a callbackName is specified from publish message, use this method to abort the callback.
///
public void RemoveCallback()
{
Dictionary.Remove(Headers.CallbackName);
}
///
/// When a callbackName is specified from Publish message, use this method to rewrite the callback name.
///
/// The new callback name.
public void RewriteCallback(string callbackName)
{
Dictionary[Headers.CallbackName] = callbackName;
}
}
================================================
FILE: src/DotNetCore.CAP/CAP.Builder.cs
================================================
// Copyright (c) .NET 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.Reflection;
using DotNetCore.CAP.Filter;
using DotNetCore.CAP.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
// ReSharper disable UnusedMember.Global
namespace DotNetCore.CAP;
///
/// A marker service used internally to verify that the CAP service has been registered on a .
/// This service is registered when AddCap() is called during dependency injection setup.
///
public class CapMarkerService
{
///
/// Gets or sets the name identifier for the CAP service.
///
public string Name { get; set; }
///
/// Gets or sets the version of the CAP assembly.
///
public string Version { get; set; }
///
/// Initializes a new instance of the class with the specified name.
/// Automatically retrieves and stores the CAP assembly version information.
///
/// The name identifier for the CAP service.
public CapMarkerService(string name)
{
Name = name;
try
{
Version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion!;
}
catch
{
Version = "N/A";
}
}
}
///
/// A marker service used internally to verify that a CAP storage extension (e.g., SQL Server, PostgreSQL, MySQL, MongoDB)
/// has been registered on a .
///
public class CapStorageMarkerService
{
///
/// Gets or sets the name identifier for the storage extension (e.g., "SqlServer", "PostgreSql", "MySql").
///
public string Name { get; set; }
//public IDictionary MetaData { get; private set; }
///
/// Initializes a new instance of the class with the specified storage name.
///
/// The name identifier for the storage extension.
public CapStorageMarkerService(string name)
{
Name = name;
}
}
///
/// A marker service used internally to verify that a CAP message transport extension (e.g., RabbitMQ, Kafka, Azure Service Bus, NATS)
/// has been registered on a .
///
public class CapMessageQueueMakerService
{
///
/// Gets or sets the name identifier for the message transport extension (e.g., "RabbitMQ", "Kafka", "AzureServiceBus").
///
public string Name { get; set; }
//public IDictionary MetaData { get; private set; }
///
/// Initializes a new instance of the class with the specified message queue name.
///
/// The name identifier for the message transport extension.
public CapMessageQueueMakerService(string name)
{
Name = name;
}
}
///
/// Provides a fluent API for fine-grained configuration of CAP services within a dependency injection container.
/// This builder allows registration of subscriber filters, custom subscriber assembly scanning, and other CAP extensions.
///
///
/// The is typically obtained through the AddCap() extension method on ,
/// enabling a fluent configuration experience for CAP setup. All builder methods return the builder instance to support method chaining.
///
public sealed class CapBuilder
{
///
/// Initializes a new instance of the class with the specified service collection.
///
/// The where CAP services are being configured.
public CapBuilder(IServiceCollection services)
{
Services = services;
}
///
/// Gets the where CAP services are registered and configured.
///
///
/// This collection is used by all builder methods to register necessary services, filters, and extensions
/// in the application's dependency injection container.
///
public IServiceCollection Services { get; }
///
/// Registers a subscriber filter that will be applied to all subscriber method executions.
/// Filters can be used for cross-cutting concerns such as logging, error handling, and transaction management.
///
///
/// The type of the filter to register. Must implement and be instantiable.
/// The filter is registered with a scoped lifetime, meaning a new instance is created per request/scope.
///
/// The current instance to support fluent method chaining.
///
/// Multiple filters can be registered by calling this method multiple times. Filters are executed in the order
/// they are registered, allowing for layered processing of subscriber messages.
///
///
///
/// services.AddCap(options =>
/// {
/// options.UseRabbitMQ(r => r.HostName = "localhost");
/// options.UseSqlServer("connection_string");
/// })
/// .AddSubscribeFilter<LoggingFilter>()
/// .AddSubscribeFilter<ExceptionHandlingFilter>();
///
///
public CapBuilder AddSubscribeFilter() where T : class, ISubscribeFilter
{
Services.TryAddScoped();
return this;
}
///
/// Registers subscriber methods from the specified assemblies.
/// This method scans the provided assemblies for classes and methods decorated with CAP subscriber attributes.
///
///
/// An array of instances to scan for subscriber implementations.
///
/// The current instance to support fluent method chaining.
/// Thrown if is null.
///
/// This method replaces the default subscriber selector with a custom one that scans the specified assemblies.
/// By default, CAP scans all loaded assemblies; this method allows you to restrict scanning to specific assemblies
/// for performance optimization or explicit control over subscriber discovery.
///
///
///
/// var capAssembly = typeof(MyCapHandlers).Assembly;
/// services.AddCap(options => { })
/// .AddSubscriberAssembly(capAssembly);
///
///
public CapBuilder AddSubscriberAssembly(params Assembly[] assemblies)
{
ArgumentNullException.ThrowIfNull(assemblies);
Services.Replace(new ServiceDescriptor(typeof(IConsumerServiceSelector),
x => new AssemblyConsumerServiceSelector(x, assemblies),
ServiceLifetime.Singleton));
return this;
}
///
/// Registers subscriber methods from the assemblies containing the specified marker types.
/// This is a convenience overload that extracts the assemblies from the provided types and delegates to .
///
///
/// An array of marker types (typically classes in the target assemblies) whose containing assemblies will be scanned for subscribers.
/// This approach allows type-safe specification of which assemblies to include without directly referencing objects.
///
/// The current instance to support fluent method chaining.
/// Thrown if is null.
///
/// This method is particularly useful in multi-project scenarios where you have separate projects for handlers
/// but want to avoid direct assembly references. Simply pass a type from each assembly, and the method will
/// automatically extract and scan those assemblies.
///
///
///
/// // Assuming MyCapHandlers is a class in the assembly containing subscriber implementations
/// services.AddCap(options => { })
/// .AddSubscriberAssembly(typeof(MyCapHandlers));
///
///
public CapBuilder AddSubscriberAssembly(params Type[] handlerAssemblyMarkerTypes)
{
ArgumentNullException.ThrowIfNull(handlerAssemblyMarkerTypes);
AddSubscriberAssembly(handlerAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly).ToArray());
return this;
}
}
================================================
FILE: src/DotNetCore.CAP/CAP.Options.cs
================================================
// Copyright (c) .NET 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 System.Text.Json;
using DotNetCore.CAP.Messages;
// ReSharper disable InconsistentNaming
namespace DotNetCore.CAP;
///
/// Provides options to customize various aspects of the message processing pipeline. This includes settings for message expiration,
/// retry mechanisms, concurrency management, and serialization, among others. This class allows fine-tuning
/// CAP's behavior to better align with specific application requirements, such as adjusting threading models for
/// subscriber message processing, setting message expiry times, and customizing serialization settings.
///
public class CapOptions
{
public CapOptions()
{
SucceedMessageExpiredAfter = 24 * 3600;
FailedMessageExpiredAfter = 15 * 24 * 3600;
FailedRetryInterval = 60;
FailedRetryCount = 50;
ConsumerThreadCount = 1;
EnablePublishParallelSend = false;
EnableSubscriberParallelExecute = false;
SubscriberParallelExecuteThreadCount = Environment.ProcessorCount;
SubscriberParallelExecuteBufferFactor = 1;
Extensions = new List();
Version = "v1";
DefaultGroupName = "cap.queue." + Assembly.GetEntryAssembly()?.GetName().Name!.ToLower();
CollectorCleaningInterval = 300;
FallbackWindowLookbackSeconds = 240;
SchedulerBatchSize = 1000;
}
internal IList Extensions { get; }
///
/// Gets or sets the default consumer group name for subscribers.
/// In Kafka, this corresponds to the consumer group name; in RabbitMQ, it corresponds to the queue name.
/// Default value is "cap.queue." followed by the entry assembly name in lowercase.
///
public string DefaultGroupName { get; set; }
///
/// Gets or sets an optional prefix to be prepended to all consumer group names.
///
public string? GroupNamePrefix { get; set; }
///
/// Gets or sets an optional prefix to be prepended to all topic names.
///
public string? TopicNamePrefix { get; set; }
///
/// Gets or sets the version identifier for messages, used to isolate data between different instances or deployments.
/// This allows multiple instances to coexist without message conflicts. Maximum length is 20 characters.
/// Default is "v1".
///
public string Version { get; set; }
///
/// Gets or sets the time interval (in seconds) after which successfully processed messages are automatically deleted.
/// This helps manage storage by removing old successfully delivered messages.
/// Default is 86,400 seconds (24 hours).
///
public int SucceedMessageExpiredAfter { get; set; }
///
/// Gets or sets the time interval (in seconds) after which failed messages are automatically deleted.
/// This allows cleanup of old failed messages that exceed the retry threshold.
/// Default is 1,296,000 seconds (15 days).
///
public int FailedMessageExpiredAfter { get; set; }
///
/// Gets or sets the polling interval (in seconds) for the retry processor to check and retry failed messages.
/// Default is 60 seconds.
///
public int FailedRetryInterval { get; set; }
///
/// Gets or sets an optional callback function invoked when a message has been retried the maximum number of times
/// specified by without success. This callback receives detailed information about the failed message.
///
public Action? FailedThresholdCallback { get; set; }
///
/// Gets or sets the maximum number of retry attempts for failed messages (both published and subscribed).
/// Once this threshold is reached, the message is marked as permanently failed and no longer retried.
/// Default is 50 times.
///
public int FailedRetryCount { get; set; }
///
/// Gets or sets the number of concurrent consumer threads for message consumption from the transport.
/// Higher values increase parallelism but consume more resources; lower values reduce resource usage but may lower throughput.
/// Default is 1.
///
public int ConsumerThreadCount { get; set; }
///
/// Gets or sets a value indicating whether to enable parallel execution of subscriber methods using an in-memory queue.
/// When enabled, received messages are buffered in memory and processed concurrently by multiple worker threads.
/// Use to configure the number of parallel threads.
/// Default is false.
///
public bool EnableSubscriberParallelExecute { get; set; }
///
/// Gets or sets the number of parallel worker threads for subscriber message execution when is enabled.
/// This controls the degree of parallelism when processing subscriber handlers.
/// Default is the number of logical processors ().
///
public int SubscriberParallelExecuteThreadCount { get; set; }
///
/// Gets or sets a multiplier factor for determining the in-memory buffer capacity when is enabled.
/// The actual buffer capacity is calculated as: SubscriberParallelExecuteThreadCount × SubscriberParallelExecuteBufferFactor.
/// This controls how many messages can be queued before blocking new incoming messages.
/// Default is 1.
///
public int SubscriberParallelExecuteBufferFactor { get; set; }
///
/// Gets or sets a value indicating whether to enable parallel execution of publish operations using the .NET thread pool.
/// When enabled, message publishing tasks are dispatched to the thread pool for concurrent execution, improving throughput for high-volume publishing scenarios.
/// Default is false.
///
public bool EnablePublishParallelSend { get; set; }
///
/// Gets or sets the lookback time window (in seconds) for the retry processor to pick up scheduled or failed status messages.
/// This ensures that messages with clocks slightly out of sync are still processed correctly.
/// Default is 240 seconds (4 minutes).
///
public int FallbackWindowLookbackSeconds { get; set; }
///
/// Gets or sets the interval (in seconds) at which the cleanup processor removes expired messages from the message storage.
/// The processor runs periodically to clean up messages that have exceeded their expiration times.
/// Default is 300 seconds (5 minutes).
///
public int CollectorCleaningInterval { get; set; }
///
/// Gets or sets the maximum number of delayed or failed messages to fetch in a single scheduler cycle.
/// Larger batches improve throughput but consume more memory; smaller batches reduce memory usage but may lower throughput.
/// Default is 1,000.
///
public int SchedulerBatchSize { get; set; }
///
/// Gets or sets the JSON serialization options used for message content serialization and deserialization.
/// Customize this to control JSON formatting, naming policies, converters, and other serialization behavior.
///
public JsonSerializerOptions JsonSerializerOptions { get; } = new();
///
/// Gets or sets a value indicating whether to use distributed storage locking when retrying failed messages.
/// When enabled, only one instance in a distributed system will perform message retries, preventing duplicate processing.
/// This is essential for clustered deployments to ensure exactly-once retry semantics.
/// Default is false.
///
public bool UseStorageLock { get; set; }
///
/// Registers a CAP options extension that will be executed when configuring CAP services.
/// Extensions allow third-party libraries to customize CAP's behavior without modifying core configuration.
///
/// The extension instance to register.
/// Thrown if is null.
public void RegisterExtension(ICapOptionsExtension extension)
{
ArgumentNullException.ThrowIfNull(extension);
Extensions.Add(extension);
}
}
================================================
FILE: src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs
================================================
// Copyright (c) .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.Internal;
using DotNetCore.CAP.Processor;
using DotNetCore.CAP.Serialization;
using DotNetCore.CAP.Transport;
using Microsoft.Extensions.DependencyInjection.Extensions;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;
///
/// Provides extension methods for registering and configuring CAP (Consistency And Partition) services
/// in a dependency injection container.
///
public static class ServiceCollectionExtensions
{
///
/// Registers and configures all CAP services in the dependency injection container.
///
///
/// This method performs the following registrations:
///
/// Core services: Message publisher, consumer selector, subscription invoker, and method matcher cache.
/// Message processors: Retry processor, transport check processor, delayed message processor, and message collector.
/// Message transport: Message sender and default JSON serializer.
/// Processing servers: Consumer registration server, dispatcher, and main CAP processing server.
/// Bootstrapper and hosted service for application startup and lifecycle management.
/// Extensions: Any configured storage and transport extensions (registered via ).
///
/// All core services are registered with singleton lifetime to ensure consistency across the application.
/// Storage and transport extensions must be added before calling this method (typically through AddCap callback).
///
///
/// The where CAP services will be registered.
/// This collection represents the application's dependency injection container.
///
///
/// A delegate that configures the settings for CAP.
/// This action is invoked to customize behavior such as message expiration, retry policies, concurrency settings,
/// and to register storage and transport extensions.
/// Use this to call UseRabbitMQ(), UseSqlServer(), and other extension methods.
///
///
/// A instance that provides a fluent API for additional CAP configuration,
/// such as registering subscriber filters and custom subscriber assembly scanning.
///
///
/// Thrown if is null.
///
///
///
/// services.AddCap(options =>
/// {
/// // Configure options
/// options.SucceedMessageExpiredAfter = 24 * 3600;
/// options.FailedRetryCount = 50;
///
/// // Register storage backend
/// options.UseSqlServer("your_connection_string");
///
/// // Register message transport
/// options.UseRabbitMQ(rabbitMqOptions =>
/// {
/// rabbitMqOptions.HostName = "localhost";
/// rabbitMqOptions.Port = 5672;
/// });
/// })
/// .AddSubscribeFilter<LoggingFilter>()
/// .AddSubscriberAssembly(typeof(MyCapHandlers));
///
///
public static CapBuilder AddCap(this IServiceCollection services, Action setupAction)
{
ArgumentNullException.ThrowIfNull(setupAction);
services.AddSingleton(_ => services);
services.TryAddSingleton(new CapMarkerService("CAP"));
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
//Processors
services.TryAddEnumerable(ServiceDescriptor.Singleton(sp => sp.GetRequiredService()));
services.TryAddEnumerable(ServiceDescriptor.Singleton(sp => sp.GetRequiredService()));
services.TryAddEnumerable(ServiceDescriptor.Singleton());
//Queue's message processor
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
//Sender
services.TryAddSingleton();
services.TryAddSingleton();
// Warning: IPublishMessageSender need to inject at extension project.
services.TryAddSingleton();
//Options and extension service
var options = new CapOptions();
setupAction(options);
services.TryAddSingleton();
foreach (var serviceExtension in options.Extensions)
serviceExtension.AddServices(services);
services.Configure(setupAction);
//Startup and Hosted
services.AddSingleton();
services.AddHostedService(sp => sp.GetRequiredService());
services.AddSingleton(sp => sp.GetRequiredService());
return new CapBuilder(services);
}
}
================================================
FILE: src/DotNetCore.CAP/Diagnostics/CapDiagnosticListenerNames.cs
================================================
// 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.Diagnostics;
///
/// Extension methods on the DiagnosticListener class to log CAP data
///
public static class CapDiagnosticListenerNames
{
private const string CapPrefix = "DotNetCore.CAP.";
//Tracing
public const string DiagnosticListenerName = "CapDiagnosticListener";
public const string BeforePublishMessageStore = CapPrefix + "WritePublishMessageStoreBefore";
public const string AfterPublishMessageStore = CapPrefix + "WritePublishMessageStoreAfter";
public const string ErrorPublishMessageStore = CapPrefix + "WritePublishMessageStoreError";
public const string BeforePublish = CapPrefix + "WritePublishBefore";
public const string AfterPublish = CapPrefix + "WritePublishAfter";
public const string ErrorPublish = CapPrefix + "WritePublishError";
public const string BeforeConsume = CapPrefix + "WriteConsumeBefore";
public const string AfterConsume = CapPrefix + "WriteConsumeAfter";
public const string ErrorConsume = CapPrefix + "WriteConsumeError";
public const string BeforeSubscriberInvoke = CapPrefix + "WriteSubscriberInvokeBefore";
public const string AfterSubscriberInvoke = CapPrefix + "WriteSubscriberInvokeAfter";
public const string ErrorSubscriberInvoke = CapPrefix + "WriteSubscriberInvokeError";
//Metrics
public const string MetricListenerName = CapPrefix + "EventCounter";
public const string PublishedPerSec = "published-per-second";
public const string ConsumePerSec = "consume-per-second";
public const string InvokeSubscriberPerSec = "invoke-subscriber-per-second";
public const string InvokeSubscriberElapsedMs = "invoke-subscriber-elapsed-ms";
}
================================================
FILE: src/DotNetCore.CAP/Diagnostics/EventCounterSource.Cap.cs
================================================
// Copyright (c) .NET 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.Tracing;
namespace DotNetCore.CAP.Diagnostics;
[EventSource(Name = CapDiagnosticListenerNames.MetricListenerName)]
public class CapEventCounterSource : EventSource
{
public static readonly CapEventCounterSource Log = new();
private IncrementingEventCounter? _publishPerSecondCounter;
private IncrementingEventCounter? _consumePerSecondCounter;
private IncrementingEventCounter? _subscriberInvokePerSecondCounter;
private EventCounter? _invokeCounter;
private CapEventCounterSource() { }
protected override void OnEventCommand(EventCommandEventArgs args)
{
if (args.Command == EventCommand.Enable)
{
_publishPerSecondCounter ??= new IncrementingEventCounter(CapDiagnosticListenerNames.PublishedPerSec, this)
{
DisplayName = "Publish Rate",
DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};
_consumePerSecondCounter ??= new IncrementingEventCounter(CapDiagnosticListenerNames.ConsumePerSec, this)
{
DisplayName = "Consume Rate",
DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};
_subscriberInvokePerSecondCounter ??= new IncrementingEventCounter(CapDiagnosticListenerNames.InvokeSubscriberPerSec, this)
{
DisplayName = "Invoke Subscriber Rate",
DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};
_invokeCounter ??= new EventCounter(CapDiagnosticListenerNames.InvokeSubscriberElapsedMs, this)
{
DisplayName = "Invoke Subscriber Elapsed Time",
DisplayUnits = "ms"
};
}
}
public void WritePublishMetrics()
{
_publishPerSecondCounter?.Increment();
}
public void WriteConsumeMetrics()
{
_consumePerSecondCounter?.Increment();
}
public void WriteInvokeMetrics()
{
_subscriberInvokePerSecondCounter?.Increment();
}
public void WriteInvokeTimeMetrics(double elapsedMs)
{
_invokeCounter?.WriteMetric(elapsedMs);
}
protected override void Dispose(bool disposing)
{
_publishPerSecondCounter?.Dispose();
_consumePerSecondCounter?.Dispose();
_subscriberInvokePerSecondCounter?.Dispose();
_invokeCounter?.Dispose();
_publishPerSecondCounter = null;
_consumePerSecondCounter = null;
_subscriberInvokePerSecondCounter = null;
_invokeCounter = null;
base.Dispose(disposing);
}
}
================================================
FILE: src/DotNetCore.CAP/Diagnostics/EventData.Cap.P.cs
================================================
// Copyright (c) .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.Messages;
using DotNetCore.CAP.Transport;
namespace DotNetCore.CAP.Diagnostics;
public class CapEventDataPubStore
{
public long? OperationTimestamp { get; set; }
public string Operation { get; set; } = default!;
public Message Message { get; set; } = default!;
public long? ElapsedTimeMs { get; set; }
public Exception? Exception { get; set; }
}
public class CapEventDataPubSend
{
public long? OperationTimestamp { get; set; }
public string Operation { get; set; } = default!;
public TransportMessage TransportMessage { get; set; } = default!;
public BrokerAddress BrokerAddress { get; set; }
public long? ElapsedTimeMs { get; set; }
public Exception? Exception { get; set; }
}
================================================
FILE: src/DotNetCore.CAP/Diagnostics/EventData.Cap.S.cs
================================================
// Copyright (c) .NET 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 DotNetCore.CAP.Messages;
using DotNetCore.CAP.Transport;
namespace DotNetCore.CAP.Diagnostics;
public class CapEventDataSubStore
{
public long? OperationTimestamp { get; set; }
public string Operation { get; set; } = default!;
public TransportMessage TransportMessage { get; set; } = default!;
public BrokerAddress BrokerAddress { get; set; }
public long? ElapsedTimeMs { get; set; }
public Exception? Exception { get; set; }
}
public class CapEventDataSubExecute
{
public long? OperationTimestamp { get; set; }
public string Operation { get; set; } = default!;
public Message Message { get; set; } = default!;
public MethodInfo? MethodInfo { get; set; }
public long? ElapsedTimeMs { get; set; }
public Exception? Exception { get; set; }
}
================================================
FILE: src/DotNetCore.CAP/DotNetCore.CAP.csproj
================================================
net8.0;net9.0;net10.0enabletrue1701;1702;1705;CS1591README.mdTrue\
================================================
FILE: src/DotNetCore.CAP/IBootstrapper.cs
================================================
// Copyright (c) .NET 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;
namespace DotNetCore.CAP;
///
/// Defines the contract for CAP bootstrapping logic that initializes the system when the application starts.
/// Implementations perform setup tasks such as initializing storage, registering consumers, or preparing the message queue.
///
///
/// The bootstrapper is responsible for:
///
/// Initializing storage tables or schema if not already present.
/// Registering consumer subscribers from discovered assemblies.
/// Verifying connection to message brokers and storage backends.
/// Preparing the system for message publishing and consuming operations.
///
/// The bootstrapper is registered as a hosted service and automatically starts when the application starts.
///
public interface IBootstrapper : IAsyncDisposable
{
///
/// Gets a value indicating whether the bootstrap process has completed successfully.
/// Returns true if the system is fully initialized; false if bootstrap is still in progress or failed.
///
bool IsStarted { get; }
///
/// Asynchronously performs the bootstrap initialization for CAP.
/// This method is called when the application starts and should complete all necessary initialization.
///
/// A token to monitor for cancellation requests.
/// A task representing the asynchronous bootstrap operation.
Task BootstrapAsync(CancellationToken cancellationToken = default);
}
================================================
FILE: src/DotNetCore.CAP/ICapOptionsExtension.cs
================================================
// 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;
///
/// Defines an extension mechanism for customizing CAP configuration during dependency injection setup.
/// Implementations of this interface allow third-party libraries and custom code to register services
/// and configure CAP functionality without modifying the core CAP assembly.
///
///
/// Extensions are registered through
/// and are executed during the AddCap() service registration process.
/// This allows modular and composable configuration of storage backends, transport implementations, and other CAP components.
///
public interface ICapOptionsExtension
{
///
/// Called during CAP service registration to add and configure child services in the dependency injection container.
///
///
/// Implementations should use TryAdd or Replace extension methods to register services,
/// allowing other extensions or user code to override registrations if needed.
///
///
/// The where services should be registered.
/// This is the same collection being configured for the application's dependency injection.
///
///
///
/// public void AddServices(IServiceCollection services)
/// {
/// services.TryAddSingleton<IDataStorage, SqlServerDataStorage>();
/// services.TryAddSingleton<ITransport, RabbitMQTransport>();
/// }
///
///
void AddServices(IServiceCollection services);
}
================================================
FILE: src/DotNetCore.CAP/ICapPublisher.cs
================================================
// Copyright (c) .NET 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;
namespace DotNetCore.CAP;
///
/// A publish service for publishing a message to CAP.
///
public interface ICapPublisher
{
///
/// Gets the service provider.
///
IServiceProvider ServiceProvider { get; }
///
/// Gets or sets the CAP transaction context object.
///
ICapTransaction? Transaction { get; set; }
///
/// Asynchronously publishes an object message.
///
/// The type of the message content object.
/// The topic name or exchange router key.
/// The message body content that will be serialized. (can be null)
/// The callback subscriber name.
/// The cancellation token.
/// A task representing the asynchronous operation.
Task PublishAsync(string name, T? contentObj, string? callbackName = null,
CancellationToken cancellationToken = default);
///
/// Asynchronously publishes an object message with custom headers.
///
/// The type of the message content object.
/// The topic name or exchange router key.
/// The message body content that will be serialized. (can be null)
/// The message additional headers.
/// The cancellation token.
/// A task representing the asynchronous operation.
Task PublishAsync(string name, T? contentObj, IDictionary headers,
CancellationToken cancellationToken = default);
///
/// Publishes an object message.
///
/// The type of the message content object.
/// The topic name or exchange router key.
/// The message body content that will be serialized. (can be null)
/// The callback subscriber name.
void Publish(string name, T? contentObj, string? callbackName = null);
///
/// Publishes an object message with custom headers.
///
/// The type of the message content object.
/// The topic name or exchange router key.
/// The message body content that will be serialized. (can be null)
/// The message additional headers.
void Publish(string name, T? contentObj, IDictionary headers);
///
/// Asynchronously schedules a message to be published at a future time with headers.
///
/// The type of the message content object.
/// The delay for the message to be published.
/// The topic name or exchange router key.
/// The message body content that will be serialized. (can be null)
/// The message additional headers.
/// The cancellation token.
/// A task representing the asynchronous operation.
Task PublishDelayAsync(TimeSpan delayTime, string name, T? contentObj, IDictionary headers, CancellationToken cancellationToken = default);
///
/// Asynchronously schedules a message to be published at a future time.
///
/// The type of the message content object.
/// The delay for the message to be published.
/// The topic name or exchange router key.
/// The message body content that will be serialized. (can be null)
/// The callback subscriber name.
/// The cancellation token.
/// A task representing the asynchronous operation.
Task PublishDelayAsync(TimeSpan delayTime, string name, T? contentObj, string? callbackName = null, CancellationToken cancellationToken = default);
///
/// Schedules a message to be published at a future time with headers.
///
/// The type of the message content object.
/// The delay for the message to be published.
/// The topic name or exchange router key.
/// The message body content that will be serialized. (can be null)
/// The message additional headers.
void PublishDelay(TimeSpan delayTime, string name, T? contentObj, IDictionary headers);
///
/// Schedules a message to be published at a future time.
///
/// The type of the message content object.
/// The delay for the message to be published.
/// The topic name or exchange router key.
/// The message body content that will be serialized. (can be null)
/// The callback subscriber name.
void PublishDelay(TimeSpan delayTime, string name, T? contentObj, string? callbackName = null);
}
================================================
FILE: src/DotNetCore.CAP/ICapSubscribe.cs
================================================
// 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;
///
/// A marker interface that identifies a class as containing CAP subscriber methods.
/// Classes implementing this interface can use CAP subscriber attributes (e.g., [Topic(...)]) on their methods
/// to subscribe to published messages.
///
///
/// This interface serves purely as a marker and does not define any members.
/// Its purpose is to enable automatic discovery and registration of subscriber classes during application startup.
///
/// Usage example:
///
/// public class OrderSubscriber : ICapSubscribe
/// {
/// [CapSubscribe("order.created")]
/// public async Task OnOrderCreated(OrderCreatedMessage message)
/// {
/// // Handle the message
/// await ProcessOrder(message);
/// }
/// }
///
///
public interface ICapSubscribe
{
}
================================================
FILE: src/DotNetCore.CAP/ICapTransaction.Base.cs
================================================
// Copyright (c) .NET 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.Globalization;
using System.Threading;
using System.Threading.Tasks;
using DotNetCore.CAP.Messages;
using DotNetCore.CAP.Persistence;
using DotNetCore.CAP.Transport;
namespace DotNetCore.CAP;
///
/// A thread-safe holder for storing the current CAP transaction context within a scope (e.g., per HTTP request or async execution context).
/// This is used internally to associate a transaction with the ambient execution context.
///
internal sealed class CapTransactionHolder
{
///
/// Gets or sets the CAP transaction associated with the current context.
///
public ICapTransaction? Transaction;
}
///
/// Provides a base implementation of that manages message publishing within a database transaction.
/// This class handles buffering, flushing, and coordination of messages with the message transport layer.
///
///
/// This base class:
///
/// Maintains an internal queue of messages to be published.
/// Provides methods to add messages to the queue and flush them to the dispatcher.
/// Handles both delayed and immediate message publishing based on message headers.
/// Integrates with the dispatcher to enqueue messages for publishing or scheduling.
///
/// Derived classes must implement the transaction-specific Commit/Rollback operations.
///
public abstract class CapTransactionBase : ICapTransaction
{
private readonly ConcurrentQueue _bufferList;
private readonly IDispatcher _dispatcher;
///
/// Initializes a new instance of the class with a dispatcher.
///
/// The dispatcher used to enqueue messages for publishing and execution.
protected CapTransactionBase(IDispatcher dispatcher)
{
_dispatcher = dispatcher;
_bufferList = new ConcurrentQueue();
}
///
/// Gets or sets a value indicating whether this transaction is automatically committed after a message is published.
/// When true, the transaction commits immediately; when false, manual commit is required.
///
public bool AutoCommit { get; set; }
///
/// Gets or sets the underlying database transaction object.
/// This can be cast to the specific database transaction type (e.g., SqlTransaction, NpgsqlTransaction) when needed.
///
public virtual object? DbTransaction { get; set; }
///
/// Commits the transaction synchronously, causing all buffered messages to be sent to the message queue.
///
public abstract void Commit();
///
/// Asynchronously commits the transaction, causing all buffered messages to be sent to the message queue.
///
/// A token to monitor for cancellation requests.
/// A task representing the asynchronous commit operation.
public abstract Task CommitAsync(CancellationToken cancellationToken = default);
///
/// Rolls back the transaction synchronously, discarding all buffered messages without sending them.
///
public abstract void Rollback();
///
/// Asynchronously rolls back the transaction, discarding all buffered messages without sending them.
///
/// A token to monitor for cancellation requests.
/// A task representing the asynchronous rollback operation.
public abstract Task RollbackAsync(CancellationToken cancellationToken = default);
///
/// Adds a message to the internal buffer queue to be sent when the transaction is committed.
/// This is typically called when publishing a message within a transaction context.
///
/// The message to add to the buffer.
protected internal virtual void AddToSent(MediumMessage msg)
{
_bufferList.Enqueue(msg);
}
///
/// Synchronously flushes all buffered messages from the internal queue to the dispatcher.
/// This method blocks until all messages have been enqueued.
///
protected virtual void Flush()
{
FlushAsync().ConfigureAwait(false).GetAwaiter().GetResult();
}
///
/// Asynchronously flushes all buffered messages from the internal queue to the dispatcher.
/// Delayed messages are enqueued to the scheduler; immediate messages are enqueued for publishing.
///
/// A task representing the asynchronous flush operation.
protected virtual async Task FlushAsync()
{
while (!_bufferList.IsEmpty)
{
if (_bufferList.TryDequeue(out var message))
{
var isDelayMessage = message.Origin.Headers.ContainsKey(Headers.DelayTime);
if (isDelayMessage)
{
await _dispatcher.EnqueueToScheduler(message, DateTime.Parse(message.Origin.Headers[Headers.SentTime]!, CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
else
{
await _dispatcher.EnqueueToPublish(message).ConfigureAwait(false);
}
}
}
}
///
/// Disposes the transaction, releasing the underlying database transaction if it implements .
///
public virtual void Dispose()
{
(DbTransaction as IDisposable)?.Dispose();
DbTransaction = null;
}
}
================================================
FILE: src/DotNetCore.CAP/ICapTransaction.cs
================================================
// Copyright (c) .NET 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;
namespace DotNetCore.CAP;
///
/// Represents a CAP transaction wrapper that coordinates message publishing with a database transaction.
/// This interface provides a consistent API for managing outbox pattern implementations where messages
/// must be published atomically with database changes.
///
///
/// The CAP transaction wrapper enables the reliable message delivery pattern by ensuring that:
///
/// Messages are published only when the database transaction succeeds.
/// Uncommitted messages are discarded if the transaction is rolled back.
/// Different message brokers and databases can be supported through implementation-specific subclasses.
///
/// Applications typically obtain an instance of this interface through dependency injection and associate it
/// with a database transaction before publishing messages within that transaction.
///
public interface ICapTransaction : IDisposable
{
///
/// Gets or sets a value indicating whether the transaction is automatically committed after a message is published.
/// When true, the transaction commits immediately upon message publishing; when false, manual commit is required.
///
bool AutoCommit { get; set; }
///
/// Gets or sets the underlying database transaction object.
/// Can be cast to specific database transaction types (e.g., SqlTransaction, NpgsqlTransaction, IDbTransaction)
/// for database-specific operations.
///
object? DbTransaction { get; set; }
///
/// Synchronously commits the transaction context, causing all buffered CAP messages to be sent to the message queue.
/// This must be called after publishing messages within a transaction to ensure they are delivered.
///
void Commit();
///
/// Asynchronously commits the transaction context, causing all buffered CAP messages to be sent to the message queue.
/// This must be called after publishing messages within a transaction to ensure they are delivered.
///
/// A token to monitor for cancellation requests.
/// A task representing the asynchronous commit operation.
Task CommitAsync(CancellationToken cancellationToken = default);
///
/// Synchronously rolls back the transaction context, discarding all buffered CAP messages without sending them.
/// This cancels any messages that were queued but not yet committed.
///
void Rollback();
///
/// Asynchronously rolls back the transaction context, discarding all buffered CAP messages without sending them.
/// This cancels any messages that were queued but not yet committed.
///
/// A token to monitor for cancellation requests.
/// A task representing the asynchronous rollback operation.
Task RollbackAsync(CancellationToken cancellationToken = default);
}
================================================
FILE: src/DotNetCore.CAP/Internal/ConsumerContext.cs
================================================
// Copyright (c) .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.Messages;
using DotNetCore.CAP.Persistence;
namespace DotNetCore.CAP.Internal;
///
/// A context for consumers, it used to be provider wrapper of method description and received message.
///
public class ConsumerContext
{
public ConsumerContext(ConsumerContext context)
: this(context.ConsumerDescriptor, context.MediumMessage)
{
}
///
/// create a new instance of .
///
/// consumer method descriptor.
/// received message.
public ConsumerContext(ConsumerExecutorDescriptor descriptor, MediumMessage message)
{
ConsumerDescriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
MediumMessage = message ?? throw new ArgumentNullException(nameof(message));
}
///
/// a descriptor of consumer information need to be performed.
///
public ConsumerExecutorDescriptor ConsumerDescriptor { get; }
///
/// consumer received message.
///
public Message DeliverMessage => MediumMessage.Origin;
///
/// consumer received medium message.
///
public MediumMessage MediumMessage { get; }
}
================================================
FILE: src/DotNetCore.CAP/Internal/ConsumerExecutedResult.cs
================================================
// Copyright (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;
public class ConsumerExecutedResult
{
public ConsumerExecutedResult(object? result, string msgId, string? callbackName, IDictionary? callbackHeader)
{
Result = result;
MessageId = msgId;
CallbackName = callbackName;
CallbackHeader = callbackHeader;
}
public object? Result { get; set; }
public string MessageId { get; set; }
public string? CallbackName { get; set; }
public IDictionary? CallbackHeader { get; set; }
}
================================================
FILE: src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs
================================================
// Copyright (c) .NET 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 Microsoft.Extensions.Logging;
namespace DotNetCore.CAP.Internal;
///
/// A descriptor of user definition method.
///
public class ConsumerExecutorDescriptor
{
private string? _topicName;
public TypeInfo? ServiceTypeInfo { get; set; }
public MethodInfo MethodInfo { get; set; } = default!;
public TypeInfo ImplTypeInfo { get; set; } = default!;
public TopicAttribute Attribute { get; set; } = default!;
public TopicAttribute? ClassAttribute { get; set; }
public IList Parameters { get; set; } = new List();
public string? TopicNamePrefix { get; set; }
///
/// Topic name based on both and .
///
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;
if (!string.IsNullOrEmpty(TopicNamePrefix) && !string.IsNullOrEmpty(_topicName))
_topicName = $"{TopicNamePrefix}.{_topicName}";
}
return _topicName;
}
}
}
public class ConsumerExecutorDescriptorComparer : IEqualityComparer
{
private readonly ILogger _logger;
public ConsumerExecutorDescriptorComparer(ILogger logger)
{
_logger = logger;
}
public bool Equals(ConsumerExecutorDescriptor? x, ConsumerExecutorDescriptor? y)
{
//Check whether the compared objects reference the same data.
if (ReferenceEquals(x, y))
{
_logger.ConsumerDuplicates(x!.TopicName, x.Attribute.Group);
return true;
}
//Check whether any of the compared objects is null.
if (x is null || y is null) return false;
//Check whether the ConsumerExecutorDescriptor' properties are equal.
var ret = x.TopicName.Equals(y.TopicName, StringComparison.OrdinalIgnoreCase) &&
x.Attribute.Group.Equals(y.Attribute.Group, StringComparison.OrdinalIgnoreCase);
if (ret && (x.ImplTypeInfo != y.ImplTypeInfo || x.MethodInfo != y.MethodInfo)) _logger.ConsumerDuplicates(x.TopicName, x.Attribute.Group);
return ret;
}
public int GetHashCode(ConsumerExecutorDescriptor? obj)
{
//Check whether the object is null
if (obj is null) return 0;
//Get hash code for the Attribute Group field if it is not null.
var hashAttributeGroup = obj.Attribute?.Group == null ? 0 : obj.Attribute.Group.GetHashCode();
//Get hash code for the TopicName field.
var hashTopicName = obj.TopicName.GetHashCode();
//Calculate the hash code.
return hashAttributeGroup ^ hashTopicName;
}
}
public class ParameterDescriptor
{
public string Name { get; set; } = default!;
public Type ParameterType { get; set; } = default!;
public bool IsFromCap { get; set; }
}
================================================
FILE: src/DotNetCore.CAP/Internal/Filter/ExceptionContext.cs
================================================
// Copyright (c) .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.Internal;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP.Filter;
public class ExceptionContext : FilterContext
{
public ExceptionContext(ConsumerContext context, Exception e)
: base(context)
{
Exception = e;
}
public Exception Exception { get; set; }
public bool ExceptionHandled { get; set; }
public object? Result { get; set; }
}
================================================
FILE: src/DotNetCore.CAP/Internal/Filter/ExecutedContext.cs
================================================
// 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;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP.Filter;
public class ExecutedContext : FilterContext
{
public ExecutedContext(ConsumerContext context, object? result) : base(context)
{
Result = result;
}
public object? Result { get; set; }
}
================================================
FILE: src/DotNetCore.CAP/Internal/Filter/ExecutingContext.cs
================================================
// 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;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP.Filter;
public class ExecutingContext : FilterContext
{
public ExecutingContext(ConsumerContext context, object?[] arguments) : base(context)
{
Arguments = arguments;
}
public object?[] Arguments { get; set; }
}
================================================
FILE: src/DotNetCore.CAP/Internal/Filter/FilterContext.cs
================================================
// 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;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP.Filter;
public class FilterContext : ConsumerContext
{
public FilterContext(ConsumerContext context) : base(context)
{
}
}
================================================
FILE: src/DotNetCore.CAP/Internal/Filter/ISubscribeFilter.cs
================================================
// 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
using System.Threading.Tasks;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP.Filter;
///
/// A filter that surrounds execution of the subscriber.
///
public interface ISubscribeFilter
{
///
/// Called before the subscriber executes.
///
/// The .
Task OnSubscribeExecutingAsync(ExecutingContext context);
///
/// Called after the subscriber executes.
///
/// The .
Task OnSubscribeExecutedAsync(ExecutedContext context);
///
/// Called after the subscriber has thrown an .
///
/// The .
Task OnSubscribeExceptionAsync(ExceptionContext context);
}
================================================
FILE: src/DotNetCore.CAP/Internal/Filter/SubscribeFilter.cs
================================================
// 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
using System.Threading.Tasks;
// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP.Filter;
///
/// Abstract base class for ISubscribeFilter for use when implementing a subset of the interface methods.
///
public abstract class SubscribeFilter : ISubscribeFilter
{
///
/// Called before the subscriber executes.
///
/// The .
public virtual Task OnSubscribeExecutingAsync(ExecutingContext context)
{
return Task.CompletedTask;
}
///
/// Called after the subscriber executes.
///
/// The .
public virtual Task OnSubscribeExecutedAsync(ExecutedContext context)
{
return Task.CompletedTask;
}
///
/// Called after the subscriber has thrown an .
///
/// The .
public virtual Task OnSubscribeExceptionAsync(ExceptionContext context)
{
return Task.CompletedTask;
}
}
================================================
FILE: src/DotNetCore.CAP/Internal/Helper.cs
================================================
// Copyright (c) .NET 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.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Text.RegularExpressions;
namespace DotNetCore.CAP.Internal;
public static class Helper
{
public static bool IsController(TypeInfo typeInfo)
{
if (!typeInfo.IsClass) return false;
if (typeInfo.IsAbstract) return false;
if (!typeInfo.IsPublic) return false;
if (typeInfo.ContainsGenericParameters) return false;
return !typeInfo.ContainsGenericParameters
&& typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase);
}
public static bool IsComplexType(Type type)
{
return !CanConvertFromString(type);
}
public static string WildcardToRegex(string wildcard)
{
if (wildcard.IndexOf('*') >= 0) return ("^" + wildcard + "$").Replace("*", "[0-9a-zA-Z]+").Replace(".", "\\.");
if (wildcard.IndexOf('#') >= 0)
return ("^" + wildcard.Replace(".", "\\.") + "$").Replace("#", "[0-9a-zA-Z\\.]+");
return wildcard;
}
public static string? GetInstanceHostname()
{
try
{
var hostName = Dns.GetHostName();
if (hostName.Length <= 50) return hostName;
return hostName.Substring(0, 50);
}
catch
{
return null;
}
}
public static string Normalized(string name)
{
if (string.IsNullOrEmpty(name)) return name;
var pattern = "[\\>\\.\\ \\*]";
return Regex.IsMatch(name, pattern) ? Regex.Replace(name, pattern, "_") : name;
}
public static bool IsUsingType(in Type type)
{
var flags = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance |
BindingFlags.DeclaredOnly;
return type.GetFields(flags).Any(x => x.FieldType == typeof(T));
}
public static bool IsInnerIP(string ipAddress)
{
var ipNum = GetIpNum(ipAddress);
//Private IP:
//category A: 10.0.0.0-10.255.255.255
//category B: 172.16.0.0-172.31.255.255
//category C: 192.168.0.0-192.168.255.255
var aBegin = GetIpNum("10.0.0.0");
var aEnd = GetIpNum("10.255.255.255");
var bBegin = GetIpNum("172.16.0.0");
var bEnd = GetIpNum("172.31.255.255");
var cBegin = GetIpNum("192.168.0.0");
var cEnd = GetIpNum("192.168.255.255");
return IsInner(ipNum, aBegin, aEnd) || IsInner(ipNum, bBegin, bEnd) || IsInner(ipNum, cBegin, cEnd);
}
private static long GetIpNum(string ipAddress)
{
var ip = ipAddress.Split('.');
long a = int.Parse(ip[0]);
long b = int.Parse(ip[1]);
long c = int.Parse(ip[2]);
long d = int.Parse(ip[3]);
var ipNum = a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
return ipNum;
}
private static bool IsInner(long userIp, long begin, long end)
{
return userIp >= begin && userIp <= end;
}
private static bool CanConvertFromString(Type destinationType)
{
destinationType = Nullable.GetUnderlyingType(destinationType) ?? destinationType;
return IsSimpleType(destinationType) ||
TypeDescriptor.GetConverter(destinationType).CanConvertFrom(typeof(string));
}
private static bool IsSimpleType(Type type)
{
return type.GetTypeInfo().IsPrimitive ||
type == typeof(decimal) ||
type == typeof(string) ||
type == typeof(DateTime) ||
type == typeof(Guid) ||
type == typeof(DateTimeOffset) ||
type == typeof(TimeSpan) ||
type == typeof(Uri);
}
internal static void ReThrow(this Exception exception)
{
ExceptionDispatchInfo.Capture(exception).Throw();
}
}
================================================
FILE: src/DotNetCore.CAP/Internal/IBootstrapper.Default.cs
================================================
// Copyright (c) .NET 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;
using DotNetCore.CAP.Persistence;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP.Internal;
///
/// Default implement of .
///
internal class Bootstrapper : BackgroundService, IBootstrapper
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private CancellationTokenSource? _cts;
private bool _disposed;
private IEnumerable _processors = default!;
public bool IsStarted => !_cts?.IsCancellationRequested ?? false;
public Bootstrapper(IServiceProvider serviceProvider, ILogger logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
public async Task BootstrapAsync(CancellationToken cancellationToken = default)
{
if (_cts != null)
{
_logger.LogInformation("### CAP background task is already started!");
return;
}
_logger.LogDebug("### CAP background task is starting.");
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
CheckRequirement();
_processors = _serviceProvider.GetServices();
try
{
await _serviceProvider.GetRequiredService().InitializeAsync(_cts.Token).ConfigureAwait(false);
}
catch (Exception e)
{
if (e is InvalidOperationException) throw;
_logger.LogError(e, "Initializing the storage structure failed!");
}
_cts.Token.Register(() =>
{
_logger.LogDebug("### CAP background task is stopping.");
foreach (var item in _processors)
try
{
item.Dispose();
}
catch (OperationCanceledException ex)
{
_logger.ExpectedOperationCanceledException(ex);
}
});
await BootstrapCoreAsync().ConfigureAwait(false);
_disposed = false;
_logger.LogInformation("### CAP started!");
}
protected virtual async Task BootstrapCoreAsync()
{
foreach (var item in _processors)
{
try
{
_cts!.Token.ThrowIfCancellationRequested();
await item.StartAsync(_cts!.Token);
}
catch (OperationCanceledException)
{
// ignore
}
catch (Exception ex)
{
_logger.ProcessorsStartedError(ex);
}
}
}
public override void Dispose()
{
if (_disposed) return;
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
_disposed = true;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await BootstrapAsync(stoppingToken).ConfigureAwait(false);
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
_cts?.Cancel();
await base.StopAsync(cancellationToken).ConfigureAwait(false);
}
private void CheckRequirement()
{
var marker = _serviceProvider.GetService();
if (marker == null)
throw new InvalidOperationException(
"AddCap() must be added on the service collection. eg: services.AddCap(...)");
var messageQueueMarker = _serviceProvider.GetService();
if (messageQueueMarker == null)
throw new InvalidOperationException(
"You must be config transport provider for CAP!" + Environment.NewLine +
"==================================================================================" +
Environment.NewLine +
"======== eg: services.AddCap( options => { options.UseRabbitMQ(...) }); ========" +
Environment.NewLine +
"==================================================================================");
var databaseMarker = _serviceProvider.GetService();
if (databaseMarker == null)
throw new InvalidOperationException(
"You must be config storage provider for CAP!" + Environment.NewLine +
"===================================================================================" +
Environment.NewLine +
"======== eg: services.AddCap( options => { options.UseSqlServer(...) }); ========" +
Environment.NewLine +
"===================================================================================");
}
public ValueTask DisposeAsync()
{
Dispose();
return ValueTask.CompletedTask;
}
}
================================================
FILE: src/DotNetCore.CAP/Internal/ICapPublisher.Default.cs
================================================
// Copyright (c) .NET 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;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using DotNetCore.CAP.Diagnostics;
using DotNetCore.CAP.Messages;
using DotNetCore.CAP.Persistence;
using DotNetCore.CAP.Transport;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace DotNetCore.CAP.Internal;
internal class CapPublisher : ICapPublisher
{
// ReSharper disable once InconsistentNaming
protected static readonly DiagnosticListener s_diagnosticListener =
new(CapDiagnosticListenerNames.DiagnosticListenerName);
private readonly CapOptions _capOptions;
private readonly ISnowflakeId _snowflakeId;
private readonly IDispatcher _dispatcher;
private readonly IDataStorage _storage;
private readonly AsyncLocal _asyncLocal;
public CapPublisher(IServiceProvider service)
{
ServiceProvider = service;
_dispatcher = service.GetRequiredService();
_storage = service.GetRequiredService();
_capOptions = service.GetRequiredService>().Value;
_snowflakeId = service.GetRequiredService();
_asyncLocal = new AsyncLocal();
}
public IServiceProvider ServiceProvider { get; }
public ICapTransaction? Transaction
{
get => _asyncLocal.Value?.Transaction;
set
{
_asyncLocal.Value ??= new CapTransactionHolder();
_asyncLocal.Value.Transaction = value;
}
}
public async Task PublishAsync(string name, T? value, IDictionary headers,
CancellationToken cancellationToken = default)
{
await PublishInternalAsync(name, value, headers, null, cancellationToken).ConfigureAwait(false);
}
public async Task PublishAsync(string name, T? value, string? callbackName = null,
CancellationToken cancellationToken = default)
{
var headers = new Dictionary
{
{ Headers.CallbackName, callbackName }
};
await PublishAsync(name, value, headers, cancellationToken).ConfigureAwait(false);
}
public async Task PublishDelayAsync(TimeSpan delayTime, string name, T? value, IDictionary headers,
CancellationToken cancellationToken = default)
{
if (delayTime <= TimeSpan.Zero)
{
throw new ArgumentException("Delay time span must be greater than 0", nameof(delayTime));
}
await PublishInternalAsync(name, value, headers, delayTime, cancellationToken).ConfigureAwait(false);
}
public async Task PublishDelayAsync(TimeSpan delayTime, string name, T? value, string? callbackName = null,
CancellationToken cancellationToken = default)
{
var header = new Dictionary
{
{ Headers.CallbackName, callbackName }
};
await PublishDelayAsync(delayTime, name, value, header, cancellationToken).ConfigureAwait(false);
}
public void Publish(string name, T? value, string? callbackName = null)
{
PublishAsync(name, value, callbackName).ConfigureAwait(false).GetAwaiter().GetResult();
}
public void Publish(string name, T? value, IDictionary headers)
{
PublishAsync(name, value, headers).ConfigureAwait(false).GetAwaiter().GetResult();
}
public void PublishDelay(TimeSpan delayTime, string name, T? value, IDictionary headers)
{
PublishDelayAsync(delayTime, name, value, headers).ConfigureAwait(false).GetAwaiter().GetResult();
}
public void PublishDelay(TimeSpan delayTime, string name, T? value, string? callbackName = null)
{
PublishDelayAsync(delayTime, name, value, callbackName).ConfigureAwait(false).GetAwaiter().GetResult();
}
private async Task PublishInternalAsync(string name, T? value, IDictionary headers, TimeSpan? delayTime = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
if (!string.IsNullOrEmpty(_capOptions.TopicNamePrefix)) name = $"{_capOptions.TopicNamePrefix}.{name}";
if (!headers.ContainsKey(Headers.MessageId))
{
var messageId = _snowflakeId.NextId().ToString();
headers.Add(Headers.MessageId, messageId);
}
if (!headers.ContainsKey(Headers.CorrelationId))
{
headers.Add(Headers.CorrelationId, headers[Headers.MessageId]);
headers.Add(Headers.CorrelationSequence, 0.ToString());
}
headers.Add(Headers.MessageName, name);
headers.Add(Headers.Type, typeof(T).Name);
var publishTime = DateTime.Now;
if (delayTime != null)
{
publishTime += delayTime.Value;
headers.Add(Headers.DelayTime, delayTime.Value.ToString());
headers.Add(Headers.SentTime, publishTime.ToString(CultureInfo.InvariantCulture));
}
else
{
headers.Add(Headers.SentTime, publishTime.ToString(CultureInfo.InvariantCulture));
}
var message = new Message(headers, value);
long? tracingTimestamp = null;
try
{
tracingTimestamp = TracingBefore(message);
if (Transaction?.DbTransaction == null)
{
var mediumMessage = await _storage.StoreMessageAsync(name, message).ConfigureAwait(false);
TracingAfter(tracingTimestamp, message);
if (delayTime != null)
{
await _dispatcher.EnqueueToScheduler(mediumMessage, publishTime).ConfigureAwait(false);
}
else
{
await _dispatcher.EnqueueToPublish(mediumMessage).ConfigureAwait(false);
}
}
else
{
var transaction = (CapTransactionBase)Transaction;
var mediumMessage = await _storage.StoreMessageAsync(name, message, transaction.DbTransaction).ConfigureAwait(false);
TracingAfter(tracingTimestamp, message);
transaction.AddToSent(mediumMessage);
if (transaction.AutoCommit) await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (Exception e)
{
TracingError(tracingTimestamp, message, e);
throw;
}
}
#region tracing
private static long? TracingBefore(Message message)
{
if (s_diagnosticListener.IsEnabled(CapDiagnosticListenerNames.BeforePublishMessageStore))
{
var eventData = new CapEventDataPubStore
{
OperationTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Operation = message.GetName(),
Message = message
};
s_diagnosticListener.Write(CapDiagnosticListenerNames.BeforePublishMessageStore, eventData);
return eventData.OperationTimestamp;
}
return null;
}
private static void TracingAfter(long? tracingTimestamp, Message message)
{
if (tracingTimestamp != null &&
s_diagnosticListener.IsEnabled(CapDiagnosticListenerNames.AfterPublishMessageStore))
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var eventData = new CapEventDataPubStore
{
OperationTimestamp = now,
Operation = message.GetName(),
Message = message,
ElapsedTimeMs = now - tracingTimestamp.Value
};
s_diagnosticListener.Write(CapDiagnosticListenerNames.AfterPublishMessageStore, eventData);
}
}
private static void TracingError(long? tracingTimestamp, Message message, Exception ex)
{
if (tracingTimestamp != null &&
s_diagnosticListener.IsEnabled(CapDiagnosticListenerNames.ErrorPublishMessageStore))
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var eventData = new CapEventDataPubStore
{
OperationTimestamp = now,
Operation = message.GetName(),
Message = message,
ElapsedTimeMs = now - tracingTimestamp.Value,
Exception = ex
};
s_diagnosticListener.Write(CapDiagnosticListenerNames.ErrorPublishMessageStore, eventData);
}
}
#endregion
}
================================================
FILE: src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs
================================================
// Copyright (c) .NET 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;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DotNetCore.CAP.Diagnostics;
using DotNetCore.CAP.Messages;
using DotNetCore.CAP.Persistence;
using DotNetCore.CAP.Serialization;
using DotNetCore.CAP.Transport;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DotNetCore.CAP.Internal;
internal class ConsumerRegister : IConsumerRegister
{
// diagnostics listener
// ReSharper disable once InconsistentNaming
private static readonly DiagnosticListener s_diagnosticListener =
new(CapDiagnosticListenerNames.DiagnosticListenerName);
private readonly ILogger _logger;
private readonly CapOptions _options;
private readonly TimeSpan _pollingDelay = TimeSpan.FromSeconds(1);
private readonly IServiceProvider _serviceProvider;
private Task? _compositeTask;
private IConsumerClientFactory _consumerClientFactory = default!;
private CancellationTokenSource _cts = new();
private IDispatcher _dispatcher = default!;
private int _disposed;
private bool _isHealthy = true;
private MethodMatcherCache _selector = default!;
private ISerializer _serializer = default!;
private BrokerAddress _serverAddress;
private IDataStorage _storage = default!;
public ConsumerRegister(ILogger logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
_options = serviceProvider.GetRequiredService