├── .gitattributes ├── .gitignore ├── Changelog.md ├── License.txt ├── README.md ├── Updating.md ├── doc └── Example setup │ ├── Broker1 │ └── NetCoreMQTTExampleCluster.Cluster.json │ └── Broker2 │ └── NetCoreMQTTExampleCluster.Cluster.json ├── images ├── broker-connect.png ├── broker-connect.svg ├── broker-connect.wsd ├── broker-disconnect.png ├── broker-disconnect.svg ├── broker-disconnect.wsd ├── client-connect1.png ├── client-connect1.svg ├── client-connect1.wsd ├── client-connect2.png ├── client-connect2.svg ├── client-connect2.wsd ├── client-disconnect.png ├── client-disconnect.svg ├── client-disconnect.wsd ├── client-publish1.png ├── client-publish1.svg ├── client-publish1.wsd ├── client-publish2.png ├── client-publish2.svg ├── client-publish2.wsd ├── client-subscribe1.png ├── client-subscribe1.svg ├── client-subscribe1.wsd ├── client-subscribe2.png ├── client-subscribe2.svg ├── client-subscribe2.wsd ├── client-unsubscribe.png ├── client-unsubscribe.svg ├── client-unsubscribe.wsd ├── structure.png ├── structure.svg └── structure.wsd └── src ├── .editorconfig ├── Delete-BIN-OBJ-Folders.bat ├── NetCoreMQTTExampleCluster.Cluster ├── App.razor ├── GlobalUsings.cs ├── MqttService.cs ├── NetCoreMQTTExampleCluster.Cluster.csproj ├── Program.cs ├── Startup.cs ├── _Imports.razor ├── appsettings.Development.json ├── appsettings.json ├── cert.pem ├── certificate.pfx └── key.pem ├── NetCoreMQTTExampleCluster.DatabaseSetup ├── GlobalUsings.cs ├── NetCoreMQTTExampleCluster.DatabaseSetup.csproj ├── Program.cs └── appsettings.json ├── NetCoreMQTTExampleCluster.Grains.Interfaces ├── GlobalUsings.cs ├── IMqttClientGrain.cs ├── IMqttRepositoryGrain.cs ├── NetCoreMQTTExampleCluster.Grains.Interfaces.csproj ├── SimpleClientDisconnectedEventArgs.cs ├── SimpleClientUnsubscribedTopicEventArgs.cs ├── SimpleInterceptingPublishEventArgs.cs ├── SimpleInterceptingSubscriptionEventArgs.cs └── SimpleValidatingConnectionEventArgs.cs ├── NetCoreMQTTExampleCluster.Grains ├── GlobalUsings.cs ├── MqttClientGrain.cs ├── MqttRepositoryGrain.cs └── NetCoreMQTTExampleCluster.Grains.csproj ├── NetCoreMQTTExampleCluster.Models ├── Configuration │ ├── BrokerConnectionSettings.cs │ ├── ClusterConfiguration.cs │ ├── MqttDatabaseConnectionSettings.cs │ ├── OrleansConfiguration.cs │ ├── OrleansSiloConfiguration.cs │ ├── SiloEndpointOptions.cs │ └── SiloHostConfiguration.cs ├── Constants │ └── GlobalConstants.cs ├── Exceptions │ └── ConfigurationException.cs ├── Extensions │ ├── DashboardOptionsExtensions.cs │ ├── DateTimeExtensions.cs │ ├── DateTimeOffsetExtensions.cs │ └── IntegerExtensions.cs ├── GlobalUsings.cs ├── Helper │ └── FileSizeHelper.cs ├── Interfaces │ ├── IBrokerConnectionSettings.cs │ └── IConfigurationValid.cs ├── NetCoreMQTTExampleCluster.Models.csproj └── Service │ └── BackgroundServiceBase.cs ├── NetCoreMQTTExampleCluster.PasswordHashGenerator ├── GlobalUsings.cs ├── NetCoreMQTTExampleCluster.PasswordHashGenerator.csproj └── Program.cs ├── NetCoreMQTTExampleCluster.SiloHost ├── GlobalUsings.cs ├── NetCoreMQTTExampleCluster.SiloHost.csproj ├── OrleansAdoNetContent │ ├── MySQL │ │ ├── MySQL-Clustering.sql │ │ ├── MySQL-Main.sql │ │ ├── MySQL-Persistence.sql │ │ └── MySQL-Reminders.sql │ ├── Oracle │ │ ├── Oracle-Clustering.sql │ │ ├── Oracle-Main.sql │ │ ├── Oracle-Persistence.sql │ │ └── Oracle-Reminders.sql │ ├── PostgreSQL │ │ ├── PostgreSQL-Clustering.sql │ │ ├── PostgreSQL-Main.sql │ │ ├── PostgreSQL-Persistence.sql │ │ └── PostgreSQL-Reminders.sql │ └── SQLServer │ │ ├── SQLServer-Clustering.sql │ │ ├── SQLServer-Main.sql │ │ ├── SQLServer-Persistence.sql │ │ └── SQLServer-Reminders.sql ├── Program.cs ├── SiloHostService.cs ├── Startup.cs ├── appsettings.Development.json └── appsettings.json ├── NetCoreMQTTExampleCluster.Storage ├── Data │ ├── BlacklistWhiteList.cs │ ├── BlacklistWhitelistType.cs │ ├── DatabaseVersion.cs │ ├── EventLog.cs │ ├── EventType.cs │ ├── MqttUser.cs │ ├── MqttUserData.cs │ ├── PublishMessage.cs │ ├── PublishedMessagePayload.cs │ └── WebUser.cs ├── DatabaseHelper.cs ├── GlobalUsings.cs ├── IDatabaseHelper.cs ├── Interfaces │ └── IMqttDatabaseConnectionSettings.cs ├── Mappers │ └── JsonMapper.cs ├── NetCoreMQTTExampleCluster.Storage.csproj ├── OrleansQueries │ ├── PostgreSQL-Clustering.sql │ ├── PostgreSQL-Main.sql │ ├── PostgreSQL-Persistence.sql │ └── PostgreSQL-Reminders.sql ├── Repositories │ ├── Implementation │ │ ├── BaseRepository.cs │ │ ├── BlacklistRepository.cs │ │ ├── DatabaseVersionRepository.cs │ │ ├── EventLogRepository.cs │ │ ├── MqttUserRepository.cs │ │ ├── PublishMessageRepository.cs │ │ ├── WebUserRepository.cs │ │ └── WhitelistRepository.cs │ └── Interfaces │ │ ├── IBlacklistRepository.cs │ │ ├── IDatabaseVersionRepository.cs │ │ ├── IEventLogRepository.cs │ │ ├── IMqttUserRepository.cs │ │ ├── IPublishMessageRepository.cs │ │ ├── IWebUserRepository.cs │ │ └── IWhitelistRepository.cs └── Statements │ ├── CreateStatements.cs │ ├── DropStatements.cs │ ├── ExistsStatements.cs │ ├── InsertStatements.cs │ └── SelectStatements.cs ├── NetCoreMQTTExampleCluster.TopicCheck.Tests ├── GlobalUsings.cs ├── NetCoreMQTTExampleCluster.TopicCheck.Tests.csproj ├── TopicCheckerTestsCrossOperator.cs ├── TopicCheckerTestsMultipleOperators.cs └── TopicCheckerTestsPlusOperator.cs ├── NetCoreMQTTExampleCluster.TopicCheck ├── GlobalUsings.cs ├── NetCoreMQTTExampleCluster.TopicCheck.csproj └── TopicChecker.cs ├── NetCoreMQTTExampleCluster.Validation.Tests ├── GlobalUsings.cs ├── Helper │ └── UserMqttRepositoryFake.cs ├── NetCoreMQTTExampleCluster.Validation.Tests.csproj └── TestValidation.cs ├── NetCoreMQTTExampleCluster.Validation ├── GlobalUsings.cs ├── IMqttValidator.cs ├── MqttValidator.cs └── NetCoreMQTTExampleCluster.Validation.csproj ├── NetCoreMQTTExampleCluster.sln └── NetCoreMQTTExampleCluster.sln.DotSettings /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #Ignore thumbnails created by Windows 3 | Thumbs.db 4 | #Ignore files built by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | .vs/ 31 | #Nuget packages folder 32 | packages/ 33 | private-packages/ 34 | InstallFiles.wxs 35 | node_modules/ 36 | package-lock.json -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | Change history 2 | -------------- 3 | 4 | * **Version 1.4.0.0 (2020-06-29)** : Updated nuget packages, simplified usage of Systemd integration, made MqttRepositoryGrain reentrant. 5 | * **Version 1.3.0.0 (2020-06-05)** : Fixed synchronization issues. 6 | * **Version 1.2.0.0 (2020-05-06)** : Switched to .NetCore worker service. 7 | * **Version 1.1.0.0 (2020-05-06)** : Added log folder path. 8 | * **Version 1.0.0.0 (2020-04-19)** : 1.0 release. -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) SeppPenner (https://github.com/SeppPenner) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Updating.md: -------------------------------------------------------------------------------- 1 | # Updating 2 | 3 | Always update https://dotnet.github.io/orleans/Documentation/clusters_and_clients/configuration_guide/adonet_configuration.html as well. -------------------------------------------------------------------------------- /doc/Example setup/Broker1/NetCoreMQTTExampleCluster.Cluster.json: -------------------------------------------------------------------------------- 1 | { 2 | "Port": 8883, 3 | "UnencryptedPort": 1883, 4 | "BrokerConnectionSettings": { 5 | "ClientId": "Cluster_1", 6 | "HostName": "localhost", 7 | "Port": 1883, 8 | "UserName": "Test", 9 | "Password": "test", 10 | "UseTls": false, 11 | "UseCleanSession": true 12 | }, 13 | "DatabaseSettings": { 14 | "Host": "localhost", 15 | "Database": "mqtt", 16 | "Username": "postgres", 17 | "Password": "postgres", 18 | "Port": 5432 19 | }, 20 | "OrleansConfiguration": { 21 | "ClusterOptions": { 22 | "ClusterId": "mqtt001", 23 | "ServiceId": "mqtt-silo-001-A" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /doc/Example setup/Broker2/NetCoreMQTTExampleCluster.Cluster.json: -------------------------------------------------------------------------------- 1 | { 2 | "Port": 8884, 3 | "UnencryptedPort": 1884, 4 | "BrokerConnectionSettings": { 5 | "ClientId": "Cluster_2", 6 | "HostName": "localhost", 7 | "Port": 1884, 8 | "UserName": "Test", 9 | "Password": "test", 10 | "UseTls": false, 11 | "UseCleanSession": true 12 | }, 13 | "DatabaseSettings": { 14 | "Host": "localhost", 15 | "Database": "mqtt", 16 | "Username": "postgres", 17 | "Password": "postgres", 18 | "Port": 5432 19 | }, 20 | "OrleansConfiguration": { 21 | "ClusterOptions": { 22 | "ClusterId": "mqtt001", 23 | "ServiceId": "mqtt-silo-001-A" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /images/broker-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/broker-connect.png -------------------------------------------------------------------------------- /images/broker-connect.svg: -------------------------------------------------------------------------------- 1 | SiloHostMqttBrokerConnect(brokerConnectionSettings, brokerId)StoreBrokerIdInDictionary() -------------------------------------------------------------------------------- /images/broker-connect.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "Connect(brokerConnectionSettings, brokerId)" as ConnectPackage 6 | file "StoreBrokerIdInDictionary()" as Dictionary 7 | 8 | frame SiloHost { 9 | } 10 | 11 | frame "MqttBroker" as MqttBroker { 12 | } 13 | 14 | MqttBroker --> ConnectPackage 15 | ConnectPackage --> SiloHost 16 | SiloHost --> Dictionary 17 | 18 | @enduml -------------------------------------------------------------------------------- /images/broker-disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/broker-disconnect.png -------------------------------------------------------------------------------- /images/broker-disconnect.svg: -------------------------------------------------------------------------------- 1 | SiloHostMqttBrokerDisconnect(brokerId)RemoveBrokerFromDictionary() -------------------------------------------------------------------------------- /images/broker-disconnect.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "Disconnect(brokerId)" as DisconnectPackage 6 | file "RemoveBrokerFromDictionary()" as Dictionary 7 | 8 | frame SiloHost { 9 | } 10 | 11 | frame "MqttBroker" as MqttBroker { 12 | } 13 | 14 | MqttBroker --> DisconnectPackage 15 | DisconnectPackage --> SiloHost 16 | SiloHost --> Dictionary 17 | 18 | @enduml -------------------------------------------------------------------------------- /images/client-connect1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/client-connect1.png -------------------------------------------------------------------------------- /images/client-connect1.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "Connect(MqttConnectionValidatorContext)" as ConnectPackage 6 | file "SimpleMqttConnectionValidatorContext" as ConnectPackage2 7 | file "ValidateCredentials()" as ValidateCredentials 8 | 9 | frame SiloHost { 10 | } 11 | 12 | frame "MqttClient" as MqttClient { 13 | } 14 | 15 | frame "MqttBroker" as MqttBroker { 16 | } 17 | 18 | MqttClient --> ConnectPackage 19 | ConnectPackage --> MqttBroker 20 | MqttBroker --> ConnectPackage2 21 | ConnectPackage2 --> SiloHost 22 | SiloHost --> ValidateCredentials 23 | 24 | @enduml -------------------------------------------------------------------------------- /images/client-connect2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/client-connect2.png -------------------------------------------------------------------------------- /images/client-connect2.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "ValidateCredentials()" as ValidateCredentials 6 | file "true" as CanConnect 7 | file "true" as CanConnect2 8 | file "true" as CanConnect3 9 | 10 | frame SiloHost { 11 | } 12 | 13 | frame "MqttClient" as MqttClient { 14 | } 15 | 16 | frame "MqttBroker" as MqttBroker { 17 | } 18 | 19 | ValidateCredentials --> CanConnect 20 | CanConnect --> SiloHost 21 | SiloHost --> CanConnect2 22 | CanConnect2 --> MqttBroker 23 | MqttBroker --> CanConnect3 24 | CanConnect3 --> MqttClient 25 | 26 | @enduml -------------------------------------------------------------------------------- /images/client-disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/client-disconnect.png -------------------------------------------------------------------------------- /images/client-disconnect.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "Disconnect(MqttServerClientDisconnectedEventArgs)" as DisconnectPackage 6 | file "MqttServerClientDisconnectedEventArgs" as DisconnectPackage2 7 | file "ClearUserData()" as ClearUserData 8 | 9 | frame SiloHost { 10 | } 11 | 12 | frame "MqttClient" as MqttClient { 13 | } 14 | 15 | frame "MqttBroker" as MqttBroker { 16 | } 17 | 18 | MqttClient --> DisconnectPackage 19 | DisconnectPackage --> MqttBroker 20 | MqttBroker --> DisconnectPackage2 21 | DisconnectPackage2 --> SiloHost 22 | SiloHost --> ClearUserData 23 | 24 | @enduml -------------------------------------------------------------------------------- /images/client-publish1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/client-publish1.png -------------------------------------------------------------------------------- /images/client-publish1.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "Publish(MqttApplicationMessageInterceptorContext)" as PublishPackage 6 | file "Publish(MqttApplicationMessageInterceptorContext, brokerId)" as PublishPackage2 7 | file "ValidatePublish()" as ValidatePublish 8 | 9 | frame SiloHost { 10 | } 11 | 12 | frame "MqttClient" as MqttClient { 13 | } 14 | 15 | frame "MqttBroker" as MqttBroker { 16 | } 17 | 18 | MqttClient --> PublishPackage 19 | PublishPackage --> MqttBroker 20 | MqttBroker --> PublishPackage2 21 | PublishPackage2 --> SiloHost 22 | SiloHost --> ValidatePublish 23 | 24 | @enduml -------------------------------------------------------------------------------- /images/client-publish2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/client-publish2.png -------------------------------------------------------------------------------- /images/client-publish2.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "ValidatePublish()" as ValidatePublish 6 | file "Publish(MqttApplicationMessageInterceptorContext)" as Publish 7 | file "true" as CanPublish 8 | file "true" as CanPublish2 9 | file "true" as CanPublish3 10 | 11 | frame SiloHost { 12 | } 13 | 14 | frame "MqttClient" as MqttClient { 15 | } 16 | 17 | frame "MqttBroker" as MqttBroker { 18 | } 19 | 20 | frame "MqttBroker 2" as MqttBroker2 { 21 | } 22 | 23 | frame "MqttBroker n" as MqttBrokerN { 24 | } 25 | 26 | ValidatePublish --> CanPublish 27 | CanPublish --> SiloHost 28 | SiloHost --> CanPublish2 29 | SiloHost --> Publish 30 | Publish --> MqttBroker2 31 | Publish --> MqttBrokerN 32 | CanPublish2 --> MqttBroker 33 | MqttBroker --> CanPublish3 34 | CanPublish3 --> MqttClient 35 | 36 | @enduml -------------------------------------------------------------------------------- /images/client-subscribe1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/client-subscribe1.png -------------------------------------------------------------------------------- /images/client-subscribe1.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "Subscribe(MqttSubscriptionInterceptorContext)" as SubscribePackage 6 | file "MqttSubscriptionInterceptorContext" as SubscribePackage2 7 | file "ValidateSubscribe()" as ValidateSubscribe 8 | 9 | frame SiloHost { 10 | } 11 | 12 | frame "MqttClient" as MqttClient { 13 | } 14 | 15 | frame "MqttBroker" as MqttBroker { 16 | } 17 | 18 | MqttClient --> SubscribePackage 19 | SubscribePackage --> MqttBroker 20 | MqttBroker --> SubscribePackage2 21 | SubscribePackage2 --> SiloHost 22 | SiloHost --> ValidateSubscribe 23 | 24 | @enduml -------------------------------------------------------------------------------- /images/client-subscribe2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/client-subscribe2.png -------------------------------------------------------------------------------- /images/client-subscribe2.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "ValidateSubscribe()" as ValidateSubscribe 6 | file "true" as CanSubscribe 7 | file "true" as CanSubscribe2 8 | file "true" as CanSubscribe3 9 | 10 | frame SiloHost { 11 | } 12 | 13 | frame "MqttClient" as MqttClient { 14 | } 15 | 16 | frame "MqttBroker" as MqttBroker { 17 | } 18 | 19 | ValidateSubscribe --> CanSubscribe 20 | CanSubscribe --> SiloHost 21 | SiloHost --> CanSubscribe2 22 | CanSubscribe2 --> MqttBroker 23 | MqttBroker --> CanSubscribe3 24 | CanSubscribe3 --> MqttClient 25 | 26 | @enduml -------------------------------------------------------------------------------- /images/client-unsubscribe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/client-unsubscribe.png -------------------------------------------------------------------------------- /images/client-unsubscribe.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "Unsubscribe(MqttUnsubscriptionInterceptorContext)" as UnsubscribePackage 6 | file "MqttUnsubscriptionInterceptorContext" as UnsubscribePackage2 7 | 8 | frame SiloHost { 9 | } 10 | 11 | frame "MqttClient" as MqttClient { 12 | } 13 | 14 | frame "MqttBroker" as MqttBroker { 15 | } 16 | 17 | MqttClient --> UnsubscribePackage 18 | UnsubscribePackage --> MqttBroker 19 | MqttBroker --> UnsubscribePackage2 20 | UnsubscribePackage2 --> SiloHost 21 | 22 | @enduml -------------------------------------------------------------------------------- /images/structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/images/structure.png -------------------------------------------------------------------------------- /images/structure.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | 5 | file "Synchronization" as Sync 6 | 7 | frame SiloHost { 8 | } 9 | 10 | frame "MqttBroker 1" as MqttBroker1 { 11 | } 12 | 13 | frame "MqttBroker 2" as MqttBroker2 { 14 | } 15 | 16 | frame "MqttBroker n" as MqttBrokerN { 17 | } 18 | 19 | frame "MqttClient 1" as MqttClient1 { 20 | } 21 | 22 | frame "MqttClient 2" as MqttClient2 { 23 | } 24 | 25 | frame "MqttClient 3" as MqttClient3 { 26 | } 27 | 28 | frame "MqttClient n" as MqttClientN { 29 | } 30 | 31 | MqttBroker1 --> SiloHost 32 | MqttBroker2 --> SiloHost 33 | MqttBrokerN --> SiloHost 34 | MqttClient1 --> MqttBroker1 35 | MqttClient2 --> MqttBroker2 36 | MqttClient3 --> MqttBroker2 37 | MqttClientN --> MqttBrokerN 38 | SiloHost --> Sync 39 | Sync --> SiloHost 40 | 41 | @enduml -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = crlf 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | csharp_using_directive_placement=inside_namespace:warning 9 | csharp_prefer_braces=true:warning 10 | dotnet_style_allow_multiple_blank_lines_experimental=false:warning 11 | dotnet_style_allow_statement_immediately_after_block_experimental=false:warning 12 | dotnet_style_qualification_for_field=true:warning 13 | dotnet_style_qualification_for_property=true:warning 14 | dotnet_style_qualification_for_method=true:warning 15 | dotnet_style_qualification_for_event=true:warning 16 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental=false:warning 17 | dotnet_sort_system_directives_first=true:warning 18 | dotnet_diagnostic.IDE0005.severity=warning 19 | 20 | [*.cs] 21 | csharp_style_namespace_declarations = file_scoped:warning -------------------------------------------------------------------------------- /src/Delete-BIN-OBJ-Folders.bat: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | cls 3 | 4 | ECHO Deleting all BIN and OBJ folders... 5 | ECHO. 6 | 7 | FOR /d /r . %%d in (bin,obj) DO ( 8 | IF EXIST "%%d" ( 9 | ECHO %%d | FIND /I "\node_modules\" > Nul && ( 10 | ECHO.Skipping: %%d 11 | ) || ( 12 | ECHO.Deleting: %%d 13 | rd /s/q "%%d" 14 | ) 15 | ) 16 | ) 17 | 18 | ECHO. 19 | ECHO.BIN and OBJ folders have been successfully deleted. Press any key to exit. 20 | pause > nul -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Cluster/App.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.Extensions.Localization 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | @code 33 | { 34 | /// 35 | /// Gets or sets the localizer. 36 | /// 37 | [Inject] 38 | protected IStringLocalizer? Localizer { get; set; } 39 | 40 | /// 41 | /// Gets or sets the app state. 42 | /// 43 | private AppStateProvider AppState { get; set; } = new(); 44 | 45 | /// 46 | /// The method called when the go to login action is triggered. 47 | /// 48 | /// A representing any asynchronous operation. 49 | public async Task OnGoToLogin() 50 | { 51 | this.AppState!.ToastSuccess(this.Localizer!["SuccessfullyLoggedOut"]); 52 | await Task.Delay(1000); 53 | await this.AppState.AuthStateProvider!.Logout(); 54 | await this.AppState.AuthStateProvider!.NavigateToLogin(); 55 | } 56 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Cluster/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System.Diagnostics.CodeAnalysis; 3 | global using System.Reflection; 4 | global using System.Net.Mime; 5 | global using System.Security.Authentication; 6 | global using System.Security.Cryptography.X509Certificates; 7 | global using System.Text; 8 | 9 | global using Microsoft.AspNetCore.Authentication.JwtBearer; 10 | global using Microsoft.AspNetCore.Builder; 11 | global using Microsoft.AspNetCore.Hosting; 12 | global using Microsoft.AspNetCore.ResponseCompression; 13 | global using Microsoft.Extensions.Configuration; 14 | global using Microsoft.Extensions.DependencyInjection; 15 | global using Microsoft.Extensions.Hosting; 16 | global using Microsoft.IdentityModel.Tokens; 17 | 18 | global using MQTTnet; 19 | global using MQTTnet.Protocol; 20 | global using MQTTnet.Server; 21 | 22 | global using NetCoreMQTTExampleCluster.Models.Configuration; 23 | global using NetCoreMQTTExampleCluster.Models.Constants; 24 | global using NetCoreMQTTExampleCluster.Models.Service; 25 | global using NetCoreMQTTExampleCluster.Grains.Interfaces; 26 | 27 | global using Orleans; 28 | global using Orleans.Configuration; 29 | global using Orleans.Hosting; 30 | 31 | global using Serilog; 32 | global using Serilog.Events; 33 | global using Serilog.Exceptions; 34 | 35 | global using ILogger = Serilog.ILogger; 36 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 37 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Cluster/NetCoreMQTTExampleCluster.Cluster.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | latest 8 | enable 9 | NU1803 10 | true 11 | all 12 | true 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Always 44 | 45 | 46 | Always 47 | 48 | 49 | Always 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Cluster/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Security.Claims 3 | 4 | @using AutoMapper 5 | 6 | @using BlazorPro.BlazorSize 7 | 8 | @using IVU.Portal.Clients 9 | @using IVU.Portal.Clients.Shared 10 | @using IVU.Portal.Clients.Services 11 | @using IVU.Portal.Data.Configuration 12 | @using IVU.Portal.Data.Enumerations 13 | 14 | @using MatBlazor 15 | 16 | @using Microsoft.AspNetCore.Authorization 17 | @using Microsoft.AspNetCore.Components.Forms 18 | @using Microsoft.AspNetCore.Components.Routing 19 | @using Microsoft.AspNetCore.Components.Web 20 | @using Microsoft.AspNetCore.Components.Web.Virtualization 21 | @using Microsoft.AspNetCore.Components.Authorization 22 | @using Microsoft.JSInterop 23 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Cluster/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "NetCoreMQTTExampleCluster.Cluster": { 3 | "LogFolderPath": "C:\\log\\NetCoreMQTTExampleCluster.Cluster", 4 | "HeartbeatIntervalInMilliseconds": 30000, 5 | "Port": 8883, 6 | "UnencryptedPort": 1883, 7 | "BrokerConnectionSettings": { 8 | "ClientId": "mqtt-broker-sync-1", 9 | "HostName": "localhost", 10 | "Port": 8883, 11 | "Password": "Test", 12 | "UserName": "mqtt-broker-sync", 13 | "UseTls": true, 14 | "UseCleanSession": true 15 | }, 16 | "DatabaseSettings": { 17 | "Host": "localhost", 18 | "Database": "mqtt", 19 | "Username": "postgres", 20 | "Password": "postgres", 21 | "Port": 5432 22 | }, 23 | "OrleansConfiguration": { 24 | "ClusterOptions": { 25 | "ClusterId": "mqtt001", 26 | "ServiceId": "mqtt-silo-001-A" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Cluster/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "NetCoreMQTTExampleCluster.Cluster": { 3 | "LogFolderPath": "C:\\log\\NetCoreMQTTExampleCluster.Cluster", 4 | "HeartbeatIntervalInMilliseconds": 30000, 5 | "Port": 8883, 6 | "UnencryptedPort": 1883, 7 | "BrokerConnectionSettings": { 8 | "ClientId": "mqtt-broker-sync-1", 9 | "HostName": "localhost", 10 | "Port": 8883, 11 | "Password": "Test", 12 | "UserName": "mqtt-broker-sync", 13 | "UseTls": true, 14 | "UseCleanSession": true 15 | }, 16 | "DatabaseSettings": { 17 | "Host": "localhost", 18 | "Database": "mqtt", 19 | "Username": "postgres", 20 | "Password": "postgres", 21 | "Port": 5432 22 | }, 23 | "OrleansConfiguration": { 24 | "ClusterOptions": { 25 | "ClusterId": "mqtt001", 26 | "ServiceId": "mqtt-silo-001-A" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Cluster/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFrDCCA5SgAwIBAgIJAI9/2A3p/62UMA0GCSqGSIb3DQEBCwUAMGsxCzAJBgNV 3 | BAYTAkRFMQswCQYDVQQIDAJCWTENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwEVGVz 4 | dDENMAsGA1UECwwEVGVzdDENMAsGA1UEAwwEVGVzdDETMBEGCSqGSIb3DQEJARYE 5 | VGVzdDAeFw0xOTA2MjEwODM5NTFaFw0yMDA2MjAwODM5NTFaMGsxCzAJBgNVBAYT 6 | AkRFMQswCQYDVQQIDAJCWTENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDEN 7 | MAsGA1UECwwEVGVzdDENMAsGA1UEAwwEVGVzdDETMBEGCSqGSIb3DQEJARYEVGVz 8 | dDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAN+LKS1zea3NvbfhICLa 9 | zC045bYy25KLr/+0SgKHeb0A6rbdecCoClWOJzuPEN6T35XZ8kPcyPP7coK0HjVY 10 | cHO/uvfzKv9ANNWWAak+neMer6YfxM7qruXMcyZM2QPdOtIAoCNeOkFuzMFFET8R 11 | VZXaTCot7G8hCjvU0m+iTgYCFZp5xmRb1cva69DMc/4IADxuEZeIVNOvjglg7rfI 12 | shrWj/9n6oUzULsJK1Yof2D3xADnYBi3n+rZ2brEtwVttst9vUrzp0Am/BJrAjfr 13 | XIQDcwg0g2ncfFV/g/Qd6QmQq1risaRZUPC+RmguLTvnm0P6Z+auDvtYX5MQxf5P 14 | IteAzt9S80JHNSKeTShAUO8TMpIulDDZklp26pz5uxptuOpeegbo+UJ7hv9Pb/am 15 | r4yR/GY6Br0kqk7NzJaBINAEjpTkLvdfMnehrsoeyKqRayf/ycMMQof/pahBiOCG 16 | IK8OjssbkmkZFjYlRUL3v7PlrJDjkRdldtvnvFarAu3Q6Sva7KkoVDpGf+C1ulPp 17 | Vygq2I+cSodyNd+WL3WcfDPqp9gR6UKjJzSBW9otYoywEvSSZJpGx2aM1iIJ+JQe 18 | OQovenAW1UqptfapDF6DLDkNeDwmW5nl08rK+tKKC0sP7uRFGvwWhVXod7BFVPmu 19 | ka8X1jPTtvISx7TiWAR0IThhAgMBAAGjUzBRMB0GA1UdDgQWBBRek+JDGKT0H8Ll 20 | RU+AmLu3D1dkZTAfBgNVHSMEGDAWgBRek+JDGKT0H8LlRU+AmLu3D1dkZTAPBgNV 21 | HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAAgfwRUfBcembHX2HE5ksu 22 | ChqRxhJC3AR99etyYtN9+7xvfTP6WcsCOlrtQIC4EGCnpXVVZMFeeK0t+tNi0Bn5 23 | 0ZzLd+WFK61KznTspVR8yh54ymS3X2ZNZWde4N6EcecOVD4xa2TUs//yBR6PdHm2 24 | CIgkSuPgza45nhiU+JhNXIRV3vNmP2cnwE2BGzp1DD/dCX+PmnCFddaDwDNGLA9E 25 | Ev6QgQlj1W/flQ1b6gzodbHoxtlSV3D7YLadPrLUCVF2ykBTCPrK2L9ckAMl3IxI 26 | +MH+maeEBEfc81uNRtpHw40sXNVpoDH9uQ/IU4qBGRG5ntl1J0JZFSYnOcsgdnW4 27 | qcMgvGD/nwLrAUFmYJgbU6bURq4kad9dSqIC5x8/AaM7lFdQvdfAjcqwczT1DaKm 28 | cHNi5BiKrSYhDEoDk/cBxLhMuko3CzoCcN9Qz5QaEx9Q+S8PLLeniYXxifGYVCyh 29 | ZjYdBm0uEdmFxKm3PR3r15E/G7ywjq3Ep5lxklFXh/bBePacDgSI7h8pZSxQd5kD 30 | j60SteYfdKTRLM+JBVb3dBQ/VuN8lGgMKXo7zhB5kmkB4lFYU22mO27mz0zAX6oK 31 | H2CUTRAxl0O2MonCuoO8BbvoBmwbdiiKa12sNDNog1QPlz05fXJfZBwsuCTLYW08 32 | QweOV+Pd5QnJV4sm2Sbsmg== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Cluster/certificate.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeppPenner/NetCoreMQTTExampleCluster/76803bdb465c693c9b71a5ac1ea726f5ce466de6/src/NetCoreMQTTExampleCluster.Cluster/certificate.pfx -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Cluster/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIciXnzFAYgDQCAggA 3 | MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECMunROPC+JchBIIJSMs7H7nbzAo7 4 | 6+MkF+/C8EYF/MaeqMXU5cxsBqMT6KFQvRWd5o6saBhpG09+kIbUeohvKtRzRPAF 5 | ZQoHt4ABkXkNqhDdQkMF/frq0NmnHFH7ByusJnRXbDXW+eV+Sfhyg2dw71DnHzUA 6 | 4bThn5gKAMYaeJ9T5jTR6pNIzgmn4ffb9TlgMC2PkE1nCnIUVoaBHwJN3X2UiNzY 7 | 6je22qGpI6eZlI18Td6AVPhhTPjXT/0rXUaCBxbHEN46WKaWKW3gfmW1wGXP7dGV 8 | r3QBYJHmI4CXm3mSX87kLysZBiv6kmrBVUKo/rbYAxekRm7YwJeIkpMsxas4PCjX 9 | 43aSUEa8fcXxwFYa6B0OMZDNdTpzWNMSzWJOd2xUPqAx9pue5DYeuC++ivyZOMpB 10 | tOZEnHgE/pgrw7NIT6nXDrZFbJwpe/vkPWG5SnOeF5YfQ5zAT0/a43ce6aoQ3Rab 11 | EhJt+mIxVa/m8nb7ZXkV7C8Ng59RlwIsL3p559EUYOxWJMQOUe58pPWSZAYbvLqg 12 | Lr7hydzMxQUGqUlHtP5kELFJD0glXho4k4lMrtV1R6nNeay0cY3cE/nQd1xi25ld 13 | h+PGcrLC/kCHqAi0P+G5YxphxQINyjyKShmkjnKNLdJUp487cRf/nCGtiQzdWzGw 14 | ysECKF4neW4J3vban+XECZES3RAZY5VXjy3OdjZXGXj3KjnWHlm4pebbaPA0+gMZ 15 | cRrxkcJGGDD3RxCnrOphJZxoBIdmTPwRvVcg1H1ff2QJIcWoKfPp33+NSs85KdkC 16 | idx5I6N+HMS40dsWr9p3FZy/0lOazJB58faRTH8g1w9aNAbk+dN/Rk8iMEi1qQR6 17 | CJHpUghTKHbAf//gCkFEPmQxcc6UY/V/Miaw9U1NFY+sd2LU6ZFyailuNbPCnjrb 18 | r1AEKYEl42rlqeqNfONcQYE/l0K6OxirK67EiIVxhaQe7Z38XtyCMsP/7CCtFl18 19 | NIo+n1YqlxcSWIPr/lH+5bYkrIsHQyMqReYnsBPfByDslDR3iz10uj/S31T+NJ6h 20 | MNFPocoo1XhjnS3RJwYDjwR6QYt5oV6Ibpro04A1cwYKXqR+UmMhY5fWitR3SP96 21 | Go0Pbf9TJUXQAsMQyeAUubG4U+fxKvmKPT5MLqSiESxWw6+TaWGwJ2CY605A5HRD 22 | Le1nJP03TjxERmqB59x4T329GWXsbzARTuYmdU6UdV20QH1CwyVfeMGqyt5jJDuE 23 | oysagkDIrNab9wDbVaxWOZnKEx+sVKiAdDLNpvPqqrLHmtLfAjsXr6vf2FaAnC6B 24 | +2UoirSz18D1u+Hbnczxtq6Z6YsuGmAmD/IuvrE49VUmilpCUxefBgJ2EPCCSqCF 25 | CJS2mw6TffjHlWGzjKnYKsiN4OM86DUzlPGFIIjj2u7gmMUHu7Yh+iphYO1dP1ml 26 | hnXqni6lfc//SZ6qgDmbvS7weWryYTwk01FuBJ66Fbepfed2E2TTnWbWZZOqdH5B 27 | lAiUBMGCTbvsgkmy/Nfi86WagwYN3MCPXB2drg9R1JeTh4lb3jVDCYiWRJpyg92S 28 | Y+FdnlIBgUocDZlNs/yV2tzQjyVpvMgqP/XnjI6A+BBKGtVuIAbQTnL/zGp6hmvi 29 | bL0XodYiSbtUA7jbssmbTQxWHc+DD8tPic5JjawKgruySMTPfs6azsink1hG4vl9 30 | XSjQCfFWYUL3VmOajmPx2KHSdPQ/tYlmcC2oBD8JZxYA0W1Pr6zTnnFP7mrwIYtH 31 | 2N5EO1J4PRyLSqamls9jNNoqWWbPu5gnzZ/iwlIVtm7fGATXwoBz1z1IcsKcbABe 32 | MZKuWxRJumd/SgmxBxPdUrDv3JetyykDxIQZDxjaqYw5TcH/8ULZirMKezMUNCkI 33 | 4oOJmp1FJb+bq2rlAlSTI5DPmcO4XncNueYvpvcosuVK6OouhAjfuyBoze2CK4PP 34 | PCe6g8xCSyNC7uBJJ7NjARWETVQH7vRvboo/ocuiSPiNBd8GgJmJ4Nu2P4LgRLzu 35 | RNvFWo8XM34NJnBu0+8MTFCJ6/62o0V6Y6UZJRleXRcBPR8TcKPfUgY5cXWQHU+k 36 | hlNWTgwaYPrW8wKuuEeBqCLeM6wxLz7xgOf8/DYLvAuXujJVSpCwYE03L/EiVeND 37 | vfBkyVoYAJQ0sKSa2GcnIp8HH+CEuZqBbjXOINVj+06DKpCc7p2gJ70Y5zj8qXrz 38 | z3OwhsmwzUTGVj9buG0EINjJQ/XBH/W/QAbUEWbAyQ4orhWvSfMXqrR15s7QFsQ+ 39 | PvkxPKK+QfyTJXobts5sab90dpXXjq64LIKc53mJg9XRNXJ/Nff7eLmReRaa2+tV 40 | A92fGF4vuGFfwJrNhBAqCh6YxxsJiorj5KvBXya3S9UMybcNx2uNWUDMLgCXTwR8 41 | k5ed2tucYT/DOVrqmV4pVAEvjSFTrCgI1TAp/qYcToDT0H6mM1yT1VOnSO64Pq0S 42 | Z/uiB4a8OZZdkxF120WLpM5bm3HLMZU04J0huC5kNHg2NMzgStpso1DveIA909my 43 | XJxHXP7rWGWH1BKtRaWbysE5Trm9BJd4kKK/Sj3K0cp2bjYR/cRRpzhG7Fg3M1dL 44 | sSH3dX3Jn1tS1ZTFz9ZnOuRyldcrI1USmKCDehaUZGf6fdOi6IiY+Q+TNo8Fl51W 45 | aU4+u4Xp6+SyZKuzpg0JNTBLXA+RJMoyCjIUivR3M+AxbgJDxvZjFFvAw3i0viJz 46 | rVt5I997idfOXzn/QpwuOh9GywjQj3ydBiU8hUHbuAU7t6JBbPc0vXQsYq90SvRN 47 | AwonBDyaocRAVktalmt7QZaT7UIbT+YqWF9zzsdjq7eMNoL1aZlYMuWnYZvcYwRp 48 | qHk9zEw8q2L0l0V3wAgk6Wawvi93pO/Zxhe6GQe5OCjxgHotzlsqzaw9rgdObpYv 49 | q63/aTuwTD8rYHKuEgbkYfMkt8BXyPl/C5I7d2yCS7p+QJQVtdjrc7hdU4tCCkUq 50 | WN38Jo3x28AQy7Ui8goPeefA1yHD9vBhCUG7NVEuUUukd5UY8A+7JJYhKxT8Lrnc 51 | 5CSaymdNb7jX8o+OHNWkm/IIOgkIvPo2NL4Fu68li1033upcSvCUaA2+nYztarqw 52 | Lq9ACG/Xp+k1XcaqkjizHM+vXQdndzqnqrhWeHTEFi9dAmaqj8sQPBs5p0wKQGYc 53 | WyS+q9DqJqcHFQcHQIHelg== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.DatabaseSetup/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System.Diagnostics.CodeAnalysis; 3 | global using System.Reflection; 4 | 5 | global using Microsoft.AspNetCore.Identity; 6 | 7 | global using NetCoreMQTTExampleCluster.Models; 8 | global using NetCoreMQTTExampleCluster.Storage; 9 | global using NetCoreMQTTExampleCluster.Storage.Data; 10 | global using NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 11 | global using NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 12 | 13 | global using Newtonsoft.Json; 14 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 15 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.DatabaseSetup/NetCoreMQTTExampleCluster.DatabaseSetup.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | latest 8 | enable 9 | NU1803 10 | true 11 | all 12 | true 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Always 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.DatabaseSetup/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Host": "localhost", 3 | "Database": "mqtt", 4 | "Username": "postgres", 5 | "Password": "postgres", 6 | "Port": 5432, 7 | "Pooling" : false 8 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains.Interfaces/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System.Collections; 3 | 4 | global using MQTTnet; 5 | global using MQTTnet.Formatter; 6 | global using MQTTnet.Packets; 7 | global using MQTTnet.Protocol; 8 | global using MQTTnet.Server; 9 | 10 | global using NetCoreMQTTExampleCluster.Models.Interfaces; 11 | 12 | global using Orleans; 13 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 14 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains.Interfaces/IMqttClientGrain.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The grain interface for one client identifier. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Grains.Interfaces; 11 | 12 | /// 13 | /// The grain interface for one client identifier. 14 | /// 15 | public interface IMqttClientGrain : IGrainWithStringKey 16 | { 17 | /// 18 | /// Proceeds the subscription for one client identifier. 19 | /// 20 | /// The context. 21 | /// A value indicating whether the subscription is accepted or not. 22 | Task ProceedSubscription(SimpleInterceptingSubscriptionEventArgs context); 23 | 24 | /// 25 | /// Proceeds the published message for one client identifier. 26 | /// 27 | /// The context. 28 | /// A value indicating whether the published message is accepted or not. 29 | Task ProceedPublish(SimpleInterceptingPublishEventArgs context); 30 | 31 | /// 32 | /// Proceeds the connection for one client identifier. 33 | /// 34 | /// The context. 35 | /// A value indicating whether the connection is accepted or not. 36 | Task ProceedConnect(SimpleValidatingConnectionEventArgs context); 37 | 38 | /// 39 | /// Checks whether the user is a user used for synchronization. 40 | /// 41 | /// A value indicating whether the user is a broker user or not. 42 | Task IsUserBrokerUser(); 43 | 44 | /// 45 | /// Tells the grain to refresh its cache. 46 | /// 47 | /// Forces a cache update. 48 | /// A representing any asynchronous operation. 49 | Task RefreshCache(bool force); 50 | } 51 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains.Interfaces/IMqttRepositoryGrain.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The grain interface for a repository to manage the brokers. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Grains.Interfaces; 11 | 12 | /// 13 | /// The grain interface for a repository to manage the brokers. 14 | /// 15 | public interface IMqttRepositoryGrain : IGrainWithIntegerKey 16 | { 17 | /// 18 | /// Connects a broker to the grain. 19 | /// 20 | /// The broker connection settings. 21 | /// The broker identifier. 22 | /// A representing any asynchronous operation. 23 | Task ConnectBroker(IBrokerConnectionSettings brokerConnectionSettings, Guid brokerId); 24 | 25 | /// 26 | /// Disconnects the broker from the grain. 27 | /// 28 | /// The broker identifier. 29 | /// A representing any asynchronous operation. 30 | Task DisconnectBroker(Guid brokerId); 31 | 32 | /// 33 | /// Proceeds the subscription. 34 | /// 35 | /// The event args. 36 | /// A value indicating whether the subscription is accepted or not. 37 | Task ProceedSubscription(SimpleInterceptingSubscriptionEventArgs eventArgs); 38 | 39 | /// 40 | /// Proceeds the published message. 41 | /// 42 | /// The event args. 43 | /// The broker identifier. 44 | /// A value indicating whether the published message is accepted or not. 45 | Task ProceedPublish(SimpleInterceptingPublishEventArgs eventArgs, Guid brokerId); 46 | 47 | /// 48 | /// Proceeds the unsubscription for one client identifier. 49 | /// 50 | /// The event args. 51 | /// A returning any asynchronous operation. 52 | Task ProceedUnsubscription(SimpleClientUnsubscribedTopicEventArgs eventArgs); 53 | 54 | /// 55 | /// Proceeds the connection for one client identifier. 56 | /// 57 | /// The event args. 58 | /// A value indicating whether the connection is accepted or not. 59 | Task ProceedConnect(SimpleValidatingConnectionEventArgs eventArgs); 60 | 61 | /// 62 | /// Proceeds the disconnection for one client identifier. 63 | /// 64 | /// The event args. 65 | /// A returning any asynchronous operation. 66 | Task ProceedDisconnect(SimpleClientDisconnectedEventArgs eventArgs); 67 | } 68 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains.Interfaces/NetCoreMQTTExampleCluster.Grains.Interfaces.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | latest 7 | enable 8 | NU1803 9 | true 10 | all 11 | true 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains.Interfaces/SimpleClientDisconnectedEventArgs.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains a simplified version of the . 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Grains.Interfaces; 11 | 12 | /// 13 | /// A class that contains a simplified version of the . 14 | /// 15 | public sealed record class SimpleClientDisconnectedEventArgs 16 | { 17 | /// 18 | /// Initializes a new instance of the class. This is used for testing purposes only! 19 | /// 20 | public SimpleClientDisconnectedEventArgs() 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The event args. 28 | public SimpleClientDisconnectedEventArgs(ClientDisconnectedEventArgs eventArgs) 29 | { 30 | this.ClientId = eventArgs.ClientId; 31 | this.UserName = eventArgs.UserName; 32 | this.Password = eventArgs.Password; 33 | this.DisconnectType = eventArgs.DisconnectType; 34 | this.Endpoint = eventArgs.RemoteEndPoint.ToString() ?? string.Empty; 35 | this.ReasonCode = eventArgs.ReasonCode; 36 | this.ReasonString = eventArgs.ReasonString; 37 | this.SessionExpiryInterval = eventArgs.SessionExpiryInterval; 38 | this.SessionItems = eventArgs.SessionItems; 39 | this.UserProperties = eventArgs.UserProperties; 40 | } 41 | 42 | /// 43 | /// Gets or sets the client identifier. 44 | /// 45 | public string ClientId { get; init; } = string.Empty; 46 | 47 | /// 48 | /// Gets or sets the user name. 49 | /// 50 | public string UserName { get; init; } = string.Empty; 51 | 52 | /// 53 | /// Gets or sets the password. 54 | /// 55 | public string Password { get; init; } = string.Empty; 56 | 57 | /// 58 | /// Gets or sets the disconnect type. 59 | /// 60 | public MqttClientDisconnectType DisconnectType { get; init; } 61 | 62 | /// 63 | /// Gets or sets the endpoint. 64 | /// 65 | public string Endpoint { get; init; } = string.Empty; 66 | 67 | /// 68 | /// Gets or sets the reason code. 69 | /// 70 | public MqttDisconnectReasonCode? ReasonCode { get; init; } 71 | 72 | /// 73 | /// Gets or sets the reason string. 74 | /// 75 | public string ReasonString { get; init; } = string.Empty; 76 | 77 | /// 78 | /// Gets or sets the session expiry interval. 79 | /// 80 | public uint SessionExpiryInterval { get; init; } 81 | 82 | /// 83 | /// Gets or sets the session items. 84 | /// 85 | public IDictionary SessionItems { get; init; } = new Dictionary(); 86 | 87 | /// 88 | /// Gets or sets the user properties. 89 | /// 90 | public List UserProperties { get; init; } = []; 91 | } 92 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains.Interfaces/SimpleClientUnsubscribedTopicEventArgs.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains a simplified version of the . 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Grains.Interfaces; 11 | 12 | /// 13 | /// A class that contains a simplified version of the . 14 | /// 15 | public sealed record class SimpleClientUnsubscribedTopicEventArgs 16 | { 17 | /// 18 | /// Initializes a new instance of the class. This is used for testing purposes only! 19 | /// 20 | public SimpleClientUnsubscribedTopicEventArgs() 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The event args. 28 | public SimpleClientUnsubscribedTopicEventArgs(ClientUnsubscribedTopicEventArgs eventArgs) 29 | { 30 | this.ClientId = eventArgs.ClientId; 31 | this.SessionItems = eventArgs.SessionItems; 32 | this.UserName = eventArgs.UserName; 33 | this.TopicFilter = eventArgs.TopicFilter; 34 | } 35 | 36 | /// 37 | /// Gets or sets the client identifier. 38 | /// 39 | public string ClientId { get; init; } = string.Empty; 40 | 41 | /// 42 | /// Gets or sets the session items. 43 | /// 44 | public IDictionary SessionItems { get; init; } = new Dictionary(); 45 | 46 | /// 47 | /// Gets or sets the user name. 48 | /// 49 | public string UserName { get; init; } = string.Empty; 50 | 51 | /// 52 | /// Gets or sets the topic filter. 53 | /// 54 | public string TopicFilter { get; init; } = string.Empty; 55 | } 56 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains.Interfaces/SimpleInterceptingPublishEventArgs.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains a simplified version of the . 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Grains.Interfaces; 11 | 12 | /// 13 | /// A class that contains a simplified version of the . 14 | /// 15 | public sealed record class SimpleInterceptingPublishEventArgs 16 | { 17 | /// 18 | /// Initializes a new instance of the class. This is used for testing purposes only! 19 | /// 20 | public SimpleInterceptingPublishEventArgs() 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The context. 28 | public SimpleInterceptingPublishEventArgs(InterceptingPublishEventArgs context) 29 | { 30 | this.ApplicationMessage = context.ApplicationMessage; 31 | this.ClientId = context.ClientId; 32 | this.UserName = context.UserName; 33 | this.CloseConnection = context.CloseConnection; 34 | this.ProcessPublish = context.ProcessPublish; 35 | this.Response = context.Response; 36 | this.SessionItems = context.SessionItems; 37 | } 38 | 39 | /// 40 | /// Gets or sets the application message. 41 | /// 42 | public MqttApplicationMessage ApplicationMessage { get; init; } = new(); 43 | 44 | /// 45 | /// Gets or sets the client identifier. 46 | /// 47 | public string ClientId { get; init; } = string.Empty; 48 | 49 | /// 50 | /// Gets or sets the user name. 51 | /// 52 | public string UserName { get; init; } = string.Empty; 53 | 54 | /// 55 | /// Gets or sets a value indicating whether the connection will be closed or not. 56 | /// 57 | public bool CloseConnection { get; init; } 58 | 59 | /// 60 | /// Gets or sets a value indicating whether the publish command is accepted or not. 61 | /// 62 | public bool ProcessPublish { get; init; } 63 | 64 | /// 65 | /// Gets or sets the response. 66 | /// 67 | public PublishResponse Response { get; } = new(); 68 | 69 | /// 70 | /// Gets or sets the session items. 71 | /// 72 | public IDictionary SessionItems { get; init; } = new Dictionary(); 73 | } 74 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains.Interfaces/SimpleInterceptingSubscriptionEventArgs.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains a simplified version of the . 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Grains.Interfaces; 11 | 12 | /// 13 | /// A class that contains a simplified version of the . 14 | /// 15 | public sealed record class SimpleInterceptingSubscriptionEventArgs 16 | { 17 | /// 18 | /// Initializes a new instance of the class. This is used for testing purposes only! 19 | /// 20 | public SimpleInterceptingSubscriptionEventArgs() 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The event args. 28 | public SimpleInterceptingSubscriptionEventArgs(InterceptingSubscriptionEventArgs eventArgs) 29 | { 30 | this.ClientId = eventArgs.ClientId; 31 | this.UserName = eventArgs.UserName; 32 | this.CloseConnection = eventArgs.CloseConnection; 33 | this.ProcessSubscription = eventArgs.ProcessSubscription; 34 | this.ReasonString = eventArgs.ReasonString; 35 | this.Response = eventArgs.Response; 36 | this.SessionItems = eventArgs.SessionItems; 37 | this.TopicFilter = eventArgs.TopicFilter; 38 | this.UserProperties = eventArgs.UserProperties; 39 | } 40 | 41 | /// 42 | /// Gets or sets the client identifier. 43 | /// 44 | public string ClientId { get; init; } = string.Empty; 45 | 46 | /// 47 | /// Gets or sets the user name. 48 | /// 49 | public string UserName { get; init; } = string.Empty; 50 | 51 | /// 52 | /// Gets or sets a value indicating whether the connection will be closed or not. 53 | /// 54 | public bool CloseConnection { get; init; } 55 | 56 | /// 57 | /// Gets or sets a value indicating whether the subscribe command is accepted or not. 58 | /// 59 | public bool ProcessSubscription { get; init; } 60 | 61 | /// 62 | /// Gets or sets the reason string. 63 | /// 64 | public string ReasonString { get; init; } = string.Empty; 65 | 66 | /// 67 | /// Gets or sets the response. 68 | /// 69 | public SubscribeResponse Response { get; init; } = new(); 70 | 71 | /// 72 | /// Gets or sets the session items. 73 | /// 74 | public IDictionary SessionItems { get; init; } = new Dictionary(); 75 | 76 | /// 77 | /// Gets or sets the topic filter. 78 | /// 79 | public MqttTopicFilter TopicFilter { get; init; } = new(); 80 | 81 | /// 82 | /// Gets or sets the user properties. 83 | /// 84 | public List UserProperties { get; init; } = []; 85 | } 86 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System; 3 | global using System.Collections.Concurrent; 4 | global using System.Collections.Generic; 5 | global using System.Linq; 6 | global using System.Text; 7 | global using System.Threading; 8 | global using System.Threading.Tasks; 9 | 10 | global using Microsoft.AspNetCore.Identity; 11 | global using Microsoft.Extensions.Caching.Memory; 12 | 13 | global using MQTTnet; 14 | 15 | global using NetCoreMQTTExampleCluster.Grains.Interfaces; 16 | global using NetCoreMQTTExampleCluster.Models.Interfaces; 17 | global using NetCoreMQTTExampleCluster.Storage.Data; 18 | global using NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 19 | global using NetCoreMQTTExampleCluster.Validation; 20 | 21 | global using Orleans; 22 | global using Orleans.Concurrency; 23 | 24 | global using Serilog; 25 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 26 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Grains/NetCoreMQTTExampleCluster.Grains.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | latest 7 | enable 8 | NU1803 9 | true 10 | all 11 | true 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Configuration/BrokerConnectionSettings.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains the connection settings for the grains to synchronize the data to other brokers. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Configuration; 11 | 12 | /// 13 | /// 14 | public class BrokerConnectionSettings : IBrokerConnectionSettings, IConfigurationValid 15 | { 16 | /// 17 | public string ClientId { get; set; } = string.Empty; 18 | 19 | /// 20 | public string HostName { get; set; } = string.Empty; 21 | 22 | /// 23 | public int Port { get; set; } 24 | 25 | /// 26 | public string UserName { get; set; } = string.Empty; 27 | 28 | /// 29 | public string Password { get; set; } = string.Empty; 30 | 31 | /// 32 | public bool UseTls { get; set; } 33 | 34 | /// 35 | public bool UseCleanSession { get; set; } 36 | 37 | /// 38 | public bool IsValid() 39 | { 40 | if (string.IsNullOrWhiteSpace(this.ClientId)) 41 | { 42 | throw new ConfigurationException("The client identifier is empty."); 43 | } 44 | 45 | if (string.IsNullOrWhiteSpace(this.HostName)) 46 | { 47 | throw new ConfigurationException("The host name is empty."); 48 | } 49 | 50 | if (!this.Port.IsPortValid()) 51 | { 52 | throw new ConfigurationException("The port is invalid."); 53 | } 54 | 55 | if (string.IsNullOrWhiteSpace(this.UserName)) 56 | { 57 | throw new ConfigurationException("The user name is empty."); 58 | } 59 | 60 | if (string.IsNullOrWhiteSpace(this.Password)) 61 | { 62 | throw new ConfigurationException("The password is empty."); 63 | } 64 | 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Configuration/OrleansConfiguration.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains the read from the JSON settings file. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Configuration; 11 | 12 | /// 13 | /// 14 | /// A class that contains the read from the JSON settings file. 15 | /// 16 | public class OrleansConfiguration : IConfigurationValid 17 | { 18 | /// 19 | /// Gets or sets the cluster options. 20 | /// 21 | public ClusterOptions? ClusterOptions { get; set; } 22 | 23 | /// 24 | public virtual bool IsValid() 25 | { 26 | if (this.ClusterOptions is null) 27 | { 28 | throw new ConfigurationException("The cluster options are empty."); 29 | } 30 | 31 | if (string.IsNullOrWhiteSpace(this.ClusterOptions.ClusterId)) 32 | { 33 | throw new ConfigurationException("The cluster identifier is empty."); 34 | } 35 | 36 | if (string.IsNullOrWhiteSpace(this.ClusterOptions.ServiceId)) 37 | { 38 | throw new ConfigurationException("The service identifier is empty."); 39 | } 40 | 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Configuration/OrleansSiloConfiguration.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains the read from the JSON settings file. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Configuration; 11 | 12 | /// 13 | /// 14 | /// A class that contains the read from the JSON settings file. 15 | /// 16 | public class OrleansSiloConfiguration : OrleansConfiguration 17 | { 18 | /// 19 | /// Gets or sets the dashboard options. 20 | /// 21 | public DashboardOptions? DashboardOptions { get; set; } 22 | 23 | /// 24 | /// Gets or sets the Orleans endpoint options. 25 | /// 26 | public SiloEndpointOptions? EndpointOptions { get; set; } 27 | 28 | /// 29 | public override bool IsValid() 30 | { 31 | if (this.ClusterOptions is null) 32 | { 33 | throw new ConfigurationException("The cluster options are empty."); 34 | } 35 | 36 | if (string.IsNullOrWhiteSpace(this.ClusterOptions.ClusterId)) 37 | { 38 | throw new ConfigurationException("The cluster identifier is empty."); 39 | } 40 | 41 | if (string.IsNullOrWhiteSpace(this.ClusterOptions.ServiceId)) 42 | { 43 | throw new ConfigurationException("The service identifier is empty."); 44 | } 45 | 46 | if (this.DashboardOptions is null) 47 | { 48 | throw new ConfigurationException("The dashboard options are empty."); 49 | } 50 | 51 | if (this.EndpointOptions is null || !this.EndpointOptions.IsValid()) 52 | { 53 | throw new ConfigurationException("The dashboard options are empty."); 54 | } 55 | 56 | return base.IsValid(); 57 | } 58 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Configuration/SiloHostConfiguration.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains the read from the JSON settings file. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Configuration; 11 | 12 | /// 13 | /// A class that contains the read from the JSON settings file. 14 | /// 15 | public class SiloHostConfiguration : IConfigurationValid 16 | { 17 | /// 18 | /// Gets or sets the log folder path. 19 | /// 20 | public string LogFolderPath { get; set; } = string.Empty; 21 | 22 | /// 23 | /// Gets or sets the heartbeat interval in milliseconds. 24 | /// 25 | public int HeartbeatIntervalInMilliSeconds { get; set; } 26 | 27 | /// 28 | /// Gets or sets the database settings. 29 | /// 30 | public MqttDatabaseConnectionSettings? DatabaseSettings { get; set; } 31 | 32 | /// 33 | /// Gets or sets the Orleans configuration. 34 | /// 35 | public OrleansSiloConfiguration? OrleansConfiguration { get; set; } 36 | 37 | /// 38 | public bool IsValid() 39 | { 40 | if (string.IsNullOrWhiteSpace(this.LogFolderPath)) 41 | { 42 | throw new ConfigurationException("The log folder path is empty."); 43 | } 44 | 45 | if (this.HeartbeatIntervalInMilliSeconds <= 0) 46 | { 47 | throw new ConfigurationException("The heartbeat interval is set to 0 or less."); 48 | } 49 | 50 | if (this.DatabaseSettings is null || !this.DatabaseSettings.IsValid()) 51 | { 52 | throw new ConfigurationException("The database settings are invalid."); 53 | } 54 | 55 | if (this.OrleansConfiguration is null || !this.OrleansConfiguration.IsValid()) 56 | { 57 | throw new ConfigurationException("The Orleans configuration is invalid."); 58 | } 59 | 60 | return true; 61 | } 62 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Constants/GlobalConstants.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class for globally defined constants. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Constants; 11 | 12 | /// 13 | /// A class for globally defined constants. 14 | /// 15 | public class GlobalConstants 16 | { 17 | /// 18 | /// The invariant. 19 | /// 20 | public const string Invariant = "Npgsql"; 21 | 22 | /// 23 | /// The simple message stream provider name. 24 | /// 25 | public const string SimpleMessageStreamProvider = "SMSProvider"; 26 | 27 | /// 28 | /// The pub-sub store. 29 | /// 30 | public const string PubSubStore = "PubSubStore"; 31 | 32 | /// 33 | /// The repository grain identifier. 34 | /// 35 | public const int RepositoryGrainId = 0; 36 | 37 | /// 38 | /// The gigabytes divider. (Used to convert from bytes to gigabytes). 39 | /// 40 | public const decimal GigaBytesDivider = 1024 * 1024 * 1024; 41 | 42 | /// 43 | /// The megabytes divider. (Used to convert from bytes to megabytes). 44 | /// 45 | public const decimal MegaBytesDivider = 1024 * 1024; 46 | 47 | /// 48 | /// The kilobytes divider. (Used to convert from bytes to kilobytes). 49 | /// 50 | public const decimal KiloBytesDivider = 1024; 51 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Exceptions/ConfigurationException.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An exception to handle configuration errors. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Exceptions; 11 | 12 | /// 13 | /// 14 | /// An exception to handle configuration errors. 15 | /// 16 | public class ConfigurationException : Exception 17 | { 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | public ConfigurationException() 22 | { 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// The message. 29 | public ConfigurationException(string message) : base(message) 30 | { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Extensions/DashboardOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains extension methods for the class. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Extensions; 11 | 12 | /// 13 | /// A class that contains extension methods for the class. 14 | /// 15 | public static class DashboardOptionsExtensions 16 | { 17 | /// 18 | /// Binds the read endpoint options to the orleans options. 19 | /// 20 | /// The Orleans options. 21 | /// The options from the configuration. 22 | public static void Bind(this DashboardOptions options, DashboardOptions readOptions) 23 | { 24 | options.Password = readOptions.Password; 25 | options.BasePath = readOptions.BasePath; 26 | options.CounterUpdateIntervalMs = readOptions.CounterUpdateIntervalMs; 27 | options.HideTrace = readOptions.HideTrace; 28 | options.Host = readOptions.Host; 29 | options.HostSelf = readOptions.HostSelf; 30 | options.Port = readOptions.Port; 31 | options.ScriptPath = readOptions.ScriptPath; 32 | options.Username = readOptions.Username; 33 | } 34 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Extensions/DateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains extension method for the data type. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Extensions; 11 | 12 | /// 13 | /// A class that contains extension method for the data type. 14 | /// 15 | public static class DateTimeExtensions 16 | { 17 | /// 18 | /// Gets the time zone offset of the local time zone. 19 | /// 20 | /// The date to get the time zone offset from. 21 | /// The time zone offset of the local time zone 22 | public static TimeSpan GetTimeZoneOffset(this DateTime date) 23 | { 24 | return TimeZoneInfo.Local.IsDaylightSavingTime(date) ? TimeSpan.FromHours(2) : TimeSpan.FromHours(1); 25 | } 26 | 27 | /// 28 | /// Checks for an expired date. 29 | /// 30 | /// The timestamp. 31 | /// The duration. 32 | /// True when expired otherwise false. 33 | public static bool IsExpired(this DateTime timestamp, TimeSpan duration) 34 | { 35 | return timestamp.Add(duration) < DateTimeOffset.Now; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Extensions/DateTimeOffsetExtensions.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains extension method for the data type. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Extensions; 11 | 12 | /// 13 | /// A class that contains extension method for the data type. 14 | /// 15 | public static class DateTimeOffsetExtensions 16 | { 17 | /// 18 | /// Gets the end of the month. 19 | /// 20 | /// The date to get the month end from. 21 | /// A new that represents the end of the month. 22 | public static DateTimeOffset EndOfMonth(this DateTimeOffset date) 23 | { 24 | var lastDayInMonth = DateTime.DaysInMonth(date.Year, date.Month); 25 | var newDate = new DateTime(date.Year, date.Month, lastDayInMonth, 23, 59, 59); 26 | var timeZoneOffset = newDate.GetTimeZoneOffset(); 27 | return new DateTimeOffset(newDate, timeZoneOffset); 28 | } 29 | 30 | /// 31 | /// Checks for an expired date. 32 | /// 33 | /// The timestamp. 34 | /// The duration. 35 | /// True when expired otherwise false. 36 | public static bool IsExpired(this DateTimeOffset timestamp, TimeSpan duration) 37 | { 38 | return timestamp.Add(duration) < DateTimeOffset.Now; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Extensions/IntegerExtensions.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains extension method for the data type. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Extensions; 11 | 12 | /// 13 | /// A class that contains extension method for the data type. 14 | /// 15 | public static class IntegerExtensions 16 | { 17 | /// 18 | /// Checks whether a port is valid. 19 | /// 20 | /// The value. 21 | /// A value indicating whether the port is valid or not. 22 | public static bool IsPortValid(this int value) 23 | { 24 | return value switch 25 | { 26 | <= 0 => false, 27 | > 65535 => false, 28 | _ => true 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System.Diagnostics; 3 | global using System.Diagnostics.CodeAnalysis; 4 | global using System.Net; 5 | 6 | global using Microsoft.Extensions.Hosting; 7 | 8 | global using NetCoreMQTTExampleCluster.Models.Constants; 9 | global using NetCoreMQTTExampleCluster.Models.Exceptions; 10 | global using NetCoreMQTTExampleCluster.Models.Extensions; 11 | global using NetCoreMQTTExampleCluster.Models.Helper; 12 | global using NetCoreMQTTExampleCluster.Models.Interfaces; 13 | global using NetCoreMQTTExampleCluster.Storage.Interfaces; 14 | 15 | global using Orleans; 16 | global using Orleans.Configuration; 17 | global using OrleansDashboard; 18 | 19 | global using Serilog; 20 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 21 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Helper/FileSizeHelper.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A helper class to handle file size information. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Helper; 11 | 12 | /// 13 | /// A helper class to handle file size information. 14 | /// 15 | public static class FileSizeHelper 16 | { 17 | /// 18 | /// Gets the formatted file size. 19 | /// 20 | /// The value. 21 | /// The alloed decimals. 22 | /// The formatted file size. 23 | public static string GetValueWithUnitByteSize(decimal value, int allowedDecimals = 2) 24 | { 25 | if (value > GlobalConstants.GigaBytesDivider) 26 | { 27 | return $"{Math.Round(value / GlobalConstants.GigaBytesDivider, allowedDecimals)} GB"; 28 | } 29 | 30 | if (value > GlobalConstants.MegaBytesDivider) 31 | { 32 | return $"{Math.Round(value / GlobalConstants.MegaBytesDivider, allowedDecimals)} MB"; 33 | } 34 | 35 | if (value > GlobalConstants.KiloBytesDivider) 36 | { 37 | return $"{Math.Round(value / GlobalConstants.KiloBytesDivider, allowedDecimals)} kB"; 38 | } 39 | 40 | return $"{value} bytes"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Interfaces/IBrokerConnectionSettings.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An interface that contains the connection settings for the grains to synchronize the data to other brokers. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Interfaces; 11 | 12 | /// 13 | /// An interface that contains the connection settings for the grains to synchronize the data to other brokers. 14 | /// 15 | public interface IBrokerConnectionSettings 16 | { 17 | /// 18 | /// Gets or sets the client identifier. 19 | /// 20 | string ClientId { get; set; } 21 | 22 | /// 23 | /// Gets or sets the host name. 24 | /// 25 | string HostName { get; set; } 26 | 27 | /// 28 | /// Gets or sets the port. 29 | /// 30 | int Port { get; set; } 31 | 32 | /// 33 | /// Gets or sets the user name. 34 | /// 35 | string UserName { get; set; } 36 | 37 | /// 38 | /// Gets or sets the password. 39 | /// 40 | string Password { get; set; } 41 | 42 | /// 43 | /// Gets or sets a value indicating whether TLS should be used or not. 44 | /// 45 | bool UseTls { get; set; } 46 | 47 | /// 48 | /// Gets or sets a value indicating whether a clean session should be used or not. 49 | /// 50 | bool UseCleanSession { get; set; } 51 | } 52 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/Interfaces/IConfigurationValid.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An interface to validate all configurations. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Models.Interfaces; 11 | 12 | /// 13 | /// An interface to validate all configurations. 14 | /// 15 | public interface IConfigurationValid 16 | { 17 | /// 18 | /// Checks whether the configuration is valid or not. 19 | /// 20 | /// A value indicating whether the configuration is valid or not. 21 | bool IsValid(); 22 | } 23 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Models/NetCoreMQTTExampleCluster.Models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | latest 7 | enable 8 | NU1803 9 | true 10 | all 11 | true 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.PasswordHashGenerator/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using NetCoreMQTTExampleCluster.Storage.Data; 3 | 4 | global using Microsoft.AspNetCore.Identity; 5 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 6 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.PasswordHashGenerator/NetCoreMQTTExampleCluster.PasswordHashGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | latest 8 | enable 9 | NU1803 10 | true 11 | all 12 | true 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.PasswordHashGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A program to hash passwords for new users. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.PasswordHashGenerator; 11 | 12 | /// 13 | /// A program to hash passwords for new users. 14 | /// 15 | public static class Program 16 | { 17 | /// 18 | /// The password hasher. 19 | /// 20 | private static readonly IPasswordHasher PasswordHasher = new PasswordHasher(); 21 | 22 | /// 23 | /// The main method of the program. 24 | /// 25 | public static void Main() 26 | { 27 | var hashAnother = new ConsoleKeyInfo('y', ConsoleKey.Y, false, false, false); 28 | 29 | while (hashAnother.Key == ConsoleKey.Y) 30 | { 31 | Console.WriteLine("Please insert a password to hash and press enter."); 32 | var password = Console.ReadLine(); 33 | var hashedPassword = HashPassword(password); 34 | Console.WriteLine($"Your hashed password is: {hashedPassword}"); 35 | Console.WriteLine("Do you want to hash another password? y/n"); 36 | hashAnother = Console.ReadKey(); 37 | 38 | // Wait for the enter key to be pressed and add a new line to not overwrite the user input. 39 | Console.ReadKey(); 40 | Console.WriteLine(); 41 | } 42 | 43 | Console.WriteLine("You can close the window now."); 44 | } 45 | 46 | /// 47 | /// Hashes the given password. 48 | /// 49 | /// The password to hash. 50 | /// A hash representation of the password. 51 | private static string HashPassword(string password) 52 | { 53 | var mqttUser = new MqttUser 54 | { 55 | Id = Guid.NewGuid(), 56 | UserName = string.Empty, 57 | ClientId = string.Empty 58 | }; 59 | 60 | return PasswordHasher.HashPassword(mqttUser, password); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System.Reflection; 3 | 4 | global using Microsoft.AspNetCore.Builder; 5 | global using Microsoft.AspNetCore.Hosting; 6 | global using Microsoft.Extensions.Configuration; 7 | global using Microsoft.Extensions.DependencyInjection; 8 | global using Microsoft.Extensions.Hosting; 9 | 10 | global using NetCoreMQTTExampleCluster.Grains; 11 | global using NetCoreMQTTExampleCluster.Models.Configuration; 12 | global using NetCoreMQTTExampleCluster.Models.Constants; 13 | global using NetCoreMQTTExampleCluster.Models.Extensions; 14 | global using NetCoreMQTTExampleCluster.Models.Service; 15 | global using NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 16 | global using NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 17 | global using NetCoreMQTTExampleCluster.Validation; 18 | 19 | global using Orleans; 20 | global using Orleans.Configuration; 21 | global using Orleans.Hosting; 22 | 23 | global using Serilog; 24 | global using Serilog.Events; 25 | global using Serilog.Exceptions; 26 | 27 | global using ILogger = Serilog.ILogger; 28 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 29 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/NetCoreMQTTExampleCluster.SiloHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | latest 8 | enable 9 | true 10 | true 11 | NU1803 12 | true 13 | all 14 | true 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Always 51 | 52 | 53 | Always 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/OrleansAdoNetContent/MySQL/MySQL-Main.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Implementation notes: 3 | 4 | 1) The general idea is that data is read and written through Orleans specific queries. 5 | Orleans operates on column names and types when reading and on parameter names and types when writing. 6 | 7 | 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. 8 | Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract 9 | is maintained. 10 | 11 | 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting 12 | by virtue of uniform naming across concrete implementations. 13 | 14 | 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation 15 | is not important as long as it represents a unique version. In this implementation we use integers for versioning 16 | 17 | 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value 18 | or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown 19 | the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. 20 | Orleans handles exception as a failure and will retry. 21 | 22 | 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: 23 | https://docs.microsoft.com/dotnet/orleans/implementation/cluster-management 24 | https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs 25 | */ 26 | -- This table defines Orleans operational queries. Orleans uses these to manage its operations, 27 | -- these are the only queries Orleans issues to the database. 28 | -- These can be redefined (e.g. to provide non-destructive updates) provided the stated interface principles hold. 29 | CREATE TABLE OrleansQuery 30 | ( 31 | QueryKey VARCHAR(64) NOT NULL, 32 | QueryText VARCHAR(8000) NOT NULL, 33 | 34 | CONSTRAINT OrleansQuery_Key PRIMARY KEY(QueryKey) 35 | ); 36 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/OrleansAdoNetContent/MySQL/MySQL-Reminders.sql: -------------------------------------------------------------------------------- 1 | -- Orleans Reminders table - https://docs.microsoft.com/dotnet/orleans/grains/timers-and-reminders 2 | CREATE TABLE OrleansRemindersTable 3 | ( 4 | ServiceId NVARCHAR(150) NOT NULL, 5 | GrainId VARCHAR(150) NOT NULL, 6 | ReminderName NVARCHAR(150) NOT NULL, 7 | StartTime DATETIME NOT NULL, 8 | Period BIGINT NOT NULL, 9 | GrainHash INT NOT NULL, 10 | Version INT NOT NULL, 11 | 12 | CONSTRAINT PK_RemindersTable_ServiceId_GrainId_ReminderName PRIMARY KEY(ServiceId, GrainId, ReminderName) 13 | ); 14 | 15 | INSERT INTO OrleansQuery(QueryKey, QueryText) 16 | VALUES 17 | ( 18 | 'UpsertReminderRowKey',' 19 | INSERT INTO OrleansRemindersTable 20 | ( 21 | ServiceId, 22 | GrainId, 23 | ReminderName, 24 | StartTime, 25 | Period, 26 | GrainHash, 27 | Version 28 | ) 29 | VALUES 30 | ( 31 | @ServiceId, 32 | @GrainId, 33 | @ReminderName, 34 | @StartTime, 35 | @Period, 36 | @GrainHash, 37 | last_insert_id(0) 38 | ) 39 | ON DUPLICATE KEY 40 | UPDATE 41 | StartTime = @StartTime, 42 | Period = @Period, 43 | GrainHash = @GrainHash, 44 | Version = last_insert_id(Version+1); 45 | 46 | 47 | SELECT last_insert_id() AS Version; 48 | '); 49 | 50 | INSERT INTO OrleansQuery(QueryKey, QueryText) 51 | VALUES 52 | ( 53 | 'ReadReminderRowsKey',' 54 | SELECT 55 | GrainId, 56 | ReminderName, 57 | StartTime, 58 | Period, 59 | Version 60 | FROM OrleansRemindersTable 61 | WHERE 62 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 63 | AND GrainId = @GrainId AND @GrainId IS NOT NULL; 64 | '); 65 | 66 | INSERT INTO OrleansQuery(QueryKey, QueryText) 67 | VALUES 68 | ( 69 | 'ReadReminderRowKey',' 70 | SELECT 71 | GrainId, 72 | ReminderName, 73 | StartTime, 74 | Period, 75 | Version 76 | FROM OrleansRemindersTable 77 | WHERE 78 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 79 | AND GrainId = @GrainId AND @GrainId IS NOT NULL 80 | AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL; 81 | '); 82 | 83 | INSERT INTO OrleansQuery(QueryKey, QueryText) 84 | VALUES 85 | ( 86 | 'ReadRangeRows1Key',' 87 | SELECT 88 | GrainId, 89 | ReminderName, 90 | StartTime, 91 | Period, 92 | Version 93 | FROM OrleansRemindersTable 94 | WHERE 95 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 96 | AND GrainHash > @BeginHash AND @BeginHash IS NOT NULL 97 | AND GrainHash <= @EndHash AND @EndHash IS NOT NULL; 98 | '); 99 | 100 | INSERT INTO OrleansQuery(QueryKey, QueryText) 101 | VALUES 102 | ( 103 | 'ReadRangeRows2Key',' 104 | SELECT 105 | GrainId, 106 | ReminderName, 107 | StartTime, 108 | Period, 109 | Version 110 | FROM OrleansRemindersTable 111 | WHERE 112 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 113 | AND ((GrainHash > @BeginHash AND @BeginHash IS NOT NULL) 114 | OR (GrainHash <= @EndHash AND @EndHash IS NOT NULL)); 115 | '); 116 | 117 | INSERT INTO OrleansQuery(QueryKey, QueryText) 118 | VALUES 119 | ( 120 | 'DeleteReminderRowKey',' 121 | DELETE FROM OrleansRemindersTable 122 | WHERE 123 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 124 | AND GrainId = @GrainId AND @GrainId IS NOT NULL 125 | AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL 126 | AND Version = @Version AND @Version IS NOT NULL; 127 | SELECT ROW_COUNT(); 128 | '); 129 | 130 | INSERT INTO OrleansQuery(QueryKey, QueryText) 131 | VALUES 132 | ( 133 | 'DeleteReminderRowsKey',' 134 | DELETE FROM OrleansRemindersTable 135 | WHERE 136 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL; 137 | '); 138 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/OrleansAdoNetContent/Oracle/Oracle-Main.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Implementation notes: 3 | 4 | 1) The general idea is that data is read and written through Orleans specific queries. 5 | Orleans operates on column names and types when reading and on parameter names and types when writing. 6 | 7 | 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. 8 | Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract 9 | is maintained. 10 | 11 | 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting 12 | by virtue of uniform naming across concrete implementations. 13 | 14 | 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation 15 | is not important as long as it represents a unique version. In this implementation we use integers for versioning 16 | 17 | 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value 18 | or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown 19 | the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. 20 | Orleans handles exception as a failure and will retry. 21 | 22 | 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: 23 | https://docs.microsoft.com/dotnet/orleans/implementation/cluster-management 24 | https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs 25 | */ 26 | 27 | -- This table defines Orleans operational queries. Orleans uses these to manage its operations, 28 | -- these are the only queries Orleans issues to the database. 29 | -- These can be redefined (e.g. to provide non-destructive updates) provided the stated interface principles hold. 30 | CREATE TABLE "ORLEANSQUERY" 31 | ( 32 | "QUERYKEY" VARCHAR2(64 BYTE) NOT NULL ENABLE, 33 | "QUERYTEXT" VARCHAR2(4000 BYTE), 34 | 35 | CONSTRAINT "ORLEANSQUERY_PK" PRIMARY KEY ("QUERYKEY") 36 | ); 37 | / 38 | 39 | COMMIT; 40 | 41 | -- Oracle specific implementation note: 42 | -- Some OrleansQueries are implemented as functions and differ from the scripts of other databases. 43 | -- The main reason for this is the fact, that oracle doesn't support returning variables from queries 44 | -- directly. So in the case that a variable value is needed as output of a OrleansQuery (e.g. version) 45 | -- a function is used. 46 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/OrleansAdoNetContent/PostgreSQL/PostgreSQL-Main.sql: -------------------------------------------------------------------------------- 1 | -- requires Postgres 9.5 (or perhaps higher) 2 | 3 | /* 4 | Implementation notes: 5 | 6 | 1) The general idea is that data is read and written through Orleans specific queries. 7 | Orleans operates on column names and types when reading and on parameter names and types when writing. 8 | 9 | 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. 10 | Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract 11 | is maintained. 12 | 13 | 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting 14 | by virtue of uniform naming across concrete implementations. 15 | 16 | 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation 17 | is not important as long as it represents a unique version. In this implementation we use integers for versioning 18 | 19 | 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value 20 | or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown 21 | the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. 22 | Orleans handles exception as a failure and will retry. 23 | 24 | 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: 25 | https://docs.microsoft.com/dotnet/orleans/implementation/cluster-management 26 | https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs 27 | */ 28 | 29 | 30 | 31 | -- This table defines Orleans operational queries. Orleans uses these to manage its operations, 32 | -- these are the only queries Orleans issues to the database. 33 | -- These can be redefined (e.g. to provide non-destructive updates) provided the stated interface principles hold. 34 | CREATE TABLE OrleansQuery 35 | ( 36 | QueryKey varchar(64) NOT NULL, 37 | QueryText varchar(8000) NOT NULL, 38 | 39 | CONSTRAINT OrleansQuery_Key PRIMARY KEY(QueryKey) 40 | ); 41 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/OrleansAdoNetContent/SQLServer/SQLServer-Main.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Implementation notes: 3 | 4 | 1) The general idea is that data is read and written through Orleans specific queries. 5 | Orleans operates on column names and types when reading and on parameter names and types when writing. 6 | 7 | 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. 8 | Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract 9 | is maintained. 10 | 11 | 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting 12 | by virtue of uniform naming across concrete implementations. 13 | 14 | 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation 15 | is not important as long as it represents a unique version. In this implementation we use integers for versioning 16 | 17 | 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value 18 | or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown 19 | the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. 20 | Orleans handles exception as a failure and will retry. 21 | 22 | 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: 23 | https://docs.microsoft.com/dotnet/orleans/implementation/cluster-management 24 | https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs 25 | */ 26 | 27 | -- These settings improves throughput of the database by reducing locking by better separating readers from writers. 28 | -- SQL Server 2012 and newer can refer to itself as CURRENT. Older ones need a workaround. 29 | DECLARE @current NVARCHAR(256); 30 | DECLARE @snapshotSettings NVARCHAR(612); 31 | 32 | SELECT @current = N'[' + (SELECT DB_NAME()) + N']'; 33 | SET @snapshotSettings = N'ALTER DATABASE ' + @current + N' SET READ_COMMITTED_SNAPSHOT ON; ALTER DATABASE ' + @current + N' SET ALLOW_SNAPSHOT_ISOLATION ON;'; 34 | 35 | EXECUTE sp_executesql @snapshotSettings; 36 | 37 | -- This table defines Orleans operational queries. Orleans uses these to manage its operations, 38 | -- these are the only queries Orleans issues to the database. 39 | -- These can be redefined (e.g. to provide non-destructive updates) provided the stated interface principles hold. 40 | CREATE TABLE OrleansQuery 41 | ( 42 | QueryKey VARCHAR(64) NOT NULL, 43 | QueryText VARCHAR(8000) NOT NULL, 44 | 45 | CONSTRAINT OrleansQuery_Key PRIMARY KEY(QueryKey) 46 | ); 47 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/OrleansAdoNetContent/SQLServer/SQLServer-Reminders.sql: -------------------------------------------------------------------------------- 1 | -- Orleans Reminders table - https://docs.microsoft.com/dotnet/orleans/grains/timers-and-reminders 2 | CREATE TABLE OrleansRemindersTable 3 | ( 4 | ServiceId NVARCHAR(150) NOT NULL, 5 | GrainId VARCHAR(150) NOT NULL, 6 | ReminderName NVARCHAR(150) NOT NULL, 7 | StartTime DATETIME2(3) NOT NULL, 8 | Period BIGINT NOT NULL, 9 | GrainHash INT NOT NULL, 10 | Version INT NOT NULL, 11 | 12 | CONSTRAINT PK_RemindersTable_ServiceId_GrainId_ReminderName PRIMARY KEY(ServiceId, GrainId, ReminderName) 13 | ); 14 | 15 | INSERT INTO OrleansQuery(QueryKey, QueryText) 16 | VALUES 17 | ( 18 | 'UpsertReminderRowKey',' 19 | DECLARE @Version AS INT = 0; 20 | SET XACT_ABORT, NOCOUNT ON; 21 | BEGIN TRANSACTION; 22 | UPDATE OrleansRemindersTable WITH(UPDLOCK, ROWLOCK, HOLDLOCK) 23 | SET 24 | StartTime = @StartTime, 25 | Period = @Period, 26 | GrainHash = @GrainHash, 27 | @Version = Version = Version + 1 28 | WHERE 29 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 30 | AND GrainId = @GrainId AND @GrainId IS NOT NULL 31 | AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL; 32 | 33 | INSERT INTO OrleansRemindersTable 34 | ( 35 | ServiceId, 36 | GrainId, 37 | ReminderName, 38 | StartTime, 39 | Period, 40 | GrainHash, 41 | Version 42 | ) 43 | SELECT 44 | @ServiceId, 45 | @GrainId, 46 | @ReminderName, 47 | @StartTime, 48 | @Period, 49 | @GrainHash, 50 | 0 51 | WHERE 52 | @@ROWCOUNT=0; 53 | SELECT @Version AS Version; 54 | COMMIT TRANSACTION; 55 | '); 56 | 57 | INSERT INTO OrleansQuery(QueryKey, QueryText) 58 | VALUES 59 | ( 60 | 'ReadReminderRowsKey',' 61 | SELECT 62 | GrainId, 63 | ReminderName, 64 | StartTime, 65 | Period, 66 | Version 67 | FROM OrleansRemindersTable 68 | WHERE 69 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 70 | AND GrainId = @GrainId AND @GrainId IS NOT NULL; 71 | '); 72 | 73 | INSERT INTO OrleansQuery(QueryKey, QueryText) 74 | VALUES 75 | ( 76 | 'ReadReminderRowKey',' 77 | SELECT 78 | GrainId, 79 | ReminderName, 80 | StartTime, 81 | Period, 82 | Version 83 | FROM OrleansRemindersTable 84 | WHERE 85 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 86 | AND GrainId = @GrainId AND @GrainId IS NOT NULL 87 | AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL; 88 | '); 89 | 90 | INSERT INTO OrleansQuery(QueryKey, QueryText) 91 | VALUES 92 | ( 93 | 'ReadRangeRows1Key',' 94 | SELECT 95 | GrainId, 96 | ReminderName, 97 | StartTime, 98 | Period, 99 | Version 100 | FROM OrleansRemindersTable 101 | WHERE 102 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 103 | AND GrainHash > @BeginHash AND @BeginHash IS NOT NULL 104 | AND GrainHash <= @EndHash AND @EndHash IS NOT NULL; 105 | '); 106 | 107 | INSERT INTO OrleansQuery(QueryKey, QueryText) 108 | VALUES 109 | ( 110 | 'ReadRangeRows2Key',' 111 | SELECT 112 | GrainId, 113 | ReminderName, 114 | StartTime, 115 | Period, 116 | Version 117 | FROM OrleansRemindersTable 118 | WHERE 119 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 120 | AND ((GrainHash > @BeginHash AND @BeginHash IS NOT NULL) 121 | OR (GrainHash <= @EndHash AND @EndHash IS NOT NULL)); 122 | '); 123 | 124 | INSERT INTO OrleansQuery(QueryKey, QueryText) 125 | VALUES 126 | ( 127 | 'DeleteReminderRowKey',' 128 | DELETE FROM OrleansRemindersTable 129 | WHERE 130 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL 131 | AND GrainId = @GrainId AND @GrainId IS NOT NULL 132 | AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL 133 | AND Version = @Version AND @Version IS NOT NULL; 134 | SELECT @@ROWCOUNT; 135 | '); 136 | 137 | INSERT INTO OrleansQuery(QueryKey, QueryText) 138 | VALUES 139 | ( 140 | 'DeleteReminderRowsKey',' 141 | DELETE FROM OrleansRemindersTable 142 | WHERE 143 | ServiceId = @ServiceId AND @ServiceId IS NOT NULL; 144 | '); 145 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/SiloHostService.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class that contains the Orleans silo host main service. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.SiloHost; 11 | 12 | /// 13 | public class SiloHostService : BackgroundServiceBase 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The silo host configuration.. 19 | public SiloHostService(SiloHostConfiguration siloHostConfiguration) : base(siloHostConfiguration) 20 | { 21 | } 22 | 23 | /// 24 | public override async Task StartAsync(CancellationToken cancellationToken) 25 | { 26 | this.Logger.Information("Starting MQTT silo host service..."); 27 | await base.StartAsync(cancellationToken); 28 | this.Logger.Information("MQTT silo host service started."); 29 | } 30 | 31 | /// 32 | public override async Task StopAsync(CancellationToken cancellationToken) 33 | { 34 | this.Logger.Information("Stopping MQTT silo host service..."); 35 | await base.StopAsync(cancellationToken); 36 | this.Logger.Information("MQTT silo host service stopped."); 37 | } 38 | 39 | /// 40 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 41 | { 42 | while (!stoppingToken.IsCancellationRequested) 43 | { 44 | this.LogMemoryInformation(this.ServiceConfiguration.HeartbeatIntervalInMilliSeconds, Program.ServiceName.Name ?? "SiloHost"); 45 | await Task.Delay(this.ServiceConfiguration.HeartbeatIntervalInMilliSeconds, stoppingToken); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/Startup.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The startup class. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.SiloHost; 11 | 12 | /// 13 | /// The startup class. 14 | /// 15 | public class Startup 16 | { 17 | /// 18 | /// The MQTT configuration. 19 | /// 20 | private readonly ClusterConfiguration mqttConfiguration = new(); 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The configuration. 26 | public Startup(IConfiguration configuration) 27 | { 28 | configuration.GetSection(Program.ServiceName.Name ?? "NetCoreMQTTExampleCluster.SiloHost").Bind(this.mqttConfiguration); 29 | } 30 | 31 | /// 32 | /// Configures the services. 33 | /// 34 | /// The services. 35 | public void ConfigureServices(IServiceCollection services) 36 | { 37 | // Add configuration. 38 | services.AddOptions(); 39 | services.AddSingleton(this.mqttConfiguration); 40 | 41 | // Add the logger. 42 | services.AddSingleton(Log.Logger); 43 | 44 | // Add JSON converters. 45 | services.AddMvc() 46 | .AddRazorPagesOptions(options => options.RootDirectory = "/") 47 | .AddDataAnnotationsLocalization(); 48 | 49 | // Workaround to have a hosted background service available by DI. 50 | services.AddSingleton(); 51 | services.AddSingleton(p => p.GetRequiredService()); 52 | } 53 | 54 | /// 55 | /// This method gets called by the runtime. 56 | /// 57 | /// The application. 58 | /// The web hosting environment. 59 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 60 | { 61 | if (env.IsDevelopment()) 62 | { 63 | app.UseDeveloperExceptionPage(); 64 | } 65 | 66 | app.UseSerilogRequestLogging(); 67 | app.UseRouting(); 68 | 69 | app.Map("/dashboard", a => a.UseOrleansDashboard()); 70 | 71 | _ = app.ApplicationServices.GetService(); 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "NetCoreMQTTExampleCluster.SiloHost": { 3 | "LogFolderPath": "C:\\log\\NetCoreMQTTExampleCluster.SiloHost", 4 | "HeartbeatIntervalInMilliseconds": 30000, 5 | "DatabaseSettings": { 6 | "Host": "localhost", 7 | "Database": "mqtt", 8 | "Username": "postgres", 9 | "Password": "postgres", 10 | "Port": 5432 11 | }, 12 | "OrleansConfiguration": { 13 | "ClusterOptions": { 14 | "ClusterId": "mqtt001", 15 | "ServiceId": "mqtt-silo-001-A" 16 | }, 17 | "DashboardOptions": { 18 | "CounterUpdateIntervalMs": 1000, 19 | "Port": 4321 20 | }, 21 | "EndpointOptions": { 22 | "AdvertisedIPAddress": "127.0.0.1", 23 | "SiloPort": 7101, 24 | "GatewayPort": 7102, 25 | "SiloListeningEndpointAddress": "127.0.0.1", 26 | "SiloListeningEndpointPort": 7101, 27 | "GatewayListeningEndpointAddress": "127.0.0.1", 28 | "GatewayListeningEndpointPort": 7102 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.SiloHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "NetCoreMQTTExampleCluster.SiloHost": { 3 | "LogFolderPath": "C:\\log\\NetCoreMQTTExampleCluster.SiloHost", 4 | "HeartbeatIntervalInMilliseconds": 30000, 5 | "DatabaseSettings": { 6 | "Host": "localhost", 7 | "Database": "mqtt", 8 | "Username": "postgres", 9 | "Password": "postgres", 10 | "Port": 5432 11 | }, 12 | "OrleansConfiguration": { 13 | "ClusterOptions": { 14 | "ClusterId": "mqtt001", 15 | "ServiceId": "mqtt-silo-001-A" 16 | }, 17 | "DashboardOptions": { 18 | "CounterUpdateIntervalMs": 1000, 19 | "Port": 4321 20 | }, 21 | "EndpointOptions": { 22 | "AdvertisedIPAddress": "127.0.0.1", 23 | "SiloPort": 7101, 24 | "GatewayPort": 7102, 25 | "SiloListeningEndpointAddress": "127.0.0.1", 26 | "SiloListeningEndpointPort": 7101, 27 | "GatewayListeningEndpointAddress": "127.0.0.1", 28 | "GatewayListeningEndpointPort": 7102 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/BlacklistWhiteList.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The blacklist or whitelist class. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | /// 13 | /// The blacklist or whitelist class. 14 | /// 15 | public class BlacklistWhitelist 16 | { 17 | /// 18 | /// Gets or sets the primary key. 19 | /// 20 | public Guid Id { get; set; } = Guid.NewGuid(); 21 | 22 | /// 23 | /// Gets or sets the user identifier. 24 | /// 25 | public Guid UserId { get; set; } 26 | 27 | /// 28 | /// Gets or sets the blacklist or whitelist type. 29 | /// 30 | public BlacklistWhitelistType Type { get; set; } 31 | 32 | /// 33 | /// Gets or sets the blacklist or whitelist value. 34 | /// 35 | public string Value { get; set; } = string.Empty; 36 | 37 | /// 38 | /// Gets or sets the created at timestamp. 39 | /// 40 | public DateTimeOffset CreatedAt { get; set; } 41 | 42 | /// 43 | /// Gets or sets the deleted at timestamp. 44 | /// 45 | public DateTimeOffset? DeletedAt { get; set; } 46 | 47 | /// 48 | /// Gets or sets the updated at timestamp. 49 | /// 50 | public DateTimeOffset? UpdatedAt { get; set; } = null; 51 | 52 | /// 53 | /// Returns a representation of the class. 54 | /// 55 | /// A representation of the class. 56 | public override string ToString() 57 | { 58 | return JsonConvert.SerializeObject(this); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/BlacklistWhitelistType.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An enumeration representing the available blacklist or whitelist types. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | /// 13 | /// An enumeration representing the available blacklist or whitelist types. 14 | /// 15 | [JsonConverter(typeof(StringEnumConverter))] 16 | public enum BlacklistWhitelistType 17 | { 18 | /// 19 | /// The subscribe blacklist or whitelist type. 20 | /// 21 | Subscribe, 22 | 23 | /// 24 | /// The publish blacklist or whitelist type. 25 | /// 26 | Publish 27 | } 28 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/DatabaseVersion.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The database version class. It contains information about the database version used. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | /// 13 | /// The database version class. It contains information about the database version used. 14 | /// 15 | public class DatabaseVersion 16 | { 17 | /// 18 | /// Gets or sets the primary key. 19 | /// 20 | public Guid Id { get; set; } = Guid.NewGuid(); 21 | 22 | /// 23 | /// Gets or sets the version name. 24 | /// 25 | public string Name { get; set; } = string.Empty; 26 | 27 | /// 28 | /// Gets or sets the version number. 29 | /// 30 | public long Number { get; set; } 31 | 32 | /// 33 | /// Gets or sets the created at timestamp. 34 | /// 35 | public DateTimeOffset CreatedAt { get; set; } 36 | 37 | /// 38 | /// Returns a representation of the class. 39 | /// 40 | /// A representation of the class. 41 | public override string ToString() 42 | { 43 | return JsonConvert.SerializeObject(this); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/EventLog.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The event log class. It contains information about events that occurred on the other database tables. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | /// 13 | /// The event log class. It contains information about events that occurred on the other database tables. 14 | /// 15 | public class EventLog 16 | { 17 | /// 18 | /// Gets or sets the primary key. 19 | /// 20 | public Guid Id { get; set; } = Guid.NewGuid(); 21 | 22 | /// 23 | /// Gets or sets the event type. 24 | /// 25 | public EventType EventType { get; set; } 26 | 27 | /// 28 | /// Gets or sets the event details. 29 | /// 30 | public string EventDetails { get; set; } = string.Empty; 31 | 32 | /// 33 | /// Gets or sets the created at timestamp. 34 | /// 35 | public DateTimeOffset CreatedAt { get; set; } 36 | 37 | /// 38 | /// Returns a representation of the class. 39 | /// 40 | /// A representation of the class. 41 | public override string ToString() 42 | { 43 | return JsonConvert.SerializeObject(this); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/EventType.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An enumeration representing the event types. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | /// 13 | /// An enumeration representing the event types. 14 | /// 15 | [JsonConverter(typeof(StringEnumConverter))] 16 | public enum EventType 17 | { 18 | /// 19 | /// The subscription event type. 20 | /// 21 | Subscription, 22 | 23 | /// 24 | /// The unsubscription event type. 25 | /// 26 | Unsubscription, 27 | 28 | /// 29 | /// The connect event type. 30 | /// 31 | Connect, 32 | 33 | /// 34 | /// The disconnect event type. 35 | /// 36 | Disconnect, 37 | 38 | /// 39 | /// The broker connect event type. 40 | /// 41 | BrokerConnect, 42 | 43 | /// 44 | /// The broker disconnect event type. 45 | /// 46 | BrokerDisconnect 47 | } 48 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/MqttUser.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The MQTT user class. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | /// 13 | /// The MQTT user class. 14 | /// 15 | public class MqttUser 16 | { 17 | /// 18 | /// Gets or sets the primary key. 19 | /// 20 | public Guid Id { get; set; } = Guid.NewGuid(); 21 | 22 | /// 23 | /// Gets or sets the user name. 24 | /// 25 | public string UserName { get; set; } = string.Empty; 26 | 27 | /// 28 | /// Gets or sets a salted and hashed representation of the password. 29 | /// 30 | [JsonIgnore] 31 | public string PasswordHash { get; set; } = string.Empty; 32 | 33 | /// 34 | /// Gets or sets the client identifier prefix. 35 | /// 36 | public string ClientIdPrefix { get; set; } = string.Empty; 37 | 38 | /// 39 | /// Gets or sets the client identifier. 40 | /// 41 | public string ClientId { get; set; } = string.Empty; 42 | 43 | /// 44 | /// Gets or sets a value indicating whether the client identifier is validated or not. 45 | /// 46 | public bool ValidateClientId { get; set; } 47 | 48 | /// 49 | /// Gets or sets a value indicating whether the user is throttled after a certain limit or not. 50 | /// 51 | public bool ThrottleUser { get; set; } 52 | 53 | /// 54 | /// Gets or sets a user's monthly limit in byte. 55 | /// 56 | public long? MonthlyByteLimit { get; set; } 57 | 58 | /// 59 | /// Gets or sets a value indicating whether the user is a sync user or not. 60 | /// 61 | public bool IsSyncUser { get; set; } 62 | 63 | /// 64 | /// Gets or sets the description. 65 | /// 66 | public string Description { get; set; } = string.Empty; 67 | 68 | /// 69 | /// Gets or sets the created at timestamp. 70 | /// 71 | public DateTimeOffset CreatedAt { get; set; } 72 | 73 | /// 74 | /// Gets or sets the deleted at timestamp. 75 | /// 76 | public DateTimeOffset? DeletedAt { get; set; } 77 | 78 | /// 79 | /// Gets or sets the updated at timestamp. 80 | /// 81 | public DateTimeOffset? UpdatedAt { get; set; } = null; 82 | 83 | /// 84 | /// Returns a representation of the class. 85 | /// 86 | /// A representation of the class. 87 | public override string ToString() 88 | { 89 | return JsonConvert.SerializeObject(this); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/MqttUserData.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The MQTT user data class. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | namespace NetCoreMQTTExampleCluster.Storage.Data; 10 | 11 | /// 12 | /// The MQTT user data class. 13 | /// 14 | public class MqttUserData 15 | { 16 | /// 17 | /// Gets or sets the subscription whitelist. 18 | /// 19 | public List SubscriptionWhitelist { get; set; } = new(); 20 | 21 | /// 22 | /// Gets or sets the publish whitelist. 23 | /// 24 | public List PublishWhitelist { get; set; } = new(); 25 | 26 | /// 27 | /// Gets or sets the subscription blacklist. 28 | /// 29 | public List SubscriptionBlacklist { get; set; } = new(); 30 | 31 | /// 32 | /// Gets or sets the publish blacklist. 33 | /// 34 | public List PublishBlacklist { get; set; } = new(); 35 | 36 | /// 37 | /// Gets or sets the client identifier prefixes. 38 | /// 39 | public List ClientIdPrefixes { get; set; } = new(); 40 | } 41 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/PublishMessage.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The publish message class. It contains all published messages. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | /// 13 | /// The publish message class. It contains all published messages. 14 | /// 15 | public class PublishMessage 16 | { 17 | /// 18 | /// Gets or sets the primary key. 19 | /// 20 | public Guid Id { get; set; } = Guid.NewGuid(); 21 | 22 | /// 23 | /// Gets or sets the client identifier. 24 | /// 25 | public string ClientId { get; set; } = string.Empty; 26 | 27 | /// 28 | /// Gets or sets the topic. 29 | /// 30 | public string Topic { get; set; } = string.Empty; 31 | 32 | /// 33 | /// Gets or sets the payload. 34 | /// 35 | public PublishedMessagePayload Payload { get; set; } = new(); 36 | 37 | /// 38 | /// Gets or sets the quality of service level. 39 | /// 40 | public MqttQualityOfServiceLevel? QoS { get; set; } 41 | 42 | /// 43 | /// Gets or sets a value indicating whether the message was retained or not. 44 | /// 45 | public bool? Retain { get; set; } 46 | 47 | /// 48 | /// Gets or sets the created at timestamp. 49 | /// 50 | public DateTimeOffset CreatedAt { get; set; } 51 | 52 | /// 53 | /// Returns a representation of the class. 54 | /// 55 | /// A representation of the class. 56 | public override string ToString() 57 | { 58 | return JsonConvert.SerializeObject(this); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/PublishedMessagePayload.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class to serialize n published message payload to the JSON binary field in the database. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | /// 13 | /// A class to serialize n published message payload to the JSON binary field in the database. 14 | /// 15 | public class PublishedMessagePayload 16 | { 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// This constructor is needed for JSON (de-)serialization. 20 | /// 21 | public PublishedMessagePayload() 22 | { 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// The message. 29 | public PublishedMessagePayload(string message) 30 | { 31 | this.Message = message; 32 | } 33 | 34 | /// 35 | /// Gets or sets the message payload. 36 | /// 37 | public string Message { get; set; } = string.Empty; 38 | 39 | /// 40 | /// Returns a representation of the class. 41 | /// 42 | /// A representation of the class. 43 | public override string ToString() 44 | { 45 | return JsonConvert.SerializeObject(this); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Data/WebUser.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The web user class. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | /// 13 | /// The web user class. 14 | /// 15 | public class WebUser 16 | { 17 | /// 18 | /// Gets or sets the primary key. 19 | /// 20 | public Guid Id { get; set; } = Guid.NewGuid(); 21 | 22 | /// 23 | /// Gets or sets the user name. 24 | /// 25 | public string UserName { get; set; } = string.Empty; 26 | 27 | /// 28 | /// Gets or sets a salted and hashed representation of the password. 29 | /// 30 | [JsonIgnore] 31 | public string PasswordHash { get; set; } = string.Empty; 32 | 33 | /// 34 | /// Gets or sets the description. 35 | /// 36 | public string Description { get; set; } = string.Empty; 37 | 38 | /// 39 | /// Gets or sets the created at timestamp. 40 | /// 41 | public DateTimeOffset CreatedAt { get; set; } 42 | 43 | /// 44 | /// Gets or sets the deleted at timestamp. 45 | /// 46 | public DateTimeOffset? DeletedAt { get; set; } 47 | 48 | /// 49 | /// Gets or sets the updated at timestamp. 50 | /// 51 | public DateTimeOffset? UpdatedAt { get; set; } = null; 52 | 53 | /// 54 | /// Returns a representation of the class. 55 | /// 56 | /// A representation of the class. 57 | public override string ToString() 58 | { 59 | return JsonConvert.SerializeObject(this); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System.Data; 3 | global using System.Reflection; 4 | 5 | global using Dapper; 6 | 7 | global using MQTTnet.Protocol; 8 | 9 | global using NetCoreMQTTExampleCluster.Storage.Data; 10 | global using NetCoreMQTTExampleCluster.Storage.Interfaces; 11 | global using NetCoreMQTTExampleCluster.Storage.Mappers; 12 | global using NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 13 | global using NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 14 | global using NetCoreMQTTExampleCluster.Storage.Statements; 15 | 16 | global using Newtonsoft.Json; 17 | global using Newtonsoft.Json.Converters; 18 | 19 | global using Npgsql; 20 | 21 | global using Serilog; 22 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 23 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Interfaces/IMqttDatabaseConnectionSettings.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An interface for the database connection settings. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Interfaces; 11 | 12 | /// 13 | /// An interface for the database connection settings. 14 | /// 15 | public interface IMqttDatabaseConnectionSettings 16 | { 17 | /// 18 | /// Gets or sets the host of the database. 19 | /// 20 | string Host { get; set; } 21 | 22 | /// 23 | /// Gets or sets the database name. 24 | /// 25 | string Database { get; set; } 26 | 27 | /// 28 | /// Gets or sets the user name. 29 | /// 30 | string Username { get; set; } 31 | 32 | /// 33 | /// Gets or sets the password. 34 | /// 35 | string Password { get; set; } 36 | 37 | /// 38 | /// Gets or sets the port. 39 | /// 40 | int Port { get; set; } 41 | 42 | /// 43 | /// Gets or sets a value indicating whether the pooling is enabled or not. 44 | /// 45 | bool Pooling { get; set; } 46 | 47 | /// 48 | /// Gets or sets the time zone. 49 | /// 50 | string Timezone { get; set; } 51 | 52 | /// 53 | /// Gets the connection string. 54 | /// 55 | /// The connection string from the connection settings. 56 | string ToConnectionString(); 57 | 58 | /// 59 | /// Gets the administrator connection string. 60 | /// 61 | /// The administrator connection string from the connection settings. 62 | string ToAdminConnectionString(); 63 | } 64 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Mappers/JsonMapper.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A database mapper class to map classes to JSON. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Mappers; 11 | 12 | /// 13 | /// A database mapper class to map classes to JSON. 14 | /// 15 | /// The generic type parameter. 16 | public class JsonMapper : SqlMapper.TypeHandler where T : class, new() 17 | { 18 | /// 19 | /// The json serializer settings. 20 | /// 21 | private readonly JsonSerializerSettings jsonSerializerSettingsCustom = new() 22 | { 23 | NullValueHandling = NullValueHandling.Ignore, 24 | Formatting = Formatting.Indented, 25 | ObjectCreationHandling = ObjectCreationHandling.Replace, 26 | Converters = new List 27 | { 28 | new IsoDateTimeConverter() 29 | } 30 | }; 31 | 32 | /// 33 | /// Parses a database value back to a typed value. 34 | /// 35 | /// The value from the database. 36 | /// The typed value. 37 | public override T? Parse(object value) 38 | { 39 | var config = new T(); 40 | 41 | if (value is not null) 42 | { 43 | config = JsonConvert.DeserializeObject(value?.ToString() ?? string.Empty, this.jsonSerializerSettingsCustom); 44 | } 45 | 46 | return config; 47 | } 48 | 49 | /// 50 | /// Assigns the value of a parameter before a command executes. 51 | /// 52 | /// The parameter to configure. 53 | /// The parameter value. 54 | public override void SetValue(IDbDataParameter parameter, T? value) 55 | { 56 | parameter.Value = JsonConvert.SerializeObject(value, this.jsonSerializerSettingsCustom); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/NetCoreMQTTExampleCluster.Storage.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | latest 7 | enable 8 | NU1803 9 | true 10 | all 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/OrleansQueries/PostgreSQL-Main.sql: -------------------------------------------------------------------------------- 1 | -- requires Postgres 9.5 (or perhaps higher) 2 | 3 | /* 4 | Implementation notes: 5 | 6 | 1) The general idea is that data is read and written through Orleans specific queries. 7 | Orleans operates on column names and types when reading and on parameter names and types when writing. 8 | 9 | 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. 10 | Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract 11 | is maintained. 12 | 13 | 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting 14 | by virtue of uniform naming across concrete implementations. 15 | 16 | 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation 17 | is not important as long as it represents a unique version. In this implementation we use integers for versioning 18 | 19 | 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value 20 | or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown 21 | the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. 22 | Orleans handles exception as a failure and will retry. 23 | 24 | 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: 25 | https://docs.microsoft.com/dotnet/orleans/implementation/cluster-management 26 | https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs 27 | */ 28 | 29 | 30 | 31 | -- This table defines Orleans operational queries. Orleans uses these to manage its operations, 32 | -- these are the only queries Orleans issues to the database. 33 | -- These can be redefined (e.g. to provide non-destructive updates) provided the stated interface principles hold. 34 | CREATE TABLE OrleansQuery 35 | ( 36 | QueryKey varchar(64) NOT NULL, 37 | QueryText varchar(8000) NOT NULL, 38 | 39 | CONSTRAINT OrleansQuery_Key PRIMARY KEY(QueryKey) 40 | ); 41 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Implementation/BaseRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A base class for all repositories. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 11 | 12 | /// 13 | /// A base class for all repositories. 14 | /// 15 | public class BaseRepository 16 | { 17 | /// 18 | /// The connection settings to use. 19 | /// 20 | protected readonly IMqttDatabaseConnectionSettings ConnectionSettings; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The connection settings to use. 26 | public BaseRepository(IMqttDatabaseConnectionSettings connectionSettings) 27 | { 28 | this.ConnectionSettings = connectionSettings; 29 | SqlMapper.AddTypeHandler(typeof(PublishedMessagePayload), new JsonMapper()); 30 | } 31 | 32 | /// 33 | /// Gets a database connection. 34 | /// 35 | /// A . 36 | public async Task GetDatabaseConnection() 37 | { 38 | var connection = new NpgsqlConnection(this.ConnectionSettings.ToConnectionString()); 39 | await connection.OpenAsync().ConfigureAwait(false); 40 | return connection; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Implementation/BlacklistRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An implementation supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 11 | 12 | /// 13 | public class BlacklistRepository : BaseRepository, IBlacklistRepository 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The connection settings to use. 19 | public BlacklistRepository(IMqttDatabaseConnectionSettings connectionSettings) : base(connectionSettings) 20 | { 21 | } 22 | 23 | /// 24 | public async Task> GetAllBlacklistItems() 25 | { 26 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 27 | var blacklistItems = await connection.QueryAsync(SelectStatements.SelectAllBlacklistItems); 28 | return blacklistItems?.ToList() ?? new List(); 29 | } 30 | 31 | /// 32 | public async Task GetBlacklistItemById(Guid blacklistItemId) 33 | { 34 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 35 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectBlacklistItemById, new {Id = blacklistItemId}); 36 | } 37 | 38 | /// 39 | public async Task GetBlacklistItemByIdAndType(Guid blacklistItemId, BlacklistWhitelistType blacklistItemType) 40 | { 41 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 42 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectBlacklistItemByIdAndType, new {Id = blacklistItemId, Type = blacklistItemType}); 43 | } 44 | 45 | /// 46 | public async Task GetBlacklistItemByType(BlacklistWhitelistType blacklistItemType) 47 | { 48 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 49 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectBlacklistItemByType, new {Type = blacklistItemType}); 50 | } 51 | 52 | /// 53 | public async Task InsertBlacklistItem(BlacklistWhitelist blacklistItem) 54 | { 55 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 56 | var result = await connection.ExecuteAsync(InsertStatements.InsertBlacklistItem, blacklistItem); 57 | return result == 1; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Implementation/DatabaseVersionRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An implementation supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 11 | 12 | /// 13 | public class DatabaseVersionRepository : BaseRepository, IDatabaseVersionRepository 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The connection settings to use. 19 | public DatabaseVersionRepository(IMqttDatabaseConnectionSettings connectionSettings) : base(connectionSettings) 20 | { 21 | } 22 | 23 | /// 24 | public async Task> GetDatabaseVersions() 25 | { 26 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 27 | var databaseVersions = await connection.QueryAsync(SelectStatements.SelectAllDatabaseVersions); 28 | return databaseVersions?.ToList() ?? new List(); 29 | } 30 | 31 | /// 32 | public async Task GetDatabaseVersionById(Guid databaseVersionId) 33 | { 34 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 35 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectDatabaseVersionById, new {Id = databaseVersionId}); 36 | } 37 | 38 | /// 39 | public async Task GetDatabaseVersionByName(string databaseVersionName) 40 | { 41 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 42 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectDatabaseVersionByName, new {DatabaseVersionName = databaseVersionName}); 43 | } 44 | 45 | /// 46 | public async Task InsertDatabaseVersion(DatabaseVersion package) 47 | { 48 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 49 | var result = await connection.ExecuteAsync(InsertStatements.InsertDatabaseVersion, package); 50 | return result == 1; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Implementation/EventLogRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An implementation supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 11 | 12 | /// 13 | public class EventLogRepository : BaseRepository, IEventLogRepository 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The connection settings to use. 19 | public EventLogRepository(IMqttDatabaseConnectionSettings connectionSettings) : base(connectionSettings) 20 | { 21 | } 22 | 23 | /// 24 | public async Task> GetEventLogs() 25 | { 26 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 27 | var eventLogs = await connection.QueryAsync(SelectStatements.SelectAllEventLogs); 28 | return eventLogs?.ToList() ?? new List(); 29 | } 30 | 31 | /// 32 | public async Task GetEventLogById(Guid eventLogId) 33 | { 34 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 35 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectEventLogById, new { Id = eventLogId }); 36 | } 37 | 38 | /// 39 | public async Task InsertEventLog(EventLog eventLog) 40 | { 41 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 42 | var result = await connection.ExecuteAsync(InsertStatements.InsertEventLog, eventLog); 43 | return result == 1; 44 | } 45 | 46 | /// 47 | public async Task InsertEventLogs(List eventLogs) 48 | { 49 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 50 | var result = await connection.ExecuteAsync(InsertStatements.InsertEventLog, eventLogs); 51 | return result == 1; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Implementation/PublishMessageRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An implementation supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 11 | 12 | /// 13 | /// 14 | /// An implementation supporting the repository pattern to work with s. 15 | /// 16 | public class PublishMessageRepository : BaseRepository, IPublishMessageRepository 17 | { 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The connection settings to use. 22 | public PublishMessageRepository(IMqttDatabaseConnectionSettings connectionSettings) : base(connectionSettings) 23 | { 24 | } 25 | 26 | /// 27 | public async Task> GetPublishMessages() 28 | { 29 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 30 | var publishMessages = await connection.QueryAsync(SelectStatements.SelectAllPublishMessages); 31 | return publishMessages?.ToList() ?? new List(); 32 | } 33 | 34 | /// 35 | public async Task GetPublishMessageById(Guid publishMessageId) 36 | { 37 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 38 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectPublishMessageById, new { Id = publishMessageId }); 39 | } 40 | 41 | /// 42 | public async Task InsertPublishMessage(PublishMessage publishMessage) 43 | { 44 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 45 | var result = await connection.ExecuteAsync(InsertStatements.InsertPublishMessage, publishMessage); 46 | return result == 1; 47 | } 48 | 49 | /// 50 | public async Task InsertPublishMessages(List publishMessages) 51 | { 52 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 53 | var result = await connection.ExecuteAsync(InsertStatements.InsertPublishMessage, publishMessages); 54 | return result == 1; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Implementation/WebUserRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An implementation supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 11 | 12 | /// 13 | /// 14 | /// An implementation supporting the repository pattern to work with s. 15 | /// 16 | public class WebUserRepository : BaseRepository, IWebUserRepository 17 | { 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The connection settings to use. 22 | public WebUserRepository(IMqttDatabaseConnectionSettings connectionSettings): base(connectionSettings) 23 | { 24 | } 25 | 26 | /// 27 | public async Task> GetWebUsers() 28 | { 29 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 30 | var users = await connection.QueryAsync(SelectStatements.SelectAllWebUsers); 31 | return users?.ToList() ?? new List(); 32 | } 33 | 34 | /// 35 | public async Task GetWebUserById(Guid userId) 36 | { 37 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 38 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectWebUserById, new {Id = userId}); 39 | } 40 | 41 | /// 42 | public async Task GetWebUserByName(string userName) 43 | { 44 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 45 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectWebUserByUserName, new {UserName = userName}); 46 | } 47 | 48 | /// 49 | public async Task InsertWebUser(WebUser webUser) 50 | { 51 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 52 | var result = await connection.ExecuteAsync(InsertStatements.InsertWebUser, webUser); 53 | return result == 1; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Implementation/WhitelistRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An implementation supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Implementation; 11 | 12 | /// 13 | public class WhitelistRepository: BaseRepository, IWhitelistRepository 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The connection settings to use. 19 | public WhitelistRepository(IMqttDatabaseConnectionSettings connectionSettings) : base(connectionSettings) 20 | { 21 | } 22 | 23 | /// 24 | public async Task> GetAllWhitelistItems() 25 | { 26 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 27 | var whitelistItems = await connection.QueryAsync(SelectStatements.SelectAllWhitelistItems); 28 | return whitelistItems?.ToList() ?? new List(); 29 | } 30 | 31 | /// 32 | public async Task GetWhitelistItemById(Guid whitelistItemId) 33 | { 34 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 35 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectWhitelistItemById, new {Id = whitelistItemId}); 36 | } 37 | 38 | /// 39 | public async Task GetWhitelistItemByIdAndType(Guid whitelistItemId, BlacklistWhitelistType whitelistItemType) 40 | { 41 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 42 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectWhitelistItemByIdAndType, new {Id = whitelistItemId, Type = whitelistItemType}); 43 | } 44 | 45 | /// 46 | public async Task GetWhitelistItemByType(BlacklistWhitelistType whitelistItemType) 47 | { 48 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 49 | return await connection.QueryFirstOrDefaultAsync(SelectStatements.SelectWhitelistItemByType, new {Type = whitelistItemType}); 50 | } 51 | 52 | /// 53 | public async Task InsertWhitelistItem(BlacklistWhitelist whitelistItem) 54 | { 55 | await using var connection = await this.GetDatabaseConnection().ConfigureAwait(false); 56 | var result = await connection.ExecuteAsync(InsertStatements.InsertWhitelistItem, whitelistItem); 57 | return result == 1; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Interfaces/IBlacklistRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An interface supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 11 | 12 | /// 13 | /// An interface supporting the repository pattern to work with s. 14 | /// 15 | public interface IBlacklistRepository 16 | { 17 | /// 18 | /// Gets a of all items. 19 | /// 20 | /// A representing any asynchronous operation. 21 | Task> GetAllBlacklistItems(); 22 | 23 | /// 24 | /// Gets a item by its identifier. 25 | /// 26 | /// The 's identifier to query for. 27 | /// A representing any asynchronous operation. 28 | Task GetBlacklistItemById(Guid blacklistItemId); 29 | 30 | /// 31 | /// Gets a item by its type. 32 | /// 33 | /// The to query for. 34 | /// A representing any asynchronous operation. 35 | Task GetBlacklistItemByType(BlacklistWhitelistType blacklistItemType); 36 | 37 | /// 38 | /// Gets a item by its type. 39 | /// 40 | /// The 's identifier to query for. 41 | /// The to query for. 42 | /// A representing any asynchronous operation. 43 | Task GetBlacklistItemByIdAndType(Guid blacklistItemId, BlacklistWhitelistType blacklistItemType); 44 | 45 | /// 46 | /// Inserts a item to the database. 47 | /// 48 | /// The item to insert. 49 | /// A representing any asynchronous operation. 50 | Task InsertBlacklistItem(BlacklistWhitelist blacklistItem); 51 | } 52 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Interfaces/IDatabaseVersionRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An interface supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 11 | 12 | /// 13 | /// An interface supporting the repository pattern to work with s. 14 | /// 15 | public interface IDatabaseVersionRepository 16 | { 17 | /// 18 | /// Gets a of all s. 19 | /// 20 | /// A representing any asynchronous operation. 21 | Task> GetDatabaseVersions(); 22 | 23 | /// 24 | /// Gets a by its identifier. 25 | /// 26 | /// The The 's identifier to query for. 27 | /// A representing any asynchronous operation. 28 | Task GetDatabaseVersionById(Guid databaseVersionId); 29 | 30 | /// 31 | /// Gets a by its name. 32 | /// 33 | /// The 's name to query for. 34 | /// A representing any asynchronous operation. 35 | Task GetDatabaseVersionByName(string databaseVersionName); 36 | 37 | /// 38 | /// Inserts a to the database. 39 | /// 40 | /// The to insert. 41 | /// A representing any asynchronous operation. 42 | Task InsertDatabaseVersion(DatabaseVersion databaseVersion); 43 | } 44 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Interfaces/IEventLogRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An interface supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 11 | 12 | /// 13 | /// An interface supporting the repository pattern to work with s. 14 | /// 15 | public interface IEventLogRepository 16 | { 17 | /// 18 | /// Gets a of all s. 19 | /// 20 | /// A representing any asynchronous operation. 21 | Task> GetEventLogs(); 22 | 23 | /// 24 | /// Gets a by its identifier. 25 | /// 26 | /// The 's identifier to query for. 27 | /// A representing any asynchronous operation. 28 | Task GetEventLogById(Guid eventLogId); 29 | 30 | /// 31 | /// Inserts a to the database. 32 | /// 33 | /// The to insert. 34 | /// A representing any asynchronous operation. 35 | Task InsertEventLog(EventLog eventLog); 36 | 37 | /// 38 | /// Inserts a of s to the database. 39 | /// 40 | /// The ofs to insert. 41 | /// A representing any asynchronous operation. 42 | Task InsertEventLogs(List eventLogs); 43 | } 44 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Interfaces/IPublishMessageRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An interface supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 11 | 12 | /// 13 | /// An interface supporting the repository pattern to work with s. 14 | /// 15 | public interface IPublishMessageRepository 16 | { 17 | /// 18 | /// Gets a of all s. 19 | /// 20 | /// A representing any asynchronous operation. 21 | Task> GetPublishMessages(); 22 | 23 | /// 24 | /// Gets a by its identifier. 25 | /// 26 | /// The 's identifier to query for. 27 | /// A representing any asynchronous operation. 28 | Task GetPublishMessageById(Guid publishMessageId); 29 | 30 | /// 31 | /// Inserts a to the database. 32 | /// 33 | /// The to insert. 34 | /// A representing any asynchronous operation. 35 | Task InsertPublishMessage(PublishMessage publishMessage); 36 | 37 | /// 38 | /// Inserts a of s to the database. 39 | /// 40 | /// The of s to insert. 41 | /// A representing any asynchronous operation. 42 | Task InsertPublishMessages(List publishMessages); 43 | } 44 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Interfaces/IWebUserRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An interface supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 11 | 12 | /// 13 | /// An interface supporting the repository pattern to work with s. 14 | /// 15 | public interface IWebUserRepository 16 | { 17 | /// 18 | /// Gets a of all s. 19 | /// 20 | /// A representing any asynchronous operation. 21 | Task> GetWebUsers(); 22 | 23 | /// 24 | /// Gets a by their identifier. 25 | /// 26 | /// The 's identifier to query for. 27 | /// A representing any asynchronous operation. 28 | Task GetWebUserById(Guid userId); 29 | 30 | /// 31 | /// Gets a by their user name. 32 | /// 33 | /// The 's name to query for. 34 | /// A representing any asynchronous operation. 35 | Task GetWebUserByName(string userName); 36 | 37 | /// 38 | /// Inserts a to the database. 39 | /// 40 | /// The to insert. 41 | /// A representing any asynchronous operation. 42 | Task InsertWebUser(WebUser webUser); 43 | } 44 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Repositories/Interfaces/IWhitelistRepository.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // An interface supporting the repository pattern to work with s. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 11 | 12 | /// 13 | /// An interface supporting the repository pattern to work with s. 14 | /// 15 | public interface IWhitelistRepository 16 | { 17 | /// 18 | /// Gets a of all items. 19 | /// 20 | /// A representing any asynchronous operation. 21 | Task> GetAllWhitelistItems(); 22 | 23 | /// 24 | /// Gets a item by its identifier. 25 | /// 26 | /// The 's identifier to query for. 27 | /// A representing any asynchronous operation. 28 | Task GetWhitelistItemById(Guid whitelistItemId); 29 | 30 | /// 31 | /// Gets a item by its type. 32 | /// 33 | /// The to query for. 34 | /// A representing any asynchronous operation. 35 | Task GetWhitelistItemByType(BlacklistWhitelistType whitelistItemType); 36 | 37 | /// 38 | /// Gets a item by its type. 39 | /// 40 | /// The 's identifier to query for. 41 | /// The to query for. 42 | /// A representing any asynchronous operation. 43 | Task GetWhitelistItemByIdAndType(Guid whitelistItemId, BlacklistWhitelistType whitelistItemType); 44 | 45 | /// 46 | /// Inserts a item to the database. 47 | /// 48 | /// The item to insert. 49 | /// A representing any asynchronous operation. 50 | Task InsertWhitelistItem(BlacklistWhitelist whitelistItem); 51 | } 52 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Statements/DropStatements.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The SQL statements for table deletion. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Statements; 11 | 12 | /// 13 | /// The SQL statements for table deletion. 14 | /// 15 | public class DropStatements 16 | { 17 | /// 18 | /// A SQL query string to drop the database. 19 | /// 20 | public const string DropDatabase = @"DROP DATABASE IF EXISTS @Database;"; 21 | 22 | /// 23 | /// A SQL query string to delete the publish message table. 24 | /// 25 | public const string DropPublishMessageTable = @"DROP TABLE IF EXISTS publishmessage;"; 26 | 27 | /// 28 | /// A SQL query string to delete the event log table. 29 | /// 30 | public const string DropEventLogTable = @"DROP TABLE IF EXISTS eventlog;"; 31 | 32 | /// 33 | /// A SQL query string to delete the database version table. 34 | /// 35 | public const string DropDatabaseVersionTable = @"DROP TABLE IF EXISTS databaseversion;"; 36 | 37 | /// 38 | /// A SQL query string to delete the blacklist table. 39 | /// 40 | public const string DropBlacklistTable = @"DROP TABLE IF EXISTS blacklist;"; 41 | 42 | /// 43 | /// A SQL query string to delete the whitelist table. 44 | /// 45 | public const string DropWhitelistTable = @"DROP TABLE IF EXISTS whitelist;"; 46 | 47 | /// 48 | /// A SQL query string to delete the MQTT user table. 49 | /// 50 | public const string DropMqttUserTable = @"DROP TABLE IF EXISTS mqttuser;"; 51 | 52 | /// 53 | /// A SQL query string to delete the web user table. 54 | /// 55 | public const string DropWebUserTable = @"DROP TABLE IF EXISTS webuser;"; 56 | } 57 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Storage/Statements/InsertStatements.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // The SQL statements for inserting data. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Storage.Statements; 11 | 12 | /// 13 | /// The SQL statements for inserting data. 14 | /// 15 | public class InsertStatements 16 | { 17 | /// 18 | /// A SQL query string to insert a publish message. 19 | /// 20 | public const string InsertPublishMessage = 21 | @"INSERT INTO publishmessage (id, clientid, topic, payload, qos, retain) 22 | VALUES (@Id, @ClientId, @Topic, JSON(@Payload), @QoS, @Retain);"; 23 | 24 | /// 25 | /// A SQL query string to insert an event log. 26 | /// 27 | public const string InsertEventLog = 28 | @"INSERT INTO eventlog (id, eventtype, eventdetails) 29 | VALUES (@Id, @EventType, @EventDetails);"; 30 | 31 | /// 32 | /// A SQL query string to insert a MQTT user. 33 | /// 34 | public const string InsertMqttUser = 35 | @"INSERT INTO mqttuser (id, username, passwordhash, clientidprefix, clientid, validateclientid, throttleuser, monthlybytelimit, issyncuser, description) 36 | VALUES (@Id, @UserName, @PasswordHash, @ClientIdPrefix, @ClientId, @ValidateClientId, @ThrottleUser, @MonthlyByteLimit, @IsSyncUser, @Description);"; 37 | 38 | /// 39 | /// A SQL query string to insert a web user. 40 | /// 41 | public const string InsertWebUser = 42 | @"INSERT INTO webuser (id, username, passwordhash, description) 43 | VALUES (@Id, @UserName, @PasswordHash, @Description);"; 44 | 45 | /// 46 | /// A SQL query string to insert a database version. 47 | /// 48 | public const string InsertDatabaseVersion = 49 | @"INSERT INTO databaseversion (id, name, number) 50 | VALUES (@Id, @Name, @Number);"; 51 | 52 | /// 53 | /// A SQL query string to insert a blacklist item. 54 | /// 55 | public const string InsertBlacklistItem = 56 | @"INSERT INTO blacklist (id, userid, type, value) 57 | VALUES (@Id, @UserId, @Type, @Value);"; 58 | 59 | /// 60 | /// A SQL query string to insert a whitelist item. 61 | /// 62 | public const string InsertWhitelistItem = 63 | @"INSERT INTO whitelist (id, userid, type, value) 64 | VALUES (@Id, @UserId, @Type, @Value);"; 65 | } 66 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.TopicCheck.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 4 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.TopicCheck.Tests/NetCoreMQTTExampleCluster.TopicCheck.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | latest 7 | enable 8 | NU1803 9 | true 10 | all 11 | true 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.TopicCheck.Tests/TopicCheckerTestsCrossOperator.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A test class to test the with the # operator. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.TopicCheck.Tests; 11 | 12 | /// 13 | /// A test class to test the with the # operator. 14 | /// 15 | [TestClass] 16 | public class TopicCheckerTestsCrossOperator 17 | { 18 | /// 19 | /// Checks the tester with a valid topic for the # operator. 20 | /// 21 | [TestMethod] 22 | public void CheckSingleValueCrossMatch() 23 | { 24 | var result = TopicChecker.Regex("a/#", "a/b"); 25 | Assert.IsTrue(result); 26 | } 27 | 28 | /// 29 | /// Checks the tester with another valid topic for the # operator. 30 | /// 31 | [TestMethod] 32 | public void CheckSingleValueCrossMatch2() 33 | { 34 | var result = TopicChecker.Regex("a/#", "a/b/c"); 35 | Assert.IsTrue(result); 36 | } 37 | 38 | /// 39 | /// Checks the tester with a valid topic with a # for the # operator. 40 | /// 41 | [TestMethod] 42 | public void CheckSingleValueCrossMatchWithCross() 43 | { 44 | var result = TopicChecker.Regex("a/#", "a/#"); 45 | Assert.IsTrue(result); 46 | } 47 | 48 | /// 49 | /// Checks the tester with a valid topic with a + for the # operator. 50 | /// 51 | [TestMethod] 52 | public void CheckSingleValueCrossMatchWithPlus() 53 | { 54 | var result = TopicChecker.Regex("a/#", "a/+"); 55 | Assert.IsTrue(result); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.TopicCheck.Tests/TopicCheckerTestsMultipleOperators.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A test class to test the with multiple operators. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.TopicCheck.Tests; 11 | 12 | /// 13 | /// A test class to test the with multiple operators. 14 | /// 15 | [TestClass] 16 | public class TopicCheckerTestsMultipleOperators 17 | { 18 | /// 19 | /// Checks the tester with an invalid # topic with multiple operators. 20 | /// 21 | [TestMethod] 22 | public void CheckMultipleValueCrossDontMatchMixed() 23 | { 24 | var result = TopicChecker.Regex("a/#", "a/+/+/#/a"); 25 | Assert.IsFalse(result); 26 | } 27 | 28 | /// 29 | /// Checks the tester with a valid # topic with multiple operators. 30 | /// 31 | [TestMethod] 32 | public void CheckMultipleValueCrossMatchMixed() 33 | { 34 | var result = TopicChecker.Regex("a/#", "a/+/a/#"); 35 | Assert.IsTrue(result); 36 | } 37 | 38 | /// 39 | /// Checks the tester with a valid # topic with multiple operators and multiple +. 40 | /// 41 | [TestMethod] 42 | public void CheckMultipleValueCrossMatchMultiplePlus() 43 | { 44 | var result = TopicChecker.Regex("a/#", "a/+/+/a"); 45 | Assert.IsTrue(result); 46 | } 47 | 48 | /// 49 | /// Checks the tester with a valid # topic with multiple operators and a single +. 50 | /// 51 | [TestMethod] 52 | public void CheckMultipleValueCrossMatchSinglePlus() 53 | { 54 | var result = TopicChecker.Regex("a/#", "a/+/a"); 55 | Assert.IsTrue(result); 56 | } 57 | 58 | /// 59 | /// Checks the tester with multiple + topics without operators. 60 | /// 61 | [TestMethod] 62 | public void CheckMultipleValuePlusMatchNoOperators() 63 | { 64 | var result = TopicChecker.Regex("a/+/+/a", "a/b/b/a"); 65 | Assert.IsTrue(result); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.TopicCheck.Tests/TopicCheckerTestsPlusOperator.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A test class to test the with the + operator. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.TopicCheck.Tests; 11 | 12 | /// 13 | /// A test class to test the with the + operator. 14 | /// 15 | [TestClass] 16 | public class TopicCheckerTestsPlusOperator 17 | { 18 | /// 19 | /// Checks the tester with an invalid topic for the + operator. 20 | /// 21 | [TestMethod] 22 | public void CheckSingleValuePlusDontMatch() 23 | { 24 | var result = TopicChecker.Regex("a/+", "a/b/c"); 25 | Assert.IsFalse(result); 26 | } 27 | 28 | /// 29 | /// Checks the tester with an invalid topic with a # for the + operator. 30 | /// 31 | [TestMethod] 32 | public void CheckSingleValuePlusDontMatchWithCross() 33 | { 34 | var result = TopicChecker.Regex("a/+", "a/#"); 35 | Assert.IsFalse(result); 36 | } 37 | 38 | /// 39 | /// Checks the tester with a valid topic for the + operator. 40 | /// 41 | [TestMethod] 42 | public void CheckSingleValuePlusMatch() 43 | { 44 | var result = TopicChecker.Regex("a/+", "a/b"); 45 | Assert.IsTrue(result); 46 | } 47 | 48 | /// 49 | /// Checks the tester with a valid topic with a + for the + operator. 50 | /// 51 | [TestMethod] 52 | public void CheckSingleValuePlusMatchWithPlus() 53 | { 54 | var result = TopicChecker.Regex("a/+", "a/+"); 55 | Assert.IsTrue(result); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.TopicCheck/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System.Reflection; 3 | global using System.Text.RegularExpressions; 4 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 5 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.TopicCheck/NetCoreMQTTExampleCluster.TopicCheck.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | latest 7 | enable 8 | NU1803 9 | true 10 | all 11 | true 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.TopicCheck/TopicChecker.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A class to test the topics validity. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.TopicCheck; 11 | 12 | /// 13 | /// A class to test the topics validity. 14 | /// 15 | public static class TopicChecker 16 | { 17 | /// 18 | /// Does a regex check on the topics. 19 | /// 20 | /// The allowed topic. 21 | /// The topic. 22 | /// true if the topic is valid, false if not. 23 | public static bool Regex(string allowedTopic, string topic) 24 | { 25 | // Check if the topics match directly 26 | if (allowedTopic == topic) 27 | { 28 | return true; 29 | } 30 | 31 | // Check if there is more than one cross in the topic 32 | var crossCountTopic = topic.Count(c => c == '#'); 33 | if (crossCountTopic > 1) 34 | { 35 | return false; 36 | } 37 | 38 | // If the cross count is 1 in the topic 39 | if (crossCountTopic == 1) 40 | { 41 | // Check if the cross is the last char in the topic 42 | var index = topic.IndexOf("#", StringComparison.Ordinal); 43 | 44 | if (index != topic.Length - 1) 45 | { 46 | return false; 47 | } 48 | } 49 | 50 | // Else do a regex replace 51 | var realTopicRegex = allowedTopic.Replace(@"/", @"\/").Replace( 52 | "+", 53 | @"[!""$%&'()*,\-.0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz{\|}~¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ]*") 54 | .Replace( 55 | "#", 56 | @"[!""$%&'()*+#,\-.\/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz{\|}~¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ]*"); 57 | 58 | var regex = new Regex(realTopicRegex); 59 | var matches = regex.Matches(topic); 60 | 61 | return matches.ToList().Any(match => match.Value == topic); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Validation.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System.Text; 3 | 4 | global using Microsoft.AspNetCore.Identity; 5 | global using Microsoft.Extensions.Caching.Memory; 6 | global using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | global using MQTTnet; 9 | global using MQTTnet.Packets; 10 | global using MQTTnet.Protocol; 11 | 12 | global using NetCoreMQTTExampleCluster.Grains.Interfaces; 13 | global using NetCoreMQTTExampleCluster.Storage.Data; 14 | global using NetCoreMQTTExampleCluster.Storage.Repositories.Interfaces; 15 | global using NetCoreMQTTExampleCluster.Validation.Tests.Helper; 16 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 17 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Validation.Tests/NetCoreMQTTExampleCluster.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | latest 7 | enable 8 | NU1803 9 | true 10 | all 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Validation/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using Microsoft.AspNetCore.Identity; 3 | global using Microsoft.Extensions.Caching.Memory; 4 | 5 | global using MQTTnet.Server; 6 | 7 | global using NetCoreMQTTExampleCluster.Grains.Interfaces; 8 | global using NetCoreMQTTExampleCluster.Models.Extensions; 9 | global using NetCoreMQTTExampleCluster.TopicCheck; 10 | global using NetCoreMQTTExampleCluster.Storage.Data; 11 | 12 | global using Serilog; 13 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. 14 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Validation/IMqttValidator.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // Copyright (c) All rights reserved. 4 | // 5 | // 6 | // A interface to validate the different MQTT contexts. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace NetCoreMQTTExampleCluster.Validation; 11 | 12 | /// 13 | /// A interface to validate the different MQTT contexts. 14 | /// 15 | public interface IMqttValidator 16 | { 17 | /// 18 | /// Validates the connection. 19 | /// 20 | /// The context. 21 | /// The MQTT user. 22 | /// The password hasher. 23 | /// A value indicating whether the connection is accepted or not. 24 | bool ValidateConnection( 25 | SimpleValidatingConnectionEventArgs context, 26 | MqttUser mqttUser, 27 | IPasswordHasher passwordHasher); 28 | 29 | /// 30 | /// Validates the message publication. 31 | /// 32 | /// The context. 33 | /// The blacklist. 34 | /// The whitelist. 35 | /// The MQTT user. 36 | /// The data limit cache for the month. 37 | /// The client identifier prefixes. 38 | /// A value indicating whether the published message is accepted or not. 39 | bool ValidatePublish( 40 | SimpleInterceptingPublishEventArgs context, 41 | List blacklist, 42 | List whitelist, 43 | MqttUser mqttUser, 44 | IMemoryCache dataLimitCacheMonth, 45 | List clientIdPrefixes); 46 | 47 | /// 48 | /// Validates the subscription. 49 | /// 50 | /// The context. 51 | /// The blacklist. 52 | /// The whitelist. 53 | /// The MQTT user. 54 | /// The client identifier prefixes. 55 | /// A value indicating whether the subscription is accepted or not. 56 | bool ValidateSubscription( 57 | SimpleInterceptingSubscriptionEventArgs context, 58 | List blacklist, 59 | List whitelist, 60 | MqttUser mqttUser, 61 | List clientIdPrefixes); 62 | } 63 | -------------------------------------------------------------------------------- /src/NetCoreMQTTExampleCluster.Validation/NetCoreMQTTExampleCluster.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | latest 7 | enable 8 | NU1803 9 | true 10 | all 11 | true 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | --------------------------------------------------------------------------------