├── .gitattributes ├── .github └── workflows │ └── Deploy to NuGet.yml ├── .gitignore ├── LICENSE ├── MQTTnet.EventBus.sln ├── README.md ├── samples ├── MQTTnet.EventBus.AspNetCoreApi_Integration │ ├── Controllers │ │ └── ValuesController.cs │ ├── IntegrationEventHandler.cs │ ├── MQTTnet.EventBus.AspNetCoreApi_Integration.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── MQTTnet.EventBus.ConfigurationApp │ ├── MQTTnet.EventBus.ConfigurationApp.csproj │ ├── NodeState │ │ ├── ChangeNodeState.cs │ │ ├── ChangeNodeStateConverter.cs │ │ └── ChangeNodeStateTopicEntity.cs │ └── Program.cs ├── MQTTnet.EventBus.Listener_ConsoleApp │ ├── MQTTnet.EventBus.Listener_ConsoleApp.csproj │ └── Program.cs └── MQTTnet.EventBus.Publisher_ConsoleApp │ ├── MQTTnet.EventBus.Publisher_ConsoleApp.csproj │ └── Program.cs ├── src ├── Extensions │ ├── MQTTnet.EventBus.Microsoft.Extensions.Logging │ │ ├── MQTTnet.EventBus.Microsoft.Extensions.Logging.csproj │ │ ├── MicrosoftEventBusLogger .cs │ │ └── MicrosoftLoggerOptionsBuilderExtensions.cs │ ├── MQTTnet.EventBus.Newtonsoft.Json │ │ ├── MQTTnet.EventBus.Newtonsoft.Json.csproj │ │ ├── NewtonsoftJsonConverter.cs │ │ └── NewtonsoftJsonServiceCollectionExtensions.cs │ └── MQTTnet.EventBus.Serilog │ │ ├── MQTTnet.EventBus.Serilog.csproj │ │ ├── SerilogEventBusLogger.cs │ │ └── SerilogLoggerOptionsBuilderExtensions.cs └── MQTTnet.EventBus │ ├── AsyncEnumerableExtansions.cs │ ├── BusOptions.cs │ ├── ComparersManager.cs │ ├── DependencyInjection │ ├── Builder │ │ ├── IBuilder.cs │ │ ├── IEventMappingBuilder.cs │ │ ├── IEventOptionsBuilder.cs │ │ ├── ILocalServerOptionsBuilder.cs │ │ ├── ILoggerOptionsBuilder.cs │ │ ├── IServicesBuilder.cs │ │ ├── Impl │ │ │ ├── EventMappingBuilder.cs │ │ │ ├── EventOptionsBuilder.cs │ │ │ ├── LocalServerOptionsBuilder.cs │ │ │ └── ServicesBuilder.cs │ │ ├── MessageBuilderInfo.cs │ │ └── ServiceType.cs │ ├── LoggerServiceCollectionExtensions.cs │ └── ServiceCollectionExtensions.cs │ ├── EnumerableExtensions.cs │ ├── EventContext.cs │ ├── EventContextExtensions.cs │ ├── EventOptions.cs │ ├── Exceptions │ ├── EventConfigurationException.cs │ ├── EventException.cs │ └── EventNotFoundException.cs │ ├── IConsumer.cs │ ├── IEventBus.cs │ ├── IEventProvider.cs │ ├── IMqttPersisterConnection.cs │ ├── ISubscriptionsManager.cs │ ├── ITopicComparer.cs │ ├── ITopicPattenBuilder.cs │ ├── ITopicPattern.cs │ ├── Impl │ ├── DefaultMqttPersisterConnection.cs │ ├── EventProvider.cs │ ├── InMemorySubscriptionsManager.cs │ ├── MqttClientConnectionEventArgs.cs │ ├── MqttEventBus.cs │ └── TopicPattenBuilder.cs │ ├── Logger │ ├── IEventBusLogger.cs │ └── IEventBusLogger`.cs │ ├── MQTTnet.EventBus.csproj │ ├── Reflection │ ├── ConsumeMethodInvoker.cs │ ├── IConsumeMethodInvoker.cs │ ├── ReflectionHelper.cs │ └── TopicPatternVisitor.cs │ ├── Serializers │ ├── IEventConverter.cs │ ├── IEventDeserializer.cs │ ├── IEventSerializer.cs │ ├── Text │ │ └── StringConverter.cs │ └── TextConvert.cs │ ├── StaticCache.cs │ └── SubscriptionInfo.cs └── tests └── MQTTnet.EventBus.Tests ├── ConsumeMethodInvokerTest.cs └── MQTTnet.EventBus.Tests.csproj /.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 | -------------------------------------------------------------------------------- /.github/workflows/Deploy to NuGet.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | packageVersion: 7 | description: 'Nuget package version' 8 | required: false 9 | default: '1.0.0' 10 | 11 | env: 12 | PROJECT_PATH: 'MQTTnet.EventBus.sln' 13 | PACKAGE_OUTPUT_DIRECTORY: ${{ github.workspace }}\output 14 | NUGET_SOURCE_URL: 'https://api.nuget.org/v3/index.json' 15 | 16 | jobs: 17 | deploy: 18 | name: 'Deploy' 19 | runs-on: 'windows-latest' 20 | 21 | steps: 22 | - name: 'Checkout' 23 | uses: actions/checkout@v2 24 | 25 | - name: 'Install dotnet' 26 | uses: actions/setup-dotnet@v1 27 | with: 28 | dotnet-version: '3.1.x' 29 | 30 | - name: 'Restore packages' 31 | run: dotnet restore ${{ env.PROJECT_PATH }} 32 | 33 | - name: 'Build project' 34 | run: dotnet build ${{ env.PROJECT_PATH }} --no-restore --configuration Release 35 | 36 | - name: 'Pack command' 37 | id: pack-command 38 | run: echo "::set-output name=PARAMS::--no-restore --no-build --configuration Release -p:PackageVersion=${{ github.event.inputs.packageVersion }} --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }}" 39 | 40 | - name: 'Pack project MQTTnet.EventBus' 41 | run: | 42 | dotnet pack 'src/MQTTnet.EventBus/MQTTnet.EventBus.csproj' ${{ steps.pack-command.outputs.PARAMS }} 43 | dotnet pack 'src/Extensions/MQTTnet.EventBus.Serilog/MQTTnet.EventBus.Serilog.csproj' ${{ steps.pack-command.outputs.PARAMS }} 44 | dotnet pack 'src/Extensions/MQTTnet.EventBus.Microsoft.Extensions.Logging/MQTTnet.EventBus.Microsoft.Extensions.Logging.csproj' ${{ steps.pack-command.outputs.PARAMS }} 45 | dotnet pack 'src/Extensions/MQTTnet.EventBus.Newtonsoft.Json/MQTTnet.EventBus.Newtonsoft.Json.csproj' ${{ steps.pack-command.outputs.PARAMS }} 46 | 47 | - name: Archive NuGet Package MQTTnet.EventBus 48 | uses: actions/upload-artifact@v1 49 | with: 50 | name: Pack GitActionsTestApp 51 | path: ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/ 52 | 53 | - name: 'Push command' 54 | id: push-command 55 | run: echo "::set-output name=PARAMS::--api-key ${{ secrets.NUGET_AUTH_TOKEN }} --source ${{ env.NUGET_SOURCE_URL }}" 56 | 57 | - name: Push to NuGet 58 | run: | 59 | dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/MQTTnet.EventBus.${{ github.event.inputs.packageVersion }}.nupkg ${{ steps.push-command.outputs.PARAMS }} 60 | dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/MQTTnet.EventBus.Serilog.${{ github.event.inputs.packageVersion }}.nupkg ${{ steps.push-command.outputs.PARAMS }} 61 | dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/MQTTnet.EventBus.Microsoft.Extensions.Logging.${{ github.event.inputs.packageVersion }}.nupkg ${{ steps.push-command.outputs.PARAMS }} 62 | dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/MQTTnet.EventBus.Newtonsoft.Json.${{ github.event.inputs.packageVersion }}.nupkg ${{ steps.push-command.outputs.PARAMS }} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Artyom Tonoyan 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. 22 | -------------------------------------------------------------------------------- /MQTTnet.EventBus.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{805378E6-757B-4C68-9EDE-65FBB0CEBECB}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{51468033-A31E-4CE9-8C1A-0710C57C6954}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.EventBus", "src\MQTTnet.EventBus\MQTTnet.EventBus.csproj", "{41A36BF7-A37C-4CB7-8FBC-E65A4C770B7F}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.EventBus.Publisher_ConsoleApp", "samples\MQTTnet.EventBus.Publisher_ConsoleApp\MQTTnet.EventBus.Publisher_ConsoleApp.csproj", "{8971ED33-C061-40A7-86BB-ABDAFA07D197}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.EventBus.Listener_ConsoleApp", "samples\MQTTnet.EventBus.Listener_ConsoleApp\MQTTnet.EventBus.Listener_ConsoleApp.csproj", "{C495C71F-C868-4BC4-BA18-406178CA6B8B}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.EventBus.AspNetCoreApi_Integration", "samples\MQTTnet.EventBus.AspNetCoreApi_Integration\MQTTnet.EventBus.AspNetCoreApi_Integration.csproj", "{0FCC6539-E100-4B2E-AAAE-5884C30F98D4}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{5062D7B6-719C-447C-9306-E5ABEA37EDAE}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.EventBus.Tests", "tests\MQTTnet.EventBus.Tests\MQTTnet.EventBus.Tests.csproj", "{E7DC0041-C144-451D-9D52-B56A453C92D3}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.EventBus.ConfigurationApp", "samples\MQTTnet.EventBus.ConfigurationApp\MQTTnet.EventBus.ConfigurationApp.csproj", "{EBE2F498-DC69-40E5-B033-7270ED25C258}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{9C43141B-6F39-4C71-A0AB-E401CC3D7E99}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.EventBus.Microsoft.Extensions.Logging", "src\Extensions\MQTTnet.EventBus.Microsoft.Extensions.Logging\MQTTnet.EventBus.Microsoft.Extensions.Logging.csproj", "{9287C48F-C1E1-4EB2-8598-AAC627206A4F}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.EventBus.Serilog", "src\Extensions\MQTTnet.EventBus.Serilog\MQTTnet.EventBus.Serilog.csproj", "{8A8980F7-0C59-494F-B633-B324CD04D156}" 29 | EndProject 30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.EventBus.Newtonsoft.Json", "src\Extensions\MQTTnet.EventBus.Newtonsoft.Json\MQTTnet.EventBus.Newtonsoft.Json.csproj", "{5C256731-BE39-46CC-80B7-4D4FFA6A1FA6}" 31 | EndProject 32 | Global 33 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 34 | Debug|Any CPU = Debug|Any CPU 35 | Release|Any CPU = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {41A36BF7-A37C-4CB7-8FBC-E65A4C770B7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {41A36BF7-A37C-4CB7-8FBC-E65A4C770B7F}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {41A36BF7-A37C-4CB7-8FBC-E65A4C770B7F}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {41A36BF7-A37C-4CB7-8FBC-E65A4C770B7F}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {8971ED33-C061-40A7-86BB-ABDAFA07D197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {8971ED33-C061-40A7-86BB-ABDAFA07D197}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {8971ED33-C061-40A7-86BB-ABDAFA07D197}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {8971ED33-C061-40A7-86BB-ABDAFA07D197}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {C495C71F-C868-4BC4-BA18-406178CA6B8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {C495C71F-C868-4BC4-BA18-406178CA6B8B}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {C495C71F-C868-4BC4-BA18-406178CA6B8B}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {C495C71F-C868-4BC4-BA18-406178CA6B8B}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {0FCC6539-E100-4B2E-AAAE-5884C30F98D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {0FCC6539-E100-4B2E-AAAE-5884C30F98D4}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {0FCC6539-E100-4B2E-AAAE-5884C30F98D4}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {0FCC6539-E100-4B2E-AAAE-5884C30F98D4}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {E7DC0041-C144-451D-9D52-B56A453C92D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {E7DC0041-C144-451D-9D52-B56A453C92D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {E7DC0041-C144-451D-9D52-B56A453C92D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {E7DC0041-C144-451D-9D52-B56A453C92D3}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {EBE2F498-DC69-40E5-B033-7270ED25C258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {EBE2F498-DC69-40E5-B033-7270ED25C258}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {EBE2F498-DC69-40E5-B033-7270ED25C258}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {EBE2F498-DC69-40E5-B033-7270ED25C258}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {9287C48F-C1E1-4EB2-8598-AAC627206A4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {9287C48F-C1E1-4EB2-8598-AAC627206A4F}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {9287C48F-C1E1-4EB2-8598-AAC627206A4F}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {9287C48F-C1E1-4EB2-8598-AAC627206A4F}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {8A8980F7-0C59-494F-B633-B324CD04D156}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {8A8980F7-0C59-494F-B633-B324CD04D156}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {8A8980F7-0C59-494F-B633-B324CD04D156}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {8A8980F7-0C59-494F-B633-B324CD04D156}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {5C256731-BE39-46CC-80B7-4D4FFA6A1FA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {5C256731-BE39-46CC-80B7-4D4FFA6A1FA6}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {5C256731-BE39-46CC-80B7-4D4FFA6A1FA6}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {5C256731-BE39-46CC-80B7-4D4FFA6A1FA6}.Release|Any CPU.Build.0 = Release|Any CPU 74 | EndGlobalSection 75 | GlobalSection(SolutionProperties) = preSolution 76 | HideSolutionNode = FALSE 77 | EndGlobalSection 78 | GlobalSection(NestedProjects) = preSolution 79 | {41A36BF7-A37C-4CB7-8FBC-E65A4C770B7F} = {805378E6-757B-4C68-9EDE-65FBB0CEBECB} 80 | {8971ED33-C061-40A7-86BB-ABDAFA07D197} = {51468033-A31E-4CE9-8C1A-0710C57C6954} 81 | {C495C71F-C868-4BC4-BA18-406178CA6B8B} = {51468033-A31E-4CE9-8C1A-0710C57C6954} 82 | {0FCC6539-E100-4B2E-AAAE-5884C30F98D4} = {51468033-A31E-4CE9-8C1A-0710C57C6954} 83 | {E7DC0041-C144-451D-9D52-B56A453C92D3} = {5062D7B6-719C-447C-9306-E5ABEA37EDAE} 84 | {EBE2F498-DC69-40E5-B033-7270ED25C258} = {51468033-A31E-4CE9-8C1A-0710C57C6954} 85 | {9C43141B-6F39-4C71-A0AB-E401CC3D7E99} = {805378E6-757B-4C68-9EDE-65FBB0CEBECB} 86 | {9287C48F-C1E1-4EB2-8598-AAC627206A4F} = {9C43141B-6F39-4C71-A0AB-E401CC3D7E99} 87 | {8A8980F7-0C59-494F-B633-B324CD04D156} = {9C43141B-6F39-4C71-A0AB-E401CC3D7E99} 88 | {5C256731-BE39-46CC-80B7-4D4FFA6A1FA6} = {9C43141B-6F39-4C71-A0AB-E401CC3D7E99} 89 | EndGlobalSection 90 | GlobalSection(ExtensibilityGlobals) = postSolution 91 | SolutionGuid = {57EA32AA-7D49-45F1-9E15-93EC6C026DCB} 92 | EndGlobalSection 93 | EndGlobal 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MQTTnet.EventBus 2 | [![CI](https://github.com/Revalcon/MQTTnet.EventBus/actions/workflows/Deploy%20to%20NuGet.yml/badge.svg?branch=master)](https://github.com/Revalcon/MQTTnet.EventBus/actions/workflows/Deploy%20to%20NuGet.yml) 3 | [![GitHub](https://img.shields.io/github/license/arttonoyan/MQTTnet.EventBus.svg)](https://github.com/arttonoyan/MQTTnet.EventBus/blob/master/LICENSE) 4 | [![Nuget](https://img.shields.io/nuget/v/MQTTnet.EventBus.svg)](https://www.nuget.org/packages/MQTTnet.EventBus/) 5 | [![Nuget](https://img.shields.io/nuget/dt/MQTTnet.EventBus.svg)](https://www.nuget.org/packages/MQTTnet.EventBus/) 6 | 7 | ## Quick Start 8 | In your ASP.NET Core Startup.cs file add the following 9 | ``` csharp 10 | public void ConfigureServices(IServiceCollection services) 11 | { 12 | //... 13 | 14 | var retryCount = 5; 15 | services.AddMqttEventBus(cfg => 16 | { 17 | cfg 18 | .WithClientId("Api") 19 | .WithTcpServer("{Ip Address}", port: 1883); 20 | 21 | }, retryCount); 22 | services.AddTransient(); 23 | } 24 | ``` 25 | An EventHandler is a class that may handle one or more message types. Each message type is defined by the IIntegrationEventHandler interface, where T is the MqttApplicationMessageReceivedEventArgs. 26 | 27 | ```csharp 28 | public class MyEventHandler : IIntegrationEventHandler 29 | { 30 | public Task Handle(MqttApplicationMessageReceivedEventArgs args) 31 | { 32 | //Some action... 33 | return Task.CompletedTask; 34 | } 35 | } 36 | ``` 37 | Then in your application add this extension 38 | ```csharp 39 | public static class ApplicationBuilderExtansions 40 | { 41 | public static IApplicationBuilder UseEventBus(this IApplicationBuilder app, Action action) 42 | { 43 | var eventBus = app.ApplicationServices.GetRequiredService(); 44 | action.Invoke(eventBus); 45 | return app; 46 | } 47 | } 48 | ``` 49 | and use it in your Startup.cs file 50 | ```csharp 51 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 52 | { 53 | //... 54 | 55 | app.UseEventBus(async bus => 56 | { 57 | await bus.SubscribeAsync("MyTopic1"); 58 | }); 59 | } 60 | ``` 61 | ### Injected interfaces 62 | * IEventBus 63 | * IMqttPersisterConnection 64 | * IEventBusSubscriptionsManager 65 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.AspNetCoreApi_Integration/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace MQTTnet.EventBus.AspNetCoreApi_Integration.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class ValuesController : ControllerBase 12 | { 13 | // GET api/values 14 | [HttpGet] 15 | public ActionResult> Get() 16 | { 17 | return new string[] { "value1", "value2" }; 18 | } 19 | 20 | // GET api/values/5 21 | [HttpGet("{id}")] 22 | public ActionResult Get(int id) 23 | { 24 | return "value"; 25 | } 26 | 27 | // POST api/values 28 | [HttpPost] 29 | public void Post([FromBody] string value) 30 | { 31 | } 32 | 33 | // PUT api/values/5 34 | [HttpPut("{id}")] 35 | public void Put(int id, [FromBody] string value) 36 | { 37 | } 38 | 39 | // DELETE api/values/5 40 | [HttpDelete("{id}")] 41 | public void Delete(int id) 42 | { 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.AspNetCoreApi_Integration/IntegrationEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace MQTTnet.EventBus.AspNetCoreApi_Integration 4 | { 5 | public class IntegrationEventHandler : IConsumer 6 | { 7 | public Task Consume(MqttApplicationMessageReceivedEventArgs args) 8 | { 9 | //Some action... 10 | return Task.CompletedTask; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.AspNetCoreApi_Integration/MQTTnet.EventBus.AspNetCoreApi_Integration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.AspNetCoreApi_Integration/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace MQTTnet.EventBus.AspNetCoreApi_Integration 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.AspNetCoreApi_Integration/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:4465", 8 | "sslPort": 44339 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "MQTTnet.EventBus.AspNetCoreApi_Integration": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.AspNetCoreApi_Integration/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Logging; 12 | using Microsoft.Extensions.Options; 13 | 14 | namespace MQTTnet.EventBus.AspNetCoreApi_Integration 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 29 | 30 | services.AddMqttEventBus((host, service) => 31 | { 32 | host 33 | .WithClientId("Api") 34 | .WithTcpServer("{Ip Address}", 1883); 35 | 36 | }); 37 | services.AddTransient(); 38 | } 39 | 40 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 41 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 42 | { 43 | if (env.IsDevelopment()) 44 | { 45 | app.UseDeveloperExceptionPage(); 46 | } 47 | else 48 | { 49 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 50 | app.UseHsts(); 51 | } 52 | 53 | app.UseHttpsRedirection(); 54 | app.UseMvc(); 55 | 56 | app.UseEventBus(async bus => 57 | { 58 | await bus.SubscribeAsync("MyTopic1"); 59 | }); 60 | } 61 | } 62 | 63 | public static class ApplicationBuilderExtansions 64 | { 65 | public static IApplicationBuilder UseEventBus(this IApplicationBuilder app, Action action) 66 | { 67 | var eventBus = app.ApplicationServices.GetRequiredService(); 68 | action.Invoke(eventBus); 69 | return app; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.AspNetCoreApi_Integration/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.AspNetCoreApi_Integration/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.ConfigurationApp/MQTTnet.EventBus.ConfigurationApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.ConfigurationApp/NodeState/ChangeNodeState.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.ConfigurationApp 2 | { 3 | //Open, Close or Refrash NodeState 4 | public class ChangeNodeState 5 | { 6 | public int NodeId { get; set; } 7 | public int ActionType { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.ConfigurationApp/NodeState/ChangeNodeStateConverter.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.EventBus.Serializers; 2 | 3 | namespace MQTTnet.EventBus.ConfigurationApp 4 | { 5 | public class ChangeNodeStateConverter : IEventConverter 6 | { 7 | public ChangeNodeState Deserialize(byte[] body) 8 | { 9 | var data = TextConvert.ToUTF8String(body); 10 | 11 | var strNodeId = data.Substring(0, data.Length - 1); 12 | if (!int.TryParse(strNodeId, out int nodeId)) 13 | return null; 14 | 15 | var strActionType = data.Substring(data.Length - 1); 16 | if (!int.TryParse(strActionType, out int actionType)) 17 | return null; 18 | 19 | return new ChangeNodeState { NodeId = nodeId, ActionType = actionType }; 20 | } 21 | 22 | public byte[] Serialize(ChangeNodeState @event) 23 | => TextConvert.ToUTF8ByteArray($"{@event.NodeId}{@event.ActionType}"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.ConfigurationApp/NodeState/ChangeNodeStateTopicEntity.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.ConfigurationApp 2 | { 3 | public class ChangeNodeStateTopicEntity : ITopicPattern 4 | { 5 | public string Territory { get; set; } 6 | public string Server { get; set; } = "Server1"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.ConfigurationApp/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using MQTTnet.Client.Options; 3 | using MQTTnet.EventBus.Serializers; 4 | using Serilog; 5 | using System; 6 | using System.Threading.Tasks; 7 | 8 | namespace MQTTnet.EventBus.ConfigurationApp 9 | { 10 | class Program 11 | { 12 | private static IServiceProvider _provider; 13 | 14 | static async Task Main(string[] args) 15 | { 16 | //var managedOptions = new ManagedMqttClientOptionsBuilder() 17 | // .WithAutoReconnectDelay(TimeSpan.FromSeconds(5)) 18 | // .WithClientOptions(options) 19 | // .Build(); 20 | 21 | try 22 | { 23 | Configure(); 24 | } 25 | catch (Exception ex) 26 | { 27 | 28 | } 29 | 30 | var eventBus = _provider.GetService(); 31 | //await eventBus.PublishAsync(new StatusChanged { Value = 1 }, new StatusChangedTopicInfo { Territory = "garni" }); 32 | //await eventBus.PublishAsync(new StateChanged { Value = 1 }, new StateChangedTopicInfo { Territory = "garni" }); 33 | 34 | //await eventBus.PublishAsync(new MyEvent { Status = 1 }); 35 | //await eventBus.SubscribeAsync("/status/garni/sever/1"); 36 | //await eventBus.SubscribeAsync("#"); 37 | 38 | //await eventBus.SubscribeAsync("/state/Ohanavan/server1/raspberry_client"); 39 | //await eventBus.SubscribeAsync("$SYS/broker/bytes/received"); 40 | 41 | 42 | //await eventBus.SubscribeAsync("$SYS/broker/subscriptions/count"); 43 | 44 | //await eventBus.SubscribeAsync("/state/#"); 45 | //await eventBus.SubscribeAsync("/status/#"); 46 | 47 | //await eventBus.PublishAsync(new StateChanged { Value = 1 }, new StateChangedTopicInfo { Territory = "garni" }); 48 | //await eventBus.PublishAsync(new StatusChanged { Value = 1 }, new StatusChangedTopicInfo { Territory = "garni" }); 49 | 50 | await eventBus.SubscribeAsync(); 51 | await eventBus.SubscribeAsync(); 52 | 53 | //await eventBus.SubscribeAllAsync(); 54 | 55 | //await eventBus.SubscribeAsync(new StateChangedTopicInfo { Territory = "garni", Server = "Server1" }); 56 | //await eventBus.SubscribeAsync(new StatusChangedTopicInfo { Territory = "garni", Server = "Server1" }); 57 | 58 | //await eventBus.PublishAsync(new StateChanged { Value = 10 }); 59 | //var result1 = await eventBus.PublishAsync(new StateChanged { Value = 10 }, new StateChangedTopicInfo { Territory = "garni", Server = "Server1" }); 60 | //var result2 = await eventBus.PublishAsync(new StatusChanged { Value = 10 }, new StatusChangedTopicInfo { Territory = "garni", Server = "Server1" }); 61 | //var code = result.ReasonCode; 62 | 63 | ////await eventBus.PublishAsync(new StatusChanged { }, new StatusChangedTopicInfo { }); 64 | ////await eventBus.PublishAsync(new StateChanged { }, new StateChangedTopicInfo { }); 65 | 66 | //await eventBus.PublishAsync(new MyEvent { Territory = "garni", NodeId = "49", Status = 1 }); 67 | //await eventBus.PublishAsync(new MyEvent { }, "/state/garni/49"); 68 | 69 | Console.ReadLine(); 70 | } 71 | 72 | public static void Configure() 73 | { 74 | var services = new ServiceCollection(); 75 | 76 | services.AddMqttEventBus((host, localServer, service) => 77 | { 78 | host 79 | .WithClientId($"Test {Guid.NewGuid()}") 80 | .WithTcpServer("localhost", port: 1883); 81 | 82 | localServer 83 | .RetryCount(5) 84 | .MaxConcurrentCalls(2); 85 | 86 | service.AddEvenets(eventBuilder => 87 | { 88 | //eventBuilder.AddEventMapping(cfg => 89 | //{ 90 | // cfg.AddConsumer(); 91 | // cfg.UseConverter(); 92 | //}); 93 | eventBuilder.AddEventMapping(cfg => 94 | { 95 | cfg.UseConverter(); 96 | cfg.UseTopicPattern(root: "/field", 97 | ev => $"{ev.Territory}/{ev.Server}"); 98 | cfg.UseMessageBuilder(mb => mb.WithQualityOfServiceLevel(0)); 99 | }); 100 | 101 | eventBuilder.AddEventMapping(cfg => 102 | { 103 | cfg.AddConsumer(); 104 | //cfg.UseConverter(); 105 | cfg.UseTopicPattern(root: "/status", ev => $"/status/{ev.Territory}/{ev.Server}"); 106 | cfg.UseMessageBuilder(mb => mb.WithAtLeastOnceQoS()); 107 | }); 108 | 109 | eventBuilder.AddEventMapping(cfg => 110 | { 111 | cfg.AddConsumer(); 112 | cfg.UseConverter(); 113 | //cfg.UseJsonConverter(); 114 | //cfg.UseTopicPattern("/State/Test"); 115 | //cfg.UseTopicPattern("/State/{Territory}/{Server}/{ClientId}"); 116 | cfg.UseTopicPattern(root: "/state", ev => $"/{ev.Territory}/{ev.Server}/{ev.ClientId}"); 117 | cfg.UseMessageBuilder(builder => builder.WithRetainFlag()); 118 | }); 119 | 120 | //eventBuilder.AddEventMapping(cfg => 121 | //{ 122 | // cfg.AddConsumer(); 123 | // cfg.UseConverter(); 124 | // cfg.UseTopicPattern(ev => $"/State/{ev.Territory}/{ev.NodeId}"); 125 | // cfg.UseMessageBuilder(builder => builder.WithRetainFlag()); 126 | //}); 127 | 128 | //eventBuilder.AddEventMapping(cfg => 129 | //{ 130 | // cfg.AddConsumer(); 131 | // cfg.UseConverter(); 132 | // cfg.UseMessageBuilder(builder => builder.WithRetainFlag()); 133 | //}); 134 | }); 135 | 136 | service.AddLogger(provider => provider.UseSerilog(cfg => 137 | { 138 | cfg.WriteTo.Console( 139 | outputTemplate: "{Timestamp:HH:mm} [{Category} {Level}] ({ThreadId}) {Message}{NewLine}{Exception}"); 140 | })); 141 | }); 142 | 143 | services.AddScoped(); 144 | _provider = services.BuildServiceProvider(); 145 | } 146 | } 147 | 148 | public class Sys { } 149 | public class SysConsumer : IConsumer 150 | { 151 | public Task ConsumeAsync(EventContext context) 152 | { 153 | return Task.CompletedTask; 154 | } 155 | } 156 | 157 | public class SysConverter : IEventConverter 158 | { 159 | public Sys Deserialize(byte[] value) 160 | { 161 | return new Sys(); 162 | } 163 | 164 | public byte[] Serialize(Sys value) 165 | { 166 | return new byte[0]; 167 | } 168 | } 169 | 170 | public class StatusChanged 171 | { 172 | public int Value { get; set; } 173 | } 174 | 175 | public interface IStatusChangedConsumer : IConsumer { } 176 | 177 | public class StatusChangedConsumer : IStatusChangedConsumer 178 | { 179 | public Task ConsumeAsync(EventContext context) 180 | { 181 | try 182 | { 183 | var topicInfo = context.GetTopicInfo(); 184 | Console.WriteLine($"{context.Message.Topic} Status: {context.EventArg.Value}, Territory: {topicInfo.Territory}, Server: {topicInfo.Server}"); 185 | } 186 | catch (Exception ex) 187 | { 188 | Console.WriteLine(ex.Message); 189 | } 190 | return Task.CompletedTask; 191 | } 192 | } 193 | 194 | public class StatusChangedConverter : IEventConverter 195 | { 196 | public StatusChanged Deserialize(byte[] body) 197 | { 198 | var actin = TextConvert.ToUTF8String(body); 199 | if (!int.TryParse(actin, out int value)) 200 | value = -1; 201 | 202 | return new StatusChanged { Value = value }; 203 | } 204 | 205 | public byte[] Serialize(StatusChanged @event) 206 | => TextConvert.ToUTF8ByteArray(Convert.ToString(@event.Value)); 207 | } 208 | 209 | #region MyRegion 210 | 211 | public class MyConverter : IEventConverter 212 | { 213 | public MyEvent Deserialize(byte[] value) 214 | { 215 | string data = TextConvert.ToUTF8String(value); 216 | if (!int.TryParse(data, out int status)) 217 | status = -1; 218 | 219 | return new MyEvent { Status = status }; 220 | } 221 | 222 | public byte[] Serialize(MyEvent value) 223 | { 224 | string data = string.Format($"{value.Status}{value.Status}"); 225 | return TextConvert.ToUTF8ByteArray(data); 226 | } 227 | } 228 | 229 | public class AllEvents { } 230 | public class AllEventConverter : IEventConverter 231 | { 232 | public AllEvents Deserialize(byte[] value) 233 | { 234 | return new AllEvents(); 235 | } 236 | 237 | public byte[] Serialize(AllEvents value) 238 | { 239 | return new byte[0]; 240 | } 241 | } 242 | 243 | public class AllEventsConsumer : IConsumer 244 | { 245 | private static object _locker = new object(); 246 | private static string topics = ""; 247 | private static int index = 1; 248 | public Task ConsumeAsync(EventContext context) 249 | { 250 | lock (_locker) 251 | { 252 | Console.WriteLine($"{index}: {context.Message.Topic}"); 253 | topics += context.Message.Topic + Environment.NewLine; 254 | //System.IO.File.WriteAllText("topics.txt", topics); 255 | index++; 256 | } 257 | return Task.CompletedTask; 258 | } 259 | } 260 | 261 | public class MyEvent 262 | { 263 | public string NodeId { get; set; } 264 | public string Territory { get; set; } 265 | public int Status { get; set; } 266 | } 267 | 268 | public interface IMyConsumer : IConsumer { } 269 | 270 | public class MyConsumer : IMyConsumer 271 | { 272 | public Task ConsumeAsync(EventContext context) 273 | { 274 | try 275 | { 276 | var status = context.EventArg.Status; 277 | Console.WriteLine($"{context.Message.Topic} Status: {status}, Territory: {context.EventArg.Territory}, NodeId: {context.EventArg.NodeId}"); 278 | } 279 | catch (Exception ex) 280 | { 281 | Console.WriteLine(ex.Message); 282 | } 283 | return Task.CompletedTask; 284 | } 285 | } 286 | 287 | public class StateChangedTopicInfo : ITopicPattern 288 | { 289 | public string Territory { get; set; } 290 | public string Server { get; set; } = "Server1"; 291 | public string ClientId => $"{Territory}_rpi"; 292 | 293 | public Func Create() 294 | => ev => $"/state/{ev.Territory}/{ev.Server}/{ev.ClientId}"; 295 | } 296 | 297 | public class StatusChangedTopicInfo : ITopicPattern 298 | { 299 | public string Territory { get; set; } 300 | public string Server { get; set; } = "Server1"; 301 | } 302 | 303 | public class StateChanged 304 | { 305 | //public string Territory { get; set; } 306 | //public string Server { get; set; } = "Server1"; 307 | //public string ClientId => $"{Territory}_rpi"; 308 | public int Value { get; set; } 309 | } 310 | 311 | public class StateChangedConsuer : IConsumer 312 | { 313 | private static readonly object _lockObject = new object(); 314 | public Task ConsumeAsync(EventContext context) 315 | { 316 | //await Task.Delay(4000); 317 | 318 | lock (_lockObject) 319 | { 320 | Console.ForegroundColor = ConsoleColor.Red; 321 | Console.Write($"------------"); 322 | Console.ResetColor(); 323 | } 324 | 325 | var info = context.GetTopicInfo(); 326 | string aaa = context.GetTopicEntity("Territory"); 327 | if (info == null) 328 | { 329 | Console.WriteLine($"{context.Message.Topic} State: {context.EventArg.Value}"); 330 | } 331 | else 332 | { 333 | //Console.WriteLine($"{context.Message.Topic} State: {context.EventArg.Value}, Territory: {context.EventArg.Territory}, ClientId: {context.EventArg.ClientId}"); 334 | Console.WriteLine($"{context.Message.Topic} State: {context.EventArg.Value}, Territory: {info.Territory}, ClientId: {info.ClientId}"); 335 | } 336 | return Task.CompletedTask; 337 | } 338 | } 339 | 340 | public class StateChangedConverter : IEventConverter 341 | { 342 | public StateChanged Deserialize(byte[] body) 343 | { 344 | string data = TextConvert.ToUTF8String(body); 345 | if (!int.TryParse(data, out int value)) 346 | value = -1; 347 | 348 | return new StateChanged { Value = value }; 349 | } 350 | 351 | public byte[] Serialize(StateChanged @event) 352 | { 353 | string data = @event.Value.ToString(); 354 | return TextConvert.ToUTF8ByteArray(data); 355 | } 356 | } 357 | 358 | #endregion 359 | } 360 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.Listener_ConsoleApp/MQTTnet.EventBus.Listener_ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.Listener_ConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Client.Options; 3 | using MQTTnet.Protocol; 4 | using System; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MQTTnet.EventBus.Listener_ConsoleApp 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) => MainAsync().GetAwaiter().GetResult(); 13 | 14 | private static async Task MainAsync() 15 | { 16 | Console.Title = "Listener"; 17 | 18 | var mqttClient = new MqttFactory().CreateMqttClient(); 19 | 20 | var options = new MqttClientOptionsBuilder() 21 | .WithClientId("Console_App_Listener") 22 | .WithTcpServer("{Ip Address}", 1883) 23 | .Build(); 24 | 25 | await mqttClient.ConnectAsync(options); 26 | 27 | Console.Write("Subscribe To Topic: "); 28 | string topic = Console.ReadLine(); 29 | 30 | await mqttClient.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce); 31 | 32 | mqttClient.UseApplicationMessageReceivedHandler(e => 33 | { 34 | Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); 35 | Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); 36 | Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}"); 37 | Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); 38 | Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); 39 | Console.WriteLine(); 40 | }); 41 | 42 | Console.ReadLine(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.Publisher_ConsoleApp/MQTTnet.EventBus.Publisher_ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/MQTTnet.EventBus.Publisher_ConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet; 2 | using MQTTnet.Client; 3 | using MQTTnet.Client.Options; 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | namespace MQTTnet.EventBus.Publisher_ConsoleApp 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) => MainAsync().GetAwaiter().GetResult(); 12 | 13 | private static async Task MainAsync() 14 | { 15 | Console.Title = "Publisher"; 16 | 17 | var mqttClient = new MqttFactory().CreateMqttClient(); 18 | 19 | var options = new MqttClientOptionsBuilder() 20 | .WithClientId("Console_App_Publisher") 21 | .WithTcpServer("{Ip Address}", 1883) 22 | .Build(); 23 | 24 | mqttClient.UseDisconnectedHandler(async e => 25 | { 26 | await mqttClient.ConnectAsync(options); 27 | }); 28 | 29 | await mqttClient.ConnectAsync(options); 30 | 31 | Console.WriteLine("MyTopic1"); 32 | while (true) 33 | { 34 | Console.Write("message: "); 35 | string messageText = Console.ReadLine(); 36 | 37 | var message = new MqttApplicationMessageBuilder() 38 | .WithTopic("MyTopic1") 39 | .WithPayload(messageText) 40 | .WithExactlyOnceQoS() 41 | .WithRetainFlag() 42 | .Build(); 43 | 44 | try 45 | { 46 | await mqttClient.PublishAsync(message); 47 | } 48 | catch (Exception ex) 49 | { 50 | Console.WriteLine(ex.Message); 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Extensions/MQTTnet.EventBus.Microsoft.Extensions.Logging/MQTTnet.EventBus.Microsoft.Extensions.Logging.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | Artyom Tonoyan 6 | Revalcon LLC 7 | @ 2019 Revalcon LLC 8 | 9 | https://github.com/Revalcon/MQTTnet.EventBus 10 | https://github.com/Revalcon/MQTTnet.EventBus 11 | Mqtt MqttNet EventBus Event MQ Logging Microsoft.Extensions.Logging 12 | true 13 | true 14 | 2.0.0 15 | Apache-2.0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Extensions/MQTTnet.EventBus.Microsoft.Extensions.Logging/MicrosoftEventBusLogger .cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Logging; 4 | using MQTTnet.Diagnostics; 5 | using MQTTnet.EventBus.Logger; 6 | 7 | namespace MQTTnet.EventBus.Microsoft.Extensions.Logging 8 | { 9 | public class MicrosoftEventBusLogger : IEventBusLogger 10 | { 11 | protected readonly ILogger _logger; 12 | private readonly ILoggerFactory _loggerFactory; 13 | private static readonly IDictionary _logLevelMap; 14 | 15 | static MicrosoftEventBusLogger() 16 | { 17 | _logLevelMap = new Dictionary 18 | { 19 | { MqttNetLogLevel.Verbose, LogLevel.Trace }, 20 | { MqttNetLogLevel.Info, LogLevel.Information }, 21 | { MqttNetLogLevel.Warning, LogLevel.Warning }, 22 | { MqttNetLogLevel.Error, LogLevel.Error } 23 | }; 24 | } 25 | 26 | public MicrosoftEventBusLogger(ILoggerFactory loggerFactory, ILogger logger) 27 | { 28 | _loggerFactory = loggerFactory; 29 | _logger = logger; 30 | } 31 | 32 | public IEventBusLogger CreateLogger(string categoryName) 33 | => new MicrosoftEventBusLogger(_loggerFactory, _loggerFactory.CreateLogger(categoryName)); 34 | 35 | public IMqttNetScopedLogger CreateScopedLogger(string source) 36 | => new MicrosoftEventBusLogger(_loggerFactory, _loggerFactory.CreateLogger(source)); 37 | 38 | public IEventBusLogger CreateLogger() 39 | => new MicrosoftEventBusLogger(_loggerFactory, _loggerFactory.CreateLogger()); 40 | 41 | public void LogTrace(string message) 42 | => _logger.LogTrace(message); 43 | 44 | public void LogInformation(string message) 45 | => _logger.LogInformation(message); 46 | 47 | public void LogWarning(string message) 48 | => _logger.LogWarning(message); 49 | 50 | public void LogWarning(Exception ex, string message) 51 | => _logger.LogWarning(ex, message); 52 | 53 | public void LogError(Exception ex, string message) 54 | => _logger.LogError(ex, message); 55 | 56 | public void Publish(MqttNetLogLevel logLevel, string message, object[] parameters, Exception exception) 57 | => _logger.Log(_logLevelMap[logLevel], exception, message, parameters); 58 | } 59 | 60 | public class MicrosoftEventBusLogger : MicrosoftEventBusLogger, IEventBusLogger 61 | { 62 | public MicrosoftEventBusLogger(ILoggerFactory loggerFactory, ILogger logger) 63 | : base(loggerFactory, logger) 64 | { } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Extensions/MQTTnet.EventBus.Microsoft.Extensions.Logging/MicrosoftLoggerOptionsBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using MQTTnet.EventBus.DependencyInjection; 4 | using MQTTnet.EventBus.DependencyInjection.Builder; 5 | using MQTTnet.EventBus.Microsoft.Extensions.Logging; 6 | 7 | namespace Microsoft.Extensions.DependencyInjection 8 | { 9 | public static class MicrosoftLoggerOptionsBuilderExtensions 10 | { 11 | public static IServicesBuilder AddMicrosoftExtensionsLgger(this IServicesBuilder builder) 12 | => builder.AddLogger(p => p.UseMicrosoftExtension()); 13 | 14 | public static IServicesBuilder AddMicrosoftExtensionsLgger(this IServicesBuilder builder, Action configure) 15 | => builder.AddLogger(p => p.UseMicrosoftExtension(configure)); 16 | 17 | public static ILoggerOptionsBuilder UseMicrosoftExtension(this ILoggerOptionsBuilder builder) 18 | { 19 | builder.Services 20 | .AddLogging() 21 | .AddEventBusLogger(); 22 | return builder; 23 | } 24 | 25 | public static ILoggerOptionsBuilder UseMicrosoftExtension(this ILoggerOptionsBuilder builder, Action configure) 26 | { 27 | builder.Services 28 | .AddLogging(configure) 29 | .AddEventBusLogger(); 30 | return builder; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Extensions/MQTTnet.EventBus.Newtonsoft.Json/MQTTnet.EventBus.Newtonsoft.Json.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | Artyom Tonoyan 6 | Revalcon LLC 7 | @ 2019 Revalcon LLC 8 | 9 | https://github.com/Revalcon/MQTTnet.EventBus 10 | https://github.com/Revalcon/MQTTnet.EventBus 11 | Mqtt MqttNet EventBus Event MQ Newtonsoft Json 12 | true 13 | true 14 | 2.0.0 15 | Apache-2.0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Extensions/MQTTnet.EventBus.Newtonsoft.Json/NewtonsoftJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.EventBus.Serializers; 2 | using Newtonsoft.Json; 3 | 4 | namespace MQTTnet.EventBus.Newtonsoft.Json 5 | { 6 | public class NewtonsoftJsonConverter : IEventConverter 7 | { 8 | public T Deserialize(byte[] value) 9 | { 10 | string data = TextConvert.ToUTF8String(value); 11 | return JsonConvert.DeserializeObject(data); 12 | } 13 | 14 | public byte[] Serialize(T value) 15 | { 16 | var data = JsonConvert.SerializeObject(value); 17 | return TextConvert.ToUTF8ByteArray(data); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Extensions/MQTTnet.EventBus.Newtonsoft.Json/NewtonsoftJsonServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.EventBus.DependencyInjection.Builder; 2 | using MQTTnet.EventBus.Newtonsoft.Json; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection 5 | { 6 | public static class NewtonsoftJsonServiceCollectionExtensions 7 | { 8 | public static IEventMappingBuilder UseJsonConverter(this IEventMappingBuilder builder) 9 | { 10 | builder.UseConverter>(); 11 | return builder; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Extensions/MQTTnet.EventBus.Serilog/MQTTnet.EventBus.Serilog.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | Artyom Tonoyan 6 | Revalcon LLC 7 | @ 2019 Revalcon LLC 8 | 9 | https://github.com/Revalcon/MQTTnet.EventBus 10 | https://github.com/Revalcon/MQTTnet.EventBus 11 | Mqtt MqttNet EventBus Event MQ Logging Serilog 12 | true 13 | true 14 | 2.0.0 15 | Apache-2.0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Extensions/MQTTnet.EventBus.Serilog/SerilogEventBusLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using MQTTnet.EventBus.Logger; 4 | using MQTTnet.Diagnostics; 5 | using System.Collections.Generic; 6 | using Serilog.Events; 7 | 8 | namespace MQTTnet.EventBus.Serilog 9 | { 10 | public class SerilogEventBusLogger : IEventBusLogger 11 | { 12 | public const string CategoryTemplateName = "Category"; 13 | protected readonly ILogger _logger; 14 | private static readonly IDictionary _logLevelMap; 15 | 16 | static SerilogEventBusLogger() 17 | { 18 | _logLevelMap = new Dictionary 19 | { 20 | { MqttNetLogLevel.Verbose, LogEventLevel.Verbose }, 21 | { MqttNetLogLevel.Info, LogEventLevel.Information }, 22 | { MqttNetLogLevel.Warning, LogEventLevel.Warning }, 23 | { MqttNetLogLevel.Error, LogEventLevel.Error } 24 | }; 25 | } 26 | 27 | public SerilogEventBusLogger(ILogger logger) 28 | { 29 | _logger = logger; 30 | } 31 | 32 | public IEventBusLogger CreateLogger(string categoryName) 33 | => new SerilogEventBusLogger(_logger.ForContext(CategoryTemplateName, categoryName)); 34 | 35 | public IEventBusLogger CreateLogger() 36 | => new SerilogEventBusLogger(_logger.ForContext(CategoryTemplateName, typeof(TCategoryName).FullName)); 37 | 38 | public IMqttNetScopedLogger CreateScopedLogger(string source) 39 | => CreateLogger(source); 40 | 41 | public void LogTrace(string message) 42 | => _logger.Verbose(message); 43 | 44 | public void LogInformation(string message) 45 | => _logger.Information(message); 46 | 47 | public void LogWarning(string message) 48 | => _logger.Warning(message); 49 | 50 | public void LogWarning(Exception ex, string message) 51 | => _logger.Information(ex, message); 52 | 53 | public void LogError(Exception ex, string message) 54 | => _logger.Error(ex, message); 55 | 56 | public void Publish(MqttNetLogLevel logLevel, string message, object[] parameters, Exception exception) 57 | => _logger.Write(_logLevelMap[logLevel], exception, message, parameters); 58 | } 59 | 60 | public class SerilogEventBusLogger : SerilogEventBusLogger, IEventBusLogger 61 | { 62 | public SerilogEventBusLogger(ILogger logger) 63 | : base(logger.ForContext(CategoryTemplateName, typeof(TCategoryName).FullName)) 64 | { } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Extensions/MQTTnet.EventBus.Serilog/SerilogLoggerOptionsBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MQTTnet.EventBus.Serilog; 3 | using MQTTnet.EventBus.DependencyInjection; 4 | using MQTTnet.EventBus.DependencyInjection.Builder; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Serilog 8 | { 9 | public static class SerilogLoggerOptionsBuilderExtensions 10 | { 11 | public static IServicesBuilder AddSerilog(this IServicesBuilder builder) 12 | => builder.AddLogger(p => p.UseSerilog()); 13 | 14 | public static IServicesBuilder AddSerilog(this IServicesBuilder builder, Action configure) 15 | => builder.AddLogger(p => p.UseSerilog(configure)); 16 | 17 | public static IServicesBuilder AddSerilog(this IServicesBuilder builder, ILogger logger) 18 | => builder.AddLogger(p => p.UseSerilog(logger)); 19 | 20 | public static ILoggerOptionsBuilder UseSerilog(this ILoggerOptionsBuilder builder, Action configure) 21 | { 22 | if (builder == null) throw new ArgumentNullException(nameof(builder)); 23 | 24 | var cfg = new LoggerConfiguration(); 25 | configure.Invoke(cfg); 26 | return UseSerilog(builder, cfg.CreateLogger()); 27 | } 28 | 29 | public static ILoggerOptionsBuilder UseSerilog(this ILoggerOptionsBuilder builder) 30 | => UseSerilog(builder, cfg => cfg.CreateLogger()); 31 | 32 | public static ILoggerOptionsBuilder UseSerilog(this ILoggerOptionsBuilder builder, ILogger logger) 33 | { 34 | if (builder == null) throw new ArgumentNullException(nameof(builder)); 35 | 36 | Log.Logger = logger; 37 | builder.Services 38 | .AddSingleton(logger) 39 | .AddEventBusLogger(); 40 | return builder; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/AsyncEnumerableExtansions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace MQTTnet.EventBus 5 | { 6 | public static class AsyncEnumerableExtansions 7 | { 8 | public static async Task> ToListAsync(this IAsyncEnumerable source) 9 | { 10 | var list = new List(); 11 | await foreach (var item in source) 12 | list.Add(item); 13 | return list; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/BusOptions.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus 2 | { 3 | public class BusOptions 4 | { 5 | public int RetryCount { get; set; } = 5; 6 | public byte MaxConcurrentCalls { get; set; } = 10; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/ComparersManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MQTTnet.EventBus 4 | { 5 | public static class ComparersManager 6 | { 7 | public static IEqualityComparer SubscriptionInfo { get; } = new SubscriptionInfoEqualityComparer(); 8 | public static IEqualityComparer EventOptions { get; } = new EventOptionsEqualityComparer(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/IBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.DependencyInjection.Builder 2 | { 3 | public interface IBuilder 4 | { 5 | T Build(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/IEventMappingBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace MQTTnet.EventBus.DependencyInjection.Builder 5 | { 6 | public interface IEventMappingBuilder 7 | { 8 | IEventMappingBuilder AddConsumer() where TConsumer : IConsumer; 9 | IEventMappingBuilder UseConverter() where TConverter : Serializers.IEventConverter; 10 | IEventMappingBuilder UseTopicPattern(string pattern); 11 | IEventMappingBuilder UseTopicPattern(string root, string pattern); 12 | IEventMappingBuilder UseTopicPattern(string root, Expression> pattern) 13 | where TTopicInfo : ITopicPattern; 14 | IEventMappingBuilder UseMessageBuilder(Action messageBuilderConfigurator); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/IEventOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MQTTnet.EventBus.DependencyInjection.Builder 4 | { 5 | public interface IEventOptionsBuilder 6 | { 7 | IEventOptionsBuilder AddEventMapping(string eventName, Action> mappingConfigurator); 8 | } 9 | } 10 | 11 | namespace Microsoft.Extensions.DependencyInjection 12 | { 13 | using MQTTnet.EventBus.DependencyInjection.Builder; 14 | 15 | public static class EventOptionsBuilderExtensions 16 | { 17 | public static IEventOptionsBuilder AddEventMapping(this IEventOptionsBuilder builder, Action> mappingConfigurator) 18 | => builder.AddEventMapping(typeof(TEvent).Name, mappingConfigurator); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/ILocalServerOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.DependencyInjection.Builder 2 | { 3 | public interface ILocalServerOptionsBuilder : IBuilder 4 | { 5 | ILocalServerOptionsBuilder RetryCount(int value); 6 | ILocalServerOptionsBuilder MaxConcurrentCalls(byte value); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/ILoggerOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.DependencyInjection.Builder 2 | { 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | public interface ILoggerOptionsBuilder 6 | { 7 | IServiceCollection Services { get; } 8 | } 9 | 10 | public class LoggerOptionsBuilder : ILoggerOptionsBuilder 11 | { 12 | public LoggerOptionsBuilder(IServiceCollection services) 13 | { 14 | Services = services; 15 | } 16 | 17 | public IServiceCollection Services { get; } 18 | 19 | } 20 | } 21 | 22 | namespace Microsoft.Extensions.DependencyInjection 23 | { 24 | using MQTTnet.Diagnostics; 25 | using MQTTnet.EventBus.DependencyInjection; 26 | using MQTTnet.EventBus.DependencyInjection.Builder; 27 | using MQTTnet.EventBus.Logger; 28 | 29 | public static class InnerLoggerOptionsBuilderExtensions 30 | { 31 | public static ILoggerOptionsBuilder UseInnerLogger(this ILoggerOptionsBuilder builder) 32 | { 33 | builder.Services 34 | .AddSingleton() 35 | .AddEventBusLogger(); 36 | return builder; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/IServicesBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace MQTTnet.EventBus.DependencyInjection.Builder 5 | { 6 | public interface IServicesBuilder 7 | { 8 | IServicesBuilder AddServices(Action addServices, ServiceType serviceType = ServiceType.Custom); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/Impl/EventMappingBuilder.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.EventBus.Reflection; 2 | using MQTTnet.EventBus.Serializers; 3 | using System; 4 | using System.Linq.Expressions; 5 | 6 | namespace MQTTnet.EventBus.DependencyInjection.Builder.Impl 7 | { 8 | public class EventMappingBuilder : IEventMappingBuilder 9 | { 10 | private readonly EventOptions _eventOptions; 11 | 12 | public EventMappingBuilder(EventOptions eventOptions) 13 | { 14 | _eventOptions = eventOptions; 15 | } 16 | 17 | public IEventMappingBuilder AddConsumer() 18 | where TConsumer : IConsumer 19 | { 20 | _eventOptions.ConsumerType = typeof(TConsumer); 21 | return this; 22 | } 23 | 24 | public IEventMappingBuilder UseConverter() 25 | where TConverter : IEventConverter 26 | { 27 | _eventOptions.ConverterType = typeof(TConverter); 28 | return this; 29 | } 30 | 31 | public IEventMappingBuilder UseMessageBuilder(Action messageBuilderConfigurator) 32 | { 33 | _eventOptions.MessageCreater = messageBuilderConfigurator; 34 | return this; 35 | } 36 | 37 | public IEventMappingBuilder UseTopicPattern(string pattern) => 38 | UseTopicPattern(pattern, pattern); 39 | 40 | public IEventMappingBuilder UseTopicPattern(string root, string pattern) 41 | { 42 | _eventOptions.Topic.Root = root; 43 | _eventOptions.Topic.Pattern = pattern; 44 | return this; 45 | } 46 | 47 | public IEventMappingBuilder UseTopicPattern(string root, Expression> patternExp) 48 | where TPatternType : ITopicPattern 49 | { 50 | string pattern = ReflectionHelper.CreateTopicPattern(patternExp); 51 | _eventOptions.Topic.PatternType = typeof(TPatternType); 52 | 53 | if (!string.IsNullOrWhiteSpace(root)) 54 | { 55 | if (!pattern.StartsWith(root)) 56 | { 57 | root = root.TrimEnd('/'); 58 | pattern = pattern.TrimStart('/'); 59 | pattern = $"{root}/{pattern}"; 60 | } 61 | } 62 | 63 | return UseTopicPattern(root, pattern); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/Impl/EventOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MQTTnet.EventBus.DependencyInjection.Builder.Impl 5 | { 6 | public class EventOptionsBuilder : IEventOptionsBuilder, IBuilder> 7 | { 8 | private readonly HashSet _consumerOptions; 9 | 10 | public EventOptionsBuilder() 11 | { 12 | _consumerOptions = new HashSet(ComparersManager.EventOptions); 13 | } 14 | 15 | public IEventOptionsBuilder AddEventMapping(string eventName, Action> mappingConfigurator) 16 | { 17 | var options = new EventOptions(eventName) 18 | { 19 | EventType = typeof(TEvent) 20 | }; 21 | 22 | var mappingBuilder = new EventMappingBuilder(options); 23 | mappingConfigurator.Invoke(mappingBuilder); 24 | 25 | _consumerOptions.Add(options); 26 | 27 | return this; 28 | } 29 | 30 | public HashSet Build() => _consumerOptions; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/Impl/LocalServerOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.DependencyInjection.Builder.Impl 2 | { 3 | public class LocalServerOptionsBuilder : ILocalServerOptionsBuilder 4 | { 5 | private readonly BusOptions _busOptions; 6 | 7 | public LocalServerOptionsBuilder() : this(new BusOptions()) { } 8 | 9 | public LocalServerOptionsBuilder(BusOptions busOptions) 10 | { 11 | _busOptions = busOptions; 12 | } 13 | 14 | public ILocalServerOptionsBuilder MaxConcurrentCalls(byte value) 15 | { 16 | _busOptions.MaxConcurrentCalls = value; 17 | return this; 18 | } 19 | 20 | public ILocalServerOptionsBuilder RetryCount(int value) 21 | { 22 | _busOptions.RetryCount = value; 23 | return this; 24 | } 25 | 26 | public BusOptions Build() => _busOptions; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/Impl/ServicesBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace MQTTnet.EventBus.DependencyInjection.Builder.Impl 6 | { 7 | public class ServicesBuilder : IServicesBuilder 8 | { 9 | private readonly IServiceCollection _services; 10 | private readonly HashSet _addedServices; 11 | 12 | public ServicesBuilder(IServiceCollection services, HashSet tempreryData) 13 | { 14 | _services = services; 15 | _addedServices = tempreryData; 16 | } 17 | 18 | public IServicesBuilder AddServices(Action addServices, ServiceType serviceType = ServiceType.Custom) 19 | { 20 | _addedServices.Add(serviceType); 21 | addServices.Invoke(_services); 22 | return this; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/MessageBuilderInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MQTTnet.EventBus.DependencyInjection.Builder 4 | { 5 | public class MessageBuilderInfo 6 | { 7 | public Type ConverterType { get; set; } 8 | public Action MessageCreater { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/Builder/ServiceType.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.DependencyInjection.Builder 2 | { 3 | public enum ServiceType : byte 4 | { 5 | Logger, 6 | Event, 7 | Converter, 8 | Custom 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/LoggerServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using MQTTnet.EventBus.Logger; 3 | using System; 4 | 5 | namespace MQTTnet.EventBus.DependencyInjection 6 | { 7 | public static class LoggerServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddEventBusLogger(this IServiceCollection services) 10 | where TLogger : IEventBusLogger 11 | => services.AddEventBusLogger(MakeGenericType()); 12 | 13 | public static IServiceCollection AddEventBusLogger(this IServiceCollection services, Type loggerType) 14 | => services.AddSingleton(typeof(IEventBusLogger<>), loggerType); 15 | 16 | private static Type MakeGenericType() where TLogger : IEventBusLogger 17 | { 18 | var generycSymbol = typeof(IEventBusLogger<>).FullName[typeof(IEventBusLogger).FullName.Length..]; 19 | var loggerType = typeof(TLogger); 20 | return loggerType.Assembly.GetType($"{loggerType.FullName}{generycSymbol}"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/DependencyInjection/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client.Options; 2 | using MQTTnet.EventBus; 3 | using MQTTnet.EventBus.DependencyInjection.Builder; 4 | using MQTTnet.EventBus.DependencyInjection.Builder.Impl; 5 | using MQTTnet.EventBus.Exceptions; 6 | using MQTTnet.EventBus.Impl; 7 | using MQTTnet.EventBus.Logger; 8 | using MQTTnet.EventBus.Reflection; 9 | using MQTTnet.EventBus.Serializers.Text; 10 | using System; 11 | using System.Collections.Generic; 12 | 13 | namespace Microsoft.Extensions.DependencyInjection 14 | { 15 | public static class ServiceCollectionExtensions 16 | { 17 | public static IServicesBuilder AddEvenets(this IServicesBuilder serviceBuilder, Action configurator) => 18 | serviceBuilder.AddServices(services => 19 | { 20 | var eventBuilder = new EventOptionsBuilder(); 21 | configurator.Invoke(eventBuilder); 22 | 23 | var eventOptions = eventBuilder.Build(); 24 | foreach (var o in eventOptions) 25 | { 26 | if (o.ConsumerType != null && !o.ConsumerType.IsInterface) 27 | services.AddScoped(o.ConsumerType); 28 | 29 | if (o.ConverterType == null) 30 | throw new EventConfigurationException(o.EventName, o.EventType, 31 | $"Converter for '{o.EventName}' event not configured, this configuration is necessary."); 32 | 33 | if (!o.ConverterType.IsInterface) 34 | services.AddScoped(o.ConverterType); 35 | } 36 | 37 | services.AddSingleton(serviceProvider => 38 | { 39 | StaticCache.EventProvider = new EventProvider( 40 | serviceProvider, 41 | serviceProvider.GetRequiredService(), 42 | eventOptions, 43 | serviceProvider.GetRequiredService>()); 44 | return StaticCache.EventProvider; 45 | }); 46 | }, ServiceType.Event); 47 | 48 | public static IServiceCollection AddMqttEventBus(this IServiceCollection services, Action configurator) => 49 | services.AddMqttEventBus(null, configurator); 50 | 51 | public static IServiceCollection AddMqttEventBus(this IServiceCollection services, Action configurator) => 52 | services.AddMqttEventBus(configurator, null); 53 | 54 | private static IServiceCollection AddMqttEventBus(this IServiceCollection services, 55 | Action cfgStrategy1, 56 | Action cfgStrategy2) 57 | { 58 | var addedServices = new HashSet(); 59 | 60 | var mqttHostBuilder = new MqttClientOptionsBuilder(); 61 | var serviceBuilder = new ServicesBuilder(services, addedServices); 62 | var localServerBuilder = new LocalServerOptionsBuilder(); 63 | 64 | if (cfgStrategy1 != null) 65 | cfgStrategy1.Invoke(mqttHostBuilder, serviceBuilder); 66 | 67 | if (cfgStrategy2 != null) 68 | cfgStrategy2.Invoke(mqttHostBuilder, localServerBuilder, serviceBuilder); 69 | 70 | if (!addedServices.Contains(ServiceType.Logger)) 71 | { 72 | serviceBuilder.AddLogger(cfg => cfg.UseInnerLogger()); 73 | } 74 | 75 | return services 76 | .AddSingleton(mqttHostBuilder.Build()) 77 | .AddSingleton(localServerBuilder.Build()) 78 | .AddEventBusServices(); 79 | } 80 | 81 | private static IServiceCollection AddEventBusServices(this IServiceCollection services) => 82 | services 83 | .AddSingleton() 84 | .AddSingleton() 85 | .AddSingleton() 86 | .AddSingleton() 87 | .AddSingleton() 88 | .AddSingleton() 89 | .AddSingleton(); 90 | 91 | public static IServicesBuilder AddLogger(this IServicesBuilder builder, Action loggerConfigurator) => 92 | builder.AddServices(services => 93 | { 94 | var loggerBuilder = new LoggerOptionsBuilder(services); 95 | loggerConfigurator.Invoke(loggerBuilder); 96 | }, ServiceType.Logger); 97 | 98 | public static IServiceCollection AddConsumer(this IServiceCollection services) 99 | where TIConsumer : class, IConsumer 100 | where TConsumer : class, TIConsumer 101 | => services.AddScoped(); 102 | } 103 | } -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace MQTTnet.EventBus 5 | { 6 | public static class EnumerableExtensions 7 | { 8 | public static bool IsNullOrEmpty(this IEnumerable source) 9 | => source == null || !source.Any(); 10 | 11 | public static bool IsNullOrEmpty(this ICollection source) 12 | => source == null || source.Count == 0; 13 | 14 | public static bool IsNullOrEmpty(this T[] source) 15 | => source == null || source.Length == 0; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/EventContext.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus 2 | { 3 | public class EventContext 4 | { 5 | private readonly MqttApplicationMessageReceivedEventArgs _message; 6 | protected readonly IEventProvider _eventProvider; 7 | 8 | public EventContext(MqttApplicationMessageReceivedEventArgs message, IEventProvider eventProvider) 9 | { 10 | _message = message; 11 | _eventProvider = eventProvider; 12 | } 13 | 14 | public string ClientId => _message.ClientId; 15 | public bool ProcessingFailed => _message.ProcessingFailed; 16 | public MqttApplicationMessage Message => _message.ApplicationMessage; 17 | } 18 | 19 | public class EventContext : EventContext 20 | { 21 | public EventContext(TEvent eventArg, MqttApplicationMessageReceivedEventArgs message, IEventProvider eventProvider) 22 | : base(message, eventProvider) 23 | { 24 | EventArg = eventArg; 25 | } 26 | 27 | public TEvent EventArg { get; } 28 | 29 | public TTopicInfo GetTopicInfo() 30 | where TTopicInfo : ITopicPattern, new() 31 | { 32 | var topicInfo = new TTopicInfo(); 33 | bool created = this.GetTopicInfo((provider, eventType) => provider.TrySetTopicInfo(topicInfo, eventType, Message.Topic)); 34 | return created ? topicInfo : default; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/EventContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MQTTnet.EventBus 4 | { 5 | public static class EventContextExtensions 6 | { 7 | 8 | public static string GetTopicEntity(this EventContext context, string name) 9 | => context.GetTopicInfo((provider, eventType) => provider.GetTopicEntity(eventType, context.Message.Topic, name)); 10 | 11 | public static TResult GetTopicInfo(this EventContext context, Func func) 12 | { 13 | var contextType = context.GetType(); 14 | if (!contextType.IsGenericType) 15 | return default; 16 | 17 | var eventType = contextType.GetGenericArguments()[0]; 18 | if (StaticCache.EventProvider.HasTopicPattern(eventType)) 19 | { 20 | return func.Invoke(StaticCache.EventProvider, eventType); 21 | } 22 | 23 | return default; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/EventOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MQTTnet.EventBus 5 | { 6 | public class EventOptions 7 | { 8 | public EventOptions(string eventName) 9 | { 10 | EventName = eventName; 11 | Topic = new TopicDescription(); 12 | } 13 | 14 | public string EventName { get; set; } 15 | public Type EventType { get; set; } 16 | public Type ConsumerType { get; set; } 17 | public Type ConverterType { get; set; } 18 | public TopicDescription Topic { get; set; } 19 | public Action MessageCreater { get; set; } 20 | } 21 | 22 | public class TopicDescription 23 | { 24 | public string Root { get; set; } 25 | public string Pattern { get; set; } 26 | public Type PatternType { get; set; } 27 | 28 | public bool HasPattern => !string.IsNullOrWhiteSpace(Pattern); 29 | public string RootTopic => $"{Root}/#"; 30 | } 31 | 32 | internal class EventOptionsEqualityComparer : IEqualityComparer 33 | { 34 | public bool Equals(EventOptions x, EventOptions y) 35 | { 36 | if (ReferenceEquals(x, y)) 37 | return true; 38 | if (x is null) 39 | return false; 40 | if (y is null) 41 | return false; 42 | 43 | return x.EventName == y.EventName; 44 | } 45 | 46 | public int GetHashCode(EventOptions obj) 47 | => obj.EventName.GetHashCode(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Exceptions/EventConfigurationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MQTTnet.EventBus.Exceptions 4 | { 5 | public class EventConfigurationException : EventException 6 | { 7 | public EventConfigurationException(string eventName, Type eventType, string message) 8 | : base(eventName, eventType, message) 9 | { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Exceptions/EventException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MQTTnet.EventBus.Exceptions 4 | { 5 | public class EventException : Exception 6 | { 7 | public EventException(string eventName, Type eventType, string message) 8 | : base(message) 9 | { 10 | EventName = eventName; 11 | EventType = eventType; 12 | } 13 | 14 | public string EventName { get; } 15 | public Type EventType { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Exceptions/EventNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MQTTnet.EventBus.Exceptions 4 | { 5 | public class EventNotFoundException : EventException 6 | { 7 | public EventNotFoundException(string eventName, Type eventType) 8 | : base(eventName, eventType, $"The given event '{eventName}' was not present in the mapping dictionary, check the startup configuration") 9 | { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/IConsumer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace MQTTnet.EventBus 4 | { 5 | public interface IConsumer : IConsumer 6 | { 7 | Task ConsumeAsync(EventContext context); 8 | } 9 | 10 | public interface IConsumer { } 11 | } -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/IEventBus.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client.Publishing; 2 | using MQTTnet.Client.Subscribing; 3 | using MQTTnet.Client.Unsubscribing; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace MQTTnet.EventBus 10 | { 11 | public interface IEventBus 12 | { 13 | Task PublishAsync(MqttApplicationMessage message, CancellationToken cancellationToken = default); 14 | Task SubscribeAsync(SubscriptionInfo subscriptionInfo, CancellationToken cancellationToken = default); 15 | Task UnsubscribeAsync(SubscriptionInfo subscriptionInfo, CancellationToken cancellationToken = default); 16 | Task ReSubscribeAllAsync(CancellationToken cancellationToken = default); 17 | } 18 | 19 | public static class EventBusExtensions 20 | { 21 | //PublishAsync extensions 22 | public static Task PublishAsync(this IEventBus eventBus, string eventName, object @event, string topic, CancellationToken cancellationToken = default) => 23 | eventBus.PublishAsync(StaticCache.EventProvider.CreateMessage(eventName, @event, topic), cancellationToken); 24 | public static Task PublishAsync(this IEventBus eventBus, object @event, string topic, CancellationToken cancellationToken = default) => 25 | eventBus.PublishAsync(StaticCache.EventProvider.CreateMessage(@event, topic), cancellationToken); 26 | public static Task PublishAsync(this IEventBus eventBus, object @event, CancellationToken cancellationToken = default) => 27 | eventBus.PublishAsync(StaticCache.EventProvider.CreateMessage(@event, StaticCache.EventProvider.GetTopic(@event)), cancellationToken); 28 | public static Task PublishAsync(this IEventBus eventBus, TEvent @event, ITopicPattern topicInfo, CancellationToken cancellationToken = default) => 29 | eventBus.PublishAsync(StaticCache.EventProvider.CreateMessage(@event, StaticCache.EventProvider.GetTopic(topicInfo)), cancellationToken); 30 | 31 | //SubscribeAsync extensions 32 | public static Task SubscribeAsync(this IEventBus eventBus, string eventName, Type eventType, string topic, CancellationToken cancellationToken = default) => 33 | eventBus.SubscribeAsync(StaticCache.EventProvider.CreateSubscriptionInfo(eventName, eventType, topic), cancellationToken); 34 | public static Task SubscribeAsync(this IEventBus eventBus, Type eventType, string topic, CancellationToken cancellationToken = default) => 35 | SubscribeAsync(eventBus, StaticCache.EventProvider.GetEventName(eventType), eventType, topic, cancellationToken); 36 | public static Task SubscribeAsync(this IEventBus eventBus, string topic, CancellationToken cancellationToken = default) => 37 | SubscribeAsync(eventBus, typeof(TEvent), topic, cancellationToken); 38 | public static Task SubscribeAsync(this IEventBus eventBus, ITopicPattern topicInfo, CancellationToken cancellationToken = default) => 39 | SubscribeAsync(eventBus, StaticCache.EventProvider.GetTopic(topicInfo), cancellationToken); 40 | public static Task SubscribeAsync(this IEventBus eventBus, Type eventType, CancellationToken cancellationToken = default) 41 | { 42 | var options = StaticCache.EventProvider.GetEventOptions(eventType); 43 | return SubscribeAsync(eventBus, options.EventName, eventType, options.Topic.RootTopic, cancellationToken); 44 | } 45 | public static Task SubscribeAsync(this IEventBus eventBus, CancellationToken cancellationToken = default) => 46 | SubscribeAsync(eventBus, typeof(TEvent), cancellationToken); 47 | 48 | //UnsubscribeAsync extensions 49 | public static Task UnsubscribeAsync(this IEventBus eventBus, string eventName, Type eventType, string topic, CancellationToken cancellationToken = default) => 50 | eventBus.UnsubscribeAsync(StaticCache.EventProvider.CreateSubscriptionInfo(eventName, eventType, topic), cancellationToken); 51 | public static Task UnsubscribeAsync(this IEventBus eventBus, Type eventType, string topic, CancellationToken cancellationToken = default) => 52 | UnsubscribeAsync(eventBus, StaticCache.EventProvider.GetEventName(eventType), eventType, topic, cancellationToken); 53 | public static Task UnsubscribeAsync(this IEventBus eventBus, string topic, CancellationToken cancellationToken = default) => 54 | UnsubscribeAsync(eventBus, typeof(TEvent), topic, cancellationToken); 55 | public static Task UnsubscribeAsync(this IEventBus eventBus, ITopicPattern topicInfo, CancellationToken cancellationToken = default) => 56 | UnsubscribeAsync(eventBus, StaticCache.EventProvider.GetTopic(topicInfo), cancellationToken); 57 | public static Task UnsubscribeAsync(this IEventBus eventBus, Type eventType, CancellationToken cancellationToken = default) 58 | { 59 | var options = StaticCache.EventProvider.GetEventOptions(eventType); 60 | return UnsubscribeAsync(eventBus, options.EventName, eventType, options.Topic.RootTopic, cancellationToken); 61 | } 62 | public static Task UnsubscribeAsync(this IEventBus eventBus, CancellationToken cancellationToken = default) => 63 | UnsubscribeAsync(eventBus, typeof(TEvent), cancellationToken); 64 | 65 | 66 | public static Task> SubscribeAllAsync(this IEventBus eventBus, CancellationToken cancellationToken = default) => 67 | StaticCache.EventProvider 68 | .ExecuteForAllEventsAsync(info => eventBus.SubscribeAsync(info.EventName, info.EventType, info.Topic.RootTopic), cancellationToken) 69 | .ToListAsync(); 70 | public static Task> UnsubscribeAllAsync(this IEventBus eventBus, CancellationToken cancellationToken = default) => 71 | StaticCache.EventProvider 72 | .ExecuteForAllEventsAsync(info => eventBus.UnsubscribeAsync(info.EventName, info.EventType, info.Topic.RootTopic), cancellationToken) 73 | .ToListAsync(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/IEventProvider.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.EventBus.Exceptions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace MQTTnet.EventBus 10 | { 11 | public interface IEventProvider : IEnumerable 12 | { 13 | bool HasTopicPattern(string eventName); 14 | bool TrySetTopicInfo(string eventName, object @event, string topic); 15 | string GetTopic(string eventName); 16 | string GetTopic(string eventName, object topicInfo); 17 | string GetTopicEntity(string eventName, string topic, string name); 18 | bool TryGetEventName(Type eventType, out string eventName); 19 | bool TryGetEventOptions(string eventName, out EventOptions options); 20 | MqttApplicationMessage CreateMessage(string eventName, object @event, string topic); 21 | } 22 | 23 | public static class IEventProviderExtensions 24 | { 25 | public static string GetEventName(this IEventProvider eventProvider) => 26 | GetEventName(eventProvider, typeof(TEvenet)); 27 | 28 | public static string GetEventName(this IEventProvider eventProvider, Type eventType) 29 | { 30 | if (eventProvider.TryGetEventName(eventType, out string eventName)) 31 | return eventName; 32 | throw new EventNotFoundException(eventName, eventType); 33 | } 34 | 35 | public static EventOptions GetEventOptions(this IEventProvider eventProvider) => 36 | GetEventOptions(eventProvider, typeof(TEvent)); 37 | 38 | public static EventOptions GetEventOptions(this IEventProvider eventProvider, Type eventType) 39 | { 40 | if (TryGetEventOptions(eventProvider, eventType, out var options)) 41 | return options; 42 | 43 | throw new EventNotFoundException(eventType.Name, eventType); 44 | } 45 | 46 | public static bool TryGetEventOptions(this IEventProvider eventProvider, out EventOptions options) => 47 | TryGetEventOptions(eventProvider, typeof(TEvent), out options); 48 | 49 | public static bool TryGetEventOptions(this IEventProvider eventProvider, Type eventType, out EventOptions options) 50 | { 51 | if (eventProvider.TryGetEventName(eventType, out var eventname)) 52 | return eventProvider.TryGetEventOptions(eventname, out options); 53 | 54 | options = null; 55 | return false; 56 | } 57 | 58 | public static Type GetConsumerType(this IEventProvider eventProvider, string eventName) 59 | { 60 | if (eventProvider.TryGetEventOptions(eventName, out var options)) 61 | return options.ConsumerType; 62 | return null; 63 | } 64 | 65 | public static Type GetConverterType(this IEventProvider eventProvider, string eventName) 66 | { 67 | if (eventProvider.TryGetEventOptions(eventName, out var options)) 68 | return options.ConverterType; 69 | return null; 70 | } 71 | 72 | public static string GetTopicEntity(this IEventProvider eventProvider, Type eventType, string topic, string name) 73 | { 74 | if (eventProvider.TryGetEventName(eventType, out string eventName)) 75 | return eventProvider.GetTopicEntity(eventName, topic, name); 76 | return null; 77 | } 78 | 79 | public static bool HasTopicPattern(this IEventProvider eventProvider, Type eventType) 80 | { 81 | if (eventProvider.TryGetEventName(eventType, out string eventName)) 82 | return eventProvider.HasTopicPattern(eventName); 83 | return false; 84 | } 85 | 86 | public static bool TrySetTopicInfo(this IEventProvider eventProvider, TModel model, Type eventType, string topic) 87 | { 88 | if (eventProvider.TryGetEventName(eventType, out string eventName)) 89 | return eventProvider.TrySetTopicInfo(eventName, model, topic); 90 | return false; 91 | } 92 | 93 | public static MqttApplicationMessage CreateMessage(this IEventProvider eventProvider, string eventName, object @event, object topicInfo) 94 | { 95 | string topic = eventProvider.GetTopic(eventName, topicInfo); 96 | return eventProvider.CreateMessage(eventName, @event, topic); 97 | } 98 | 99 | public static MqttApplicationMessage CreateMessage(this IEventProvider eventProvider, object @event, object topicInfo) 100 | { 101 | if (eventProvider.TryGetEventName(@event.GetType(), out string eventName)) 102 | return eventProvider.CreateMessage(eventName, @event, topicInfo); 103 | return null; 104 | } 105 | 106 | public static bool SetTopicInfo(this IEventProvider eventProvider, object @event, string topic) 107 | { 108 | if (eventProvider.TryGetEventName(@event.GetType(), out string eventName)) 109 | return eventProvider.TrySetTopicInfo(eventName, @event, topic); 110 | return false; 111 | } 112 | 113 | public static string GetTopic(this IEventProvider eventProvider, Type eventType) 114 | { 115 | if (eventProvider.TryGetEventName(eventType, out string eventName)) 116 | return eventProvider.GetTopic(eventName); 117 | return null; 118 | } 119 | 120 | public static string GetTopic(this IEventProvider eventProvider, object @event) => 121 | GetTopic(eventProvider, @event.GetType()); 122 | 123 | public static string GetTopic(this IEventProvider eventProvider, Type eventType, object toipcInfo) 124 | { 125 | if (eventProvider.TryGetEventName(eventType, out string eventName)) 126 | return eventProvider.GetTopic(eventName, toipcInfo); 127 | return null; 128 | } 129 | 130 | public static string GetTopic(this IEventProvider eventProvider, ITopicPattern toipicInfo) => 131 | GetTopic(eventProvider, typeof(TEvent), toipicInfo); 132 | 133 | public static Type GetConsumerType(this IEventProvider eventProvider, Type eventType) 134 | { 135 | if (eventProvider.TryGetEventName(eventType, out string eventName)) 136 | return eventProvider.GetConsumerType(eventName); 137 | return null; 138 | } 139 | 140 | public static MqttApplicationMessage CreateMessage(this IEventProvider eventProvider, object @event, string topic) 141 | { 142 | if (eventProvider.TryGetEventName(@event.GetType(), out string eventName)) 143 | return eventProvider.CreateMessage(eventName, @event, topic); 144 | return null; 145 | } 146 | 147 | public static Type GetConsumerType(this IEventProvider eventProvider) => 148 | eventProvider.GetConsumerType(typeof(TEvent)); 149 | 150 | public static SubscriptionInfo CreateSubscriptionInfo(this IEventProvider eventProvider, string eventName, Type eventType, string topic) => 151 | new SubscriptionInfo 152 | { 153 | Topic = topic, 154 | EventName = eventName, 155 | EventType = eventType, 156 | ConsumerType = eventProvider.GetConsumerType(eventType) 157 | }; 158 | 159 | public static SubscriptionInfo CreateSubscriptionInfo(this IEventProvider eventProvider, Type eventType, string topic) => 160 | eventProvider.CreateSubscriptionInfo(eventType.Name, eventType, topic); 161 | 162 | public static SubscriptionInfo CreateSubscriptionInfo(this IEventProvider eventProvider, string topic) => 163 | CreateSubscriptionInfo(eventProvider, typeof(TEvent), topic); 164 | 165 | public static async IAsyncEnumerable ExecuteForAllEventsAsync(this IEventProvider eventProvider, Func> executer, [EnumeratorCancellation] CancellationToken cancellationToken = default) 166 | { 167 | foreach (var info in eventProvider.Where(p => p.ConsumerType != null)) 168 | { 169 | if (cancellationToken.IsCancellationRequested) 170 | cancellationToken.ThrowIfCancellationRequested(); 171 | 172 | yield return await executer.Invoke(info); 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/IMqttPersisterConnection.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Client.Subscribing; 3 | using MQTTnet.Client.Unsubscribing; 4 | using MQTTnet.EventBus.Impl; 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace MQTTnet.EventBus 10 | { 11 | public interface IMqttPersisterConnection : IDisposable 12 | { 13 | bool IsConnected { get; } 14 | Task TryConnectAsync(bool afterDisconnection = false, CancellationToken cancellationToken = default); 15 | IMqttClient GetClient(); 16 | event Func ClientConnectionChanged; 17 | } 18 | 19 | public static class IMqttPersisterConnectionExtensions 20 | { 21 | public static async Task TryRegisterMessageHandlerAsync(this IMqttPersisterConnection persisterConnection, Func handler, CancellationToken cancellationToken = default) 22 | { 23 | if (await persisterConnection.TryConnectAsync(afterDisconnection: false, cancellationToken)) 24 | { 25 | persisterConnection.GetClient().UseApplicationMessageReceivedHandler(handler); 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | public static async Task SubscribeAsync(this IMqttPersisterConnection persisterConnection, string topic, CancellationToken cancellationToken = default) 32 | { 33 | if (await persisterConnection.TryConnectAsync(afterDisconnection: false, cancellationToken)) 34 | return await persisterConnection.GetClient().SubscribeAsync(topic); 35 | return null; 36 | } 37 | 38 | public static async Task RemoveSubscriptionAsync(this IMqttPersisterConnection persisterConnection, string topic, CancellationToken cancellationToken = default) 39 | { 40 | if (await persisterConnection.TryConnectAsync(afterDisconnection: false, cancellationToken)) 41 | return await persisterConnection.GetClient().UnsubscribeAsync(topic); 42 | return null; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/ISubscriptionsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MQTTnet.EventBus 5 | { 6 | public interface ISubscriptionsManager 7 | { 8 | bool IsEmpty { get; } 9 | event EventHandler OnEventRemoved; 10 | bool TryAddSubscription(SubscriptionInfo subscriptionInfo); 11 | void RemoveSubscription(SubscriptionInfo subscriptionInfo); 12 | bool HasSubscriptionsForEvent(string topic); 13 | HashSet GetSubscriptions(string topic); 14 | void Clear(); 15 | IEnumerable GetHandlersForEvent(string eventName); 16 | IEnumerable AllTopics(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/ITopicComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MQTTnet.EventBus 4 | { 5 | public interface ITopicComparer 6 | { 7 | bool IsMatch(string topic, string filter); 8 | } 9 | 10 | public class MqttTopicComparer : ITopicComparer 11 | { 12 | public bool IsMatch(string topic, string filter) 13 | { 14 | if (topic == null) 15 | throw new ArgumentNullException(nameof(topic)); 16 | 17 | return Server.MqttTopicFilterComparer.IsMatch(topic, filter); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/ITopicPattenBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace MQTTnet.EventBus 7 | { 8 | public class TopicPatternInfo 9 | { 10 | public bool IsConst { get; set; } 11 | public string Name { get; set; } 12 | } 13 | 14 | public interface ITopicPattenBuilder 15 | { 16 | string GetTopicEntity(string topicPattern, string topic, string name); 17 | void SetData(object model, string topicPattern, string topic); 18 | Expression> CreateTopicCreater(Type eventType, string topicPattern); 19 | Expression> CreateTopic(string topicPattern); 20 | IEnumerable Parse(string topicPattern); 21 | string ConvertToFormattedString(IEnumerable patternInfos); 22 | } 23 | 24 | public static class TopicPattenBuilderExtensions 25 | { 26 | public static bool IsPattern(this ITopicPattenBuilder topicPattenBuilder, string topicPattern) 27 | => topicPattenBuilder.Parse(topicPattern).Any(p => !p.IsConst); 28 | 29 | public static bool IsStaticTopic(this ITopicPattenBuilder topicPattenBuilder, string topicPattern) 30 | => topicPattenBuilder.Parse(topicPattern).All(p => p.IsConst); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/ITopicPattern.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MQTTnet.EventBus 4 | { 5 | public interface ITopicPattern 6 | { 7 | Type EventType => typeof(TEvent); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Impl/DefaultMqttPersisterConnection.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Client.Connecting; 3 | using MQTTnet.Client.Disconnecting; 4 | using MQTTnet.Client.Options; 5 | using MQTTnet.EventBus.Logger; 6 | using MQTTnet.Exceptions; 7 | using MQTTnet.Internal; 8 | using Polly; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Net.Sockets; 13 | using System.Threading; 14 | using System.Threading.Tasks; 15 | 16 | namespace MQTTnet.EventBus.Impl 17 | { 18 | public class DefaultMqttPersisterConnection : IMqttPersisterConnection 19 | { 20 | private bool _disposed; 21 | private IMqttClient _client; 22 | private IMqttClientOptions _options; 23 | private readonly int _retryCount; 24 | private readonly IEventBusLogger _logger; 25 | private readonly IDictionary _disconnectionCache; 26 | private readonly AsyncLock _asyncLock; 27 | 28 | public DefaultMqttPersisterConnection(IMqttClientOptions mqttClientOptions, IEventBusLogger logger, BusOptions busOptions) 29 | { 30 | _asyncLock = new AsyncLock(); 31 | _options = mqttClientOptions; 32 | _logger = logger; 33 | _retryCount = busOptions?.RetryCount ?? 5; 34 | _client = CreareMqttClient(); 35 | _disconnectionCache = new Dictionary(); 36 | } 37 | 38 | public event Func ClientConnectionChanged; 39 | public bool IsConnected => _client.IsConnected; 40 | public IMqttClient GetClient() => _client; 41 | 42 | private IMqttClient CreareMqttClient() 43 | { 44 | var client = new MqttFactory().CreateMqttClient(); 45 | client.UseDisconnectedHandler(OnDisconnectedAsync); 46 | client.UseConnectedHandler(OnConnectedAsync); 47 | _logger.LogInformation($"Mqtt Client acquired a persistent connection to '{_options.ClientId}'"); 48 | return client; 49 | } 50 | 51 | public async Task TryConnectAsync(bool afterDisconnection = false, CancellationToken cancellationToken = default) 52 | { 53 | if (IsConnected) 54 | return true; 55 | 56 | using (await _asyncLock.WaitAsync(cancellationToken)) 57 | { 58 | _logger.LogInformation("Mqtt Client is trying to connect"); 59 | 60 | try 61 | { 62 | var policy = Policy.Handle() 63 | .Or() 64 | .WaitAndRetryAsync(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => 65 | { 66 | _logger.LogWarning(ex, $"Mqtt Client could not connect after {time.TotalSeconds:n1}s ({ex.Message})"); 67 | }); 68 | 69 | await policy.ExecuteAsync(token => afterDisconnection ? 70 | _client.ReconnectAsync() : 71 | _client.ConnectAsync(_options, token), cancellationToken); 72 | 73 | _logger.LogInformation("Mqtt Client was connected"); 74 | } 75 | catch (Exception ex) 76 | { 77 | _logger.LogInformation(ex.Message); 78 | } 79 | 80 | return IsConnected; 81 | } 82 | } 83 | 84 | private Task OnConnectedAsync(MqttClientConnectedEventArgs e) 85 | { 86 | if (_disconnectionCache.TryGetValue(_options.ClientId, out var args)) 87 | { 88 | if(!args.IsReConnected) 89 | { 90 | _logger.LogInformation($"A MqttServer '{_options.ClientId}' trying to connect..."); 91 | args.MarkAsConnected(); 92 | return InvokeClientConnectionChangedMethod(args); 93 | } 94 | } 95 | else 96 | _disconnectionCache.Add(_options.ClientId, MqttClientConnectionEventArgs.Connected(_options.ClientId)); 97 | 98 | return Task.CompletedTask; 99 | } 100 | 101 | private async Task OnDisconnectedAsync(MqttClientDisconnectedEventArgs e) 102 | { 103 | if (_disposed) 104 | return; 105 | 106 | _logger.LogWarning($"A MqttServer '{_options.ClientId}' connection is shutdown, Reason: {e.Reason}. Trying to re-connect..."); 107 | if (_disconnectionCache.TryGetValue(_options.ClientId, out var args)) 108 | { 109 | if (args.IsReConnected) 110 | { 111 | args.MarkAsDisconnected(); 112 | await InvokeClientConnectionChangedMethod(args); 113 | } 114 | } 115 | else 116 | { 117 | _disconnectionCache.Add(_options.ClientId, MqttClientConnectionEventArgs.Disconnected(_options.ClientId, e.Reason)); 118 | await InvokeClientConnectionChangedMethod(args); 119 | } 120 | 121 | await TryConnectAsync(true); 122 | } 123 | 124 | private Task InvokeClientConnectionChangedMethod(MqttClientConnectionEventArgs args) 125 | { 126 | var handler = ClientConnectionChanged; 127 | if (handler != null) 128 | return handler.Invoke(this, args); 129 | return Task.CompletedTask; 130 | } 131 | 132 | public void Dispose() 133 | { 134 | if (_disposed) 135 | return; 136 | 137 | _disposed = true; 138 | 139 | try 140 | { 141 | _client?.Dispose(); 142 | _client = null; 143 | } 144 | catch (IOException ex) 145 | { 146 | _logger.LogError(ex, $"MqttClient cann't Dispose"); 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Impl/EventProvider.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.EventBus.Exceptions; 2 | using MQTTnet.EventBus.Logger; 3 | using MQTTnet.EventBus.Serializers; 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | 10 | namespace MQTTnet.EventBus.Impl 11 | { 12 | public class EventProvider : IEventProvider 13 | { 14 | private readonly IDictionary _eventCreaters; 15 | private readonly IDictionary _eventOptions; 16 | private readonly IDictionary _eventNames; 17 | private readonly IDictionary> _topicCreaters; 18 | private readonly IDictionary _topics; 19 | private readonly ITopicPattenBuilder _topicPattenBuilder; 20 | private readonly IEventBusLogger _logger; 21 | 22 | public EventProvider() : this(null, null, new HashSet(), null) { } 23 | 24 | public EventProvider(IServiceProvider serviceProvider, ITopicPattenBuilder topicPattenBuilder, HashSet eventOptions, IEventBusLogger logger) 25 | { 26 | _topicPattenBuilder = topicPattenBuilder; 27 | _eventCreaters = eventOptions.ToDictionary(p => p.EventName, p => EventCreater.New(serviceProvider, p)); 28 | _eventOptions = eventOptions.ToDictionary(p => p.EventName); 29 | _eventNames = eventOptions.ToDictionary(p => p.EventType, p => p.EventName); 30 | 31 | _topicCreaters = eventOptions 32 | .Where(p => p.Topic.PatternType != null && _topicPattenBuilder.IsPattern(p.Topic.Pattern)) 33 | .ToDictionary(p => p.EventName, p => topicPattenBuilder.CreateTopicCreater(p.Topic.PatternType, p.Topic.Pattern).Compile()); 34 | 35 | _topics = eventOptions 36 | .Where(p => !_topicPattenBuilder.IsStaticTopic(p.Topic.Pattern)) 37 | .ToDictionary(p => p.EventName, p => p.Topic.Pattern); 38 | 39 | _logger = logger; 40 | } 41 | 42 | public MqttApplicationMessage CreateMessage(string eventName, object @event, string topic) 43 | { 44 | if (_eventCreaters.TryGetValue(eventName, out var eventCreater)) 45 | return eventCreater.CreateMqttApplicationMessage(@event, topic); 46 | 47 | Type eventType = null; 48 | if (TryGetEventOptions(eventName, out var options)) 49 | eventType = options.EventType; 50 | 51 | throw new EventNotFoundException(eventName, eventType); 52 | } 53 | 54 | public string GetTopic(string eventName) 55 | { 56 | if (_topics.TryGetValue(eventName, out string topic)) 57 | return topic; 58 | return string.Empty; 59 | } 60 | 61 | public string GetTopic(string eventName, object topicInfo) 62 | { 63 | if (_topicCreaters.TryGetValue(eventName, out var topicCreater)) 64 | return topicCreater.Invoke(topicInfo); 65 | return string.Empty; 66 | } 67 | 68 | public bool HasTopicPattern(string eventName) 69 | { 70 | if (_eventOptions.TryGetValue(eventName, out var opions)) 71 | return !string.IsNullOrEmpty(opions.Topic.Pattern); 72 | return false; 73 | } 74 | 75 | public bool TryGetEventName(Type eventType, out string eventName) 76 | => _eventNames.TryGetValue(eventType, out eventName); 77 | 78 | public string GetTopicEntity(string eventName, string topic, string name) 79 | { 80 | if (_eventOptions.TryGetValue(eventName, out var opions)) 81 | { 82 | if (!opions.Topic.HasPattern) 83 | return string.Empty; 84 | 85 | return _topicPattenBuilder.GetTopicEntity(opions.Topic.Pattern, topic, name); 86 | } 87 | 88 | return string.Empty; 89 | } 90 | 91 | public bool TrySetTopicInfo(string eventName, object @event, string topic) 92 | { 93 | try 94 | { 95 | if (_eventOptions.TryGetValue(eventName, out var opions)) 96 | { 97 | if (!opions.Topic.HasPattern) 98 | return false; 99 | 100 | _topicPattenBuilder.SetData(@event, opions.Topic.Pattern, topic); 101 | return true; 102 | } 103 | } 104 | catch (Exception ex) 105 | { 106 | _logger.LogWarning(ex, $"eventName: {eventName}, EvenType: {@event.GetType()}, Topic: {topic}"); 107 | } 108 | 109 | return false; 110 | } 111 | 112 | public bool TryGetEventOptions(string eventName, out EventOptions options) => 113 | _eventOptions.TryGetValue(eventName, out options); 114 | 115 | public IEnumerator GetEnumerator() => 116 | _eventOptions.Values.GetEnumerator(); 117 | 118 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 119 | 120 | private class EventCreater 121 | { 122 | public EventCreater(IServiceProvider serviceProvider, EventOptions eventOptions) 123 | { 124 | _serializerMethod = eventOptions.ConverterType.GetMethod(nameof(IEventSerializer.Serialize)); 125 | _converter = new Lazy(() => serviceProvider.GetService(eventOptions.ConverterType)); 126 | MessageCreater = eventOptions.MessageCreater; 127 | } 128 | 129 | private readonly MethodInfo _serializerMethod; 130 | 131 | private readonly Lazy _converter; 132 | public object Converter => _converter.Value; 133 | public Action MessageCreater { get; } 134 | 135 | public byte[] Serialize(object @event) 136 | => (byte[])_serializerMethod.Invoke(Converter, new object[] { @event }); 137 | 138 | public MqttApplicationMessage CreateMqttApplicationMessage(object @event, string topic) 139 | { 140 | var data = Serialize(@event); 141 | 142 | var builder = new MqttApplicationMessageBuilder(); 143 | builder.WithTopic(topic); 144 | builder.WithPayload(data); 145 | MessageCreater.Invoke(builder); 146 | 147 | return builder.Build(); 148 | } 149 | 150 | public static EventCreater New(IServiceProvider serviceProvider, EventOptions eventOptions) 151 | { 152 | if (eventOptions.MessageCreater is null) 153 | eventOptions.MessageCreater = builder => builder.WithRetainFlag(); 154 | 155 | return new EventCreater(serviceProvider, eventOptions); 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Impl/InMemorySubscriptionsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace MQTTnet.EventBus.Impl 6 | { 7 | public partial class InMemorySubscriptionsManager : ISubscriptionsManager 8 | { 9 | private readonly Dictionary> _cache; 10 | private readonly HashSet _eventTypes; 11 | private readonly ITopicComparer _topicComparer; 12 | 13 | public event EventHandler OnEventRemoved; 14 | 15 | public InMemorySubscriptionsManager(ITopicComparer topicComparer) 16 | { 17 | _cache = new Dictionary>(); 18 | _eventTypes = new HashSet(); 19 | _topicComparer = topicComparer; 20 | } 21 | 22 | public bool IsEmpty => !_cache.Keys.Any(); 23 | public void Clear() => _cache.Clear(); 24 | 25 | public bool TryAddSubscription(SubscriptionInfo subscriptionInfo) 26 | { 27 | string eventName = subscriptionInfo.EventType.Name; 28 | DoAddSubscription(subscriptionInfo); 29 | 30 | if (!_eventTypes.Contains(eventName)) 31 | { 32 | return _eventTypes.Add(eventName); 33 | } 34 | 35 | return false; 36 | } 37 | 38 | private void DoAddSubscription(SubscriptionInfo subscriptionInfo) 39 | { 40 | string eventName = subscriptionInfo.Topic; 41 | if (!HasSubscriptionsForEvent(eventName)) 42 | { 43 | _cache.Add(eventName, new HashSet(ComparersManager.SubscriptionInfo)); 44 | } 45 | 46 | var consumerType = subscriptionInfo.ConsumerType; 47 | if (_cache[eventName].Any(s => s.ConsumerType == consumerType)) 48 | { 49 | throw new ArgumentException( 50 | $"Handler Type {consumerType.Name} already registered for '{eventName}'", nameof(consumerType)); 51 | } 52 | 53 | _cache[eventName].Add(subscriptionInfo); 54 | } 55 | 56 | public void RemoveSubscription(SubscriptionInfo subscriptionInfo) 57 | { 58 | var handlerToRemove = FindSubscriptionToRemove(subscriptionInfo.Topic, subscriptionInfo.ConsumerType); 59 | DoRemoveConsumer(subscriptionInfo.Topic, handlerToRemove); 60 | } 61 | 62 | private void DoRemoveConsumer(string topic, SubscriptionInfo subsToRemove) 63 | { 64 | if (subsToRemove != null) 65 | { 66 | _cache[topic].Remove(subsToRemove); 67 | if (!_cache[topic].Any()) 68 | { 69 | _cache.Remove(topic); 70 | var eventType = _eventTypes.SingleOrDefault(e => e == topic); 71 | if (eventType != null) 72 | { 73 | _eventTypes.Remove(eventType); 74 | } 75 | RaiseOnEventRemoved(topic); 76 | } 77 | } 78 | } 79 | public IEnumerable GetHandlersForEvent(string topic) => _cache[topic]; 80 | 81 | public IEnumerable AllTopics() => _cache.Select(p => p.Key); 82 | 83 | private void RaiseOnEventRemoved(string eventName) 84 | { 85 | var handler = OnEventRemoved; 86 | handler?.Invoke(this, eventName); 87 | } 88 | 89 | private SubscriptionInfo FindSubscriptionToRemove(string topic, Type consumerType) 90 | { 91 | if (!HasSubscriptionsForEvent(topic)) 92 | return null; 93 | 94 | return _cache[topic].SingleOrDefault(s => s.ConsumerType == consumerType); 95 | } 96 | 97 | public bool HasSubscriptionsForEvent(string topic) 98 | { 99 | if (_cache.ContainsKey(topic)) 100 | return true; 101 | return _cache.Keys.Any(filter => _topicComparer.IsMatch(topic, filter)); 102 | } 103 | 104 | public HashSet GetSubscriptions(string topic) 105 | { 106 | if (_cache.TryGetValue(topic, out var options)) 107 | return options; 108 | 109 | return _cache 110 | .Where(p => _topicComparer.IsMatch(topic, p.Key)) 111 | .SelectMany(p => p.Value) 112 | .ToHashSet(ComparersManager.SubscriptionInfo); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Impl/MqttClientConnectionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client.Disconnecting; 2 | using System; 3 | 4 | namespace MQTTnet.EventBus.Impl 5 | { 6 | public class MqttClientConnectionEventArgs 7 | { 8 | public MqttClientConnectionEventArgs(string clientId) 9 | { 10 | ClientId = clientId; 11 | } 12 | 13 | public MqttClientConnectionEventArgs(string clientId, MqttClientDisconnectReason reason) 14 | : this(clientId) 15 | { 16 | DisconnectReason = reason; 17 | } 18 | 19 | public string ClientId { get; } 20 | public MqttClientDisconnectReason DisconnectReason { get; } 21 | public bool IsReConnected { get; internal set; } 22 | public DateTime DisconnectionTime { get; internal set; } 23 | public DateTime ConnectionTime { get; internal set; } 24 | 25 | internal void MarkAsConnected() 26 | { 27 | ConnectionTime = DateTime.Now; 28 | IsReConnected = true; 29 | } 30 | 31 | internal void MarkAsDisconnected() 32 | { 33 | DisconnectionTime = DateTime.Now; 34 | IsReConnected = false; 35 | } 36 | 37 | public static MqttClientConnectionEventArgs Disconnected(string clientId, MqttClientDisconnectReason reason) => 38 | new MqttClientConnectionEventArgs(clientId, reason) 39 | { 40 | DisconnectionTime = DateTime.Now, 41 | IsReConnected = false 42 | }; 43 | 44 | public static MqttClientConnectionEventArgs Connected(string clientId) => 45 | new MqttClientConnectionEventArgs(clientId) 46 | { 47 | ConnectionTime = DateTime.Now, 48 | IsReConnected = true 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Impl/MqttEventBus.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using MQTTnet.Client; 3 | using MQTTnet.Client.Publishing; 4 | using MQTTnet.Client.Subscribing; 5 | using MQTTnet.Client.Unsubscribing; 6 | using MQTTnet.EventBus.Logger; 7 | using MQTTnet.EventBus.Reflection; 8 | using MQTTnet.Exceptions; 9 | using Polly; 10 | using System; 11 | using System.Linq; 12 | using System.Net.Sockets; 13 | using System.Threading; 14 | using System.Threading.Tasks; 15 | 16 | namespace MQTTnet.EventBus.Impl 17 | { 18 | public class MqttEventBus : IEventBus, IDisposable 19 | { 20 | private readonly IEventBusLogger _logger; 21 | private readonly ISubscriptionsManager _subsManager; 22 | private readonly IServiceScopeFactory _scopeFactory; 23 | private readonly IConsumeMethodInvoker _consumeMethodInvoker; 24 | private readonly IMqttPersisterConnection _mqttPersisterConnection; 25 | private readonly IEventProvider _eventProvider; 26 | private readonly int _retryCount; 27 | private readonly SemaphoreSlim _asyncLocker; 28 | 29 | public MqttEventBus( 30 | IMqttPersisterConnection mqttPersisterConnection, 31 | IEventProvider eventProvider, 32 | IConsumeMethodInvoker consumeMethodInvoker, 33 | IEventBusLogger logger, 34 | IServiceScopeFactory scopeFactory, 35 | ISubscriptionsManager subsManager, 36 | BusOptions busOptions) 37 | { 38 | _mqttPersisterConnection = mqttPersisterConnection ?? throw new ArgumentNullException(nameof(mqttPersisterConnection)); 39 | _mqttPersisterConnection.ClientConnectionChanged += OnConnectionLostAsync; 40 | _eventProvider = eventProvider; 41 | _consumeMethodInvoker = consumeMethodInvoker; 42 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 43 | _subsManager = subsManager ?? throw new ArgumentNullException(nameof(ISubscriptionsManager)); 44 | _retryCount = busOptions?.RetryCount ?? 5; 45 | _scopeFactory = scopeFactory; 46 | _asyncLocker = new SemaphoreSlim(busOptions.MaxConcurrentCalls, busOptions.MaxConcurrentCalls); 47 | } 48 | 49 | public async Task PublishAsync(MqttApplicationMessage message, CancellationToken cancellationToken = default) 50 | { 51 | var connection = _mqttPersisterConnection; 52 | if (!connection.IsConnected) 53 | { 54 | await connection.TryConnectAsync(cancellationToken: cancellationToken); 55 | } 56 | 57 | try 58 | { 59 | var policy = Policy 60 | .Handle() 61 | .WaitAndRetryAsync(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => 62 | { 63 | _logger.LogError(ex, $"Could not publish topic: {message?.Topic} after {time.TotalSeconds:n1}s ({ex.Message})"); 64 | }); 65 | 66 | return await policy.ExecuteAsync(token => connection.GetClient().PublishAsync(message, token), cancellationToken); 67 | } 68 | catch { } 69 | 70 | return null; 71 | } 72 | 73 | private async Task MessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) 74 | { 75 | await _asyncLocker.WaitAsync(); 76 | 77 | string topic = eventArgs?.ApplicationMessage?.Topic; 78 | try 79 | { 80 | _logger.LogInformation($"Processing Mqtt topic: \"{topic}\""); 81 | await ProcessEvent(eventArgs); 82 | } 83 | catch (Exception ex) 84 | { 85 | _logger.LogWarning(ex, $"Error Processing topic \"{topic}\""); 86 | } 87 | finally 88 | { 89 | _asyncLocker.Release(); 90 | } 91 | } 92 | 93 | private async Task ProcessEvent(MqttApplicationMessageReceivedEventArgs eventArgs) 94 | { 95 | var message = eventArgs.ApplicationMessage; 96 | var subscriptions = _subsManager.GetSubscriptions(message.Topic); 97 | if (subscriptions.IsNullOrEmpty()) 98 | { 99 | _logger.LogWarning($"No subscription for Mqtt topic: \"{message.Topic}\""); 100 | } 101 | else 102 | { 103 | foreach (var subscription in subscriptions) 104 | { 105 | using var scope = _scopeFactory.CreateScope(); 106 | await Task.Yield(); 107 | await _consumeMethodInvoker.InvokeAsync(scope.ServiceProvider, subscription, eventArgs); 108 | } 109 | } 110 | } 111 | 112 | public void Dispose() 113 | { 114 | _mqttPersisterConnection.Dispose(); 115 | _subsManager.Clear(); 116 | } 117 | 118 | public async Task OnConnectionLostAsync(IMqttPersisterConnection connection, MqttClientConnectionEventArgs args) 119 | { 120 | if (args != null && args.IsReConnected) 121 | { 122 | if (args.DisconnectReason == Client.Disconnecting.MqttClientDisconnectReason.NormalDisconnection) 123 | await ReSubscribeAllAsync(); 124 | } 125 | } 126 | 127 | public async Task SubscribeAsync(SubscriptionInfo subscriptionInfo, CancellationToken cancellationToken = default) 128 | { 129 | string topic = subscriptionInfo?.Topic; 130 | _logger.LogInformation($"Subscribing to topic {topic} with {subscriptionInfo?.ConsumerType?.Name}"); 131 | 132 | var containsKey = _subsManager.HasSubscriptionsForEvent(topic); 133 | if (!containsKey) 134 | { 135 | if (await _mqttPersisterConnection.TryRegisterMessageHandlerAsync(MessageReceivedAsync, cancellationToken)) 136 | { 137 | _subsManager.TryAddSubscription(subscriptionInfo); 138 | return await OnSubscribesAsync(topic, cancellationToken); 139 | } 140 | } 141 | 142 | return null; 143 | } 144 | 145 | private async Task OnSubscribesAsync(string topic, CancellationToken cancellationToken = default) 146 | { 147 | var connection = _mqttPersisterConnection; 148 | if (!connection.IsConnected) 149 | { 150 | await connection.TryConnectAsync(cancellationToken: cancellationToken); 151 | } 152 | 153 | try 154 | { 155 | var policy = Policy 156 | .Handle() 157 | .WaitAndRetryAsync(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => 158 | { 159 | _logger.LogError(ex, $"Could not Subscribe topic: {topic} after {time.TotalSeconds:n1}s ({ex.Message})"); 160 | }); 161 | 162 | return await policy.ExecuteAsync(token => connection.GetClient().SubscribeAsync(topic), cancellationToken); 163 | } 164 | catch { } 165 | 166 | return null; 167 | } 168 | 169 | public async Task UnsubscribeAsync(SubscriptionInfo subscriptionInfo, CancellationToken cancellationToken = default) 170 | { 171 | var connection = _mqttPersisterConnection; 172 | if (!connection.IsConnected) 173 | { 174 | await connection.TryConnectAsync(cancellationToken: cancellationToken); 175 | } 176 | 177 | var topic = subscriptionInfo?.Topic; 178 | _logger.LogInformation($"Unsubscribing to topic {topic} with {subscriptionInfo?.ConsumerType?.Name}"); 179 | 180 | var containsKey = _subsManager.HasSubscriptionsForEvent(topic); 181 | if (!containsKey) 182 | { 183 | _subsManager.RemoveSubscription(subscriptionInfo); 184 | return await connection.RemoveSubscriptionAsync(topic); 185 | } 186 | 187 | return null; 188 | } 189 | 190 | public Task ReSubscribeAllAsync(CancellationToken cancellationToken = default) 191 | { 192 | var subscribers = _subsManager.AllTopics().Select(async topic => 193 | { 194 | if (await _mqttPersisterConnection.TryConnectAsync(cancellationToken: cancellationToken)) 195 | return await OnSubscribesAsync(topic); 196 | return null; 197 | }); 198 | 199 | return Task.WhenAll(subscribers); 200 | } 201 | } 202 | } -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Impl/TopicPattenBuilder.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.EventBus.Reflection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | 8 | namespace MQTTnet.EventBus.Impl 9 | { 10 | public class TopicPattenBuilder : ITopicPattenBuilder 11 | { 12 | public string GetTopicEntity(string topicPattern, string topic, string name) 13 | { 14 | int index = 0; 15 | var topicInfo = Parse(topic).ToArray(); 16 | foreach (var patterntInfo in Parse(topicPattern)) 17 | { 18 | if (!patterntInfo.IsConst) 19 | { 20 | if (patterntInfo.Name == name) 21 | return topicInfo[index].Name; 22 | } 23 | 24 | index++; 25 | if (index == topicInfo.Length) 26 | break; 27 | } 28 | 29 | return null; 30 | } 31 | 32 | public void SetData(object model, string topicPattern, string topic) 33 | { 34 | var modelType = model.GetType(); 35 | 36 | int index = 0; 37 | var topicInfo = Parse(topic).ToArray(); 38 | foreach (var patterntInfo in Parse(topicPattern)) 39 | { 40 | if (!patterntInfo.IsConst) 41 | { 42 | var pi = modelType.GetProperty(patterntInfo.Name); 43 | if (pi.SetMethod != null) 44 | { 45 | var info = topicInfo[index]; 46 | if (pi.PropertyType == typeof(string)) 47 | { 48 | pi.SetValue(model, info.Name); 49 | } 50 | else 51 | { 52 | var value = Convert.ChangeType(info.Name, pi.PropertyType); 53 | pi.SetValue(model, value); 54 | } 55 | } 56 | } 57 | 58 | index++; 59 | if (index == topicInfo.Length) 60 | return; 61 | } 62 | } 63 | 64 | public Expression> CreateTopicCreater(Type eventType, string topicPattern) 65 | { 66 | var pEvent = Expression.Parameter(typeof(object), "ev"); 67 | 68 | var topicInfos = Parse(topicPattern); 69 | var format = ConvertToFormattedString(topicInfos); 70 | 71 | var formatExp = Expression.Constant(format, typeof(string)); 72 | 73 | Type stringType = typeof(string); 74 | var argExps = topicInfos.Where(p => !p.IsConst).Select(ti => 75 | { 76 | var prop = eventType.GetProperty(ti.Name); 77 | var propExp = Expression.Property( 78 | Expression.Convert(pEvent, eventType), prop); 79 | if (prop.PropertyType != stringType) 80 | { 81 | return (Expression)Expression.Call(propExp, typeof(object).GetMethod(nameof(object.ToString))); 82 | } 83 | 84 | return propExp; 85 | }); 86 | 87 | var argExp = Expression.NewArrayInit(typeof(object), argExps); 88 | var body = Expression.Call(null, ReflectionHelper.GetStringFormatMethod(), formatExp, argExp); 89 | 90 | return Expression.Lambda>(body, pEvent); 91 | } 92 | 93 | public Expression> CreateTopic(string topicPattern) 94 | { 95 | var eventType = typeof(TEvent); 96 | var pEvent = Expression.Parameter(eventType, "ev"); 97 | 98 | var topicInfos = Parse(topicPattern); 99 | var format = ConvertToFormattedString(topicInfos); 100 | 101 | var formatExp = Expression.Constant(format, typeof(string)); 102 | 103 | Type stringType = typeof(string); 104 | var argExps = topicInfos.Where(p => !p.IsConst).Select(ti => 105 | { 106 | var prop = eventType.GetProperty(ti.Name); 107 | var propExp = Expression.Property(pEvent, prop); 108 | if (prop.PropertyType != stringType) 109 | { 110 | return (Expression)Expression.Call(propExp, typeof(object).GetMethod(nameof(object.ToString))); 111 | } 112 | 113 | return propExp; 114 | }); 115 | 116 | var argExp = Expression.NewArrayInit(typeof(object), argExps); 117 | var body = Expression.Call(null, ReflectionHelper.GetStringFormatMethod(), formatExp, argExp); 118 | 119 | return Expression.Lambda>(body, pEvent); 120 | } 121 | 122 | public IEnumerable Parse(string topicPattern) 123 | { 124 | if (string.IsNullOrEmpty(topicPattern)) 125 | return Enumerable.Empty(); 126 | 127 | return topicPattern.Trim('/').Split('/').Select(p => 128 | { 129 | if (p.StartsWith("{") && p.EndsWith("}")) 130 | { 131 | return new TopicPatternInfo 132 | { 133 | IsConst = false, 134 | Name = p.Substring(1, p.Length - 2) 135 | }; 136 | } 137 | 138 | return new TopicPatternInfo 139 | { 140 | IsConst = true, 141 | Name = p 142 | }; 143 | }).ToArray(); 144 | } 145 | 146 | public string ConvertToFormattedString(IEnumerable patternInfos) 147 | { 148 | var builder = new StringBuilder("/"); 149 | int index = 0; 150 | foreach (var info in patternInfos) 151 | { 152 | if (info.IsConst) 153 | builder.Append(info.Name); 154 | else 155 | { 156 | builder.Append("{").Append(index).Append("}"); 157 | index++; 158 | } 159 | 160 | builder.Append("/"); 161 | } 162 | return builder.ToString().TrimEnd('/'); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Logger/IEventBusLogger.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Diagnostics; 2 | using System; 3 | 4 | namespace MQTTnet.EventBus.Logger 5 | { 6 | public interface IEventBusLogger : IMqttNetScopedLogger 7 | { 8 | IEventBusLogger CreateLogger(string categoryName); 9 | IEventBusLogger CreateLogger(); 10 | 11 | void LogTrace(string message); 12 | void LogInformation(string message); 13 | void LogWarning(string message); 14 | void LogWarning(Exception ex, string message); 15 | void LogError(Exception ex, string message); 16 | } 17 | 18 | public class EventBusLogger : IEventBusLogger 19 | { 20 | private readonly string _source; 21 | private readonly IMqttNetLogger _logger; 22 | private readonly IMqttNetScopedLogger _scopedLogger; 23 | 24 | public EventBusLogger(IMqttNetLogger logger, string categoryName) 25 | { 26 | _source = categoryName; 27 | _logger = logger; 28 | _scopedLogger = CreateScopedLogger(_source); 29 | } 30 | 31 | public IEventBusLogger CreateLogger(string categoryName) 32 | => new EventBusLogger(_logger, categoryName); 33 | 34 | public IEventBusLogger CreateLogger() 35 | => new EventBusLogger(_logger); 36 | 37 | public IMqttNetScopedLogger CreateScopedLogger(string source) 38 | => _logger.CreateScopedLogger(source); 39 | 40 | public void LogError(Exception ex, string message) 41 | => _scopedLogger.Error(ex, message); 42 | 43 | public void LogInformation(string message) 44 | => _scopedLogger.Info(message); 45 | 46 | public void LogTrace(string message) 47 | => _scopedLogger.Verbose(message); 48 | 49 | public void LogWarning(string message) 50 | => _scopedLogger.Warning(message); 51 | 52 | public void LogWarning(Exception ex, string message) 53 | => _scopedLogger.Warning(ex, message); 54 | 55 | public void Publish(MqttNetLogLevel logLevel, string message, object[] parameters, Exception exception) 56 | => _logger.Publish(logLevel, _source, message, parameters, exception); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Logger/IEventBusLogger`.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Diagnostics; 2 | 3 | namespace MQTTnet.EventBus.Logger 4 | { 5 | public interface IEventBusLogger : IEventBusLogger { } 6 | 7 | public class EventBusLogger : EventBusLogger, IEventBusLogger 8 | { 9 | public EventBusLogger(IMqttNetLogger logger) : base(logger, typeof(TCategoryName).FullName) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/MQTTnet.EventBus.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Artyom Tonoyan 6 | Revalcon LLC 7 | @ 2019 Revalcon LLC 8 | 9 | https://github.com/Revalcon/MQTTnet.EventBus 10 | https://github.com/Revalcon/MQTTnet.EventBus 11 | Mqtt MqttNet EventBus Event MQ 12 | true 13 | true 14 | 2.0.0 15 | Library 16 | true 17 | Event-Based framework for distributed applications (MQTT Core transport support). 18 | Apache-2.0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Reflection/ConsumeMethodInvoker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace MQTTnet.EventBus.Reflection 5 | { 6 | public class ConsumeMethodInvoker : IConsumeMethodInvoker 7 | { 8 | private readonly IEventProvider _eventProvider; 9 | 10 | public ConsumeMethodInvoker(IEventProvider eventProvider) 11 | { 12 | _eventProvider = eventProvider; 13 | } 14 | 15 | public Task InvokeAsync(IServiceProvider serviceProvider, SubscriptionInfo subscriptionInfo, MqttApplicationMessageReceivedEventArgs messageReceived) 16 | { 17 | var converterType = _eventProvider.GetConverterType(subscriptionInfo.EventName); 18 | var converter = ((Serializers.IEventDeserializer)serviceProvider.GetService(converterType)); 19 | 20 | var message = messageReceived.ApplicationMessage; 21 | var eventArg = converter.Deserialize(message.Payload); 22 | //_eventProvider.SetTopicInfo(subscriptionInfo.EventName, eventArg, message.Topic); 23 | 24 | var contextType = typeof(EventContext<>).MakeGenericType(subscriptionInfo.EventType); 25 | var context = Activator.CreateInstance(contextType, new object[] { eventArg, messageReceived, _eventProvider }); 26 | 27 | var consumer = serviceProvider.GetService(subscriptionInfo.ConsumerType); 28 | return (Task)consumer.GetType() 29 | .GetMethod(nameof(IConsumer.ConsumeAsync)) 30 | .Invoke(consumer, new object[] { context }); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Reflection/IConsumeMethodInvoker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace MQTTnet.EventBus.Reflection 5 | { 6 | public interface IConsumeMethodInvoker 7 | { 8 | Task InvokeAsync(IServiceProvider serviceProvider, SubscriptionInfo subscriptionInfo, MqttApplicationMessageReceivedEventArgs messageReceived); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Reflection/ReflectionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace MQTTnet.EventBus.Reflection 9 | { 10 | public static class ReflectionHelper 11 | { 12 | public static MethodInfo GetStringFormatMethod() 13 | { 14 | return typeof(string) 15 | .GetMethods(BindingFlags.Public | BindingFlags.Static) 16 | .Where(p => 17 | { 18 | if (p.Name == nameof(string.Format)) 19 | { 20 | var parametrs = p.GetParameters(); 21 | if (parametrs.Length == 2) 22 | { 23 | if (parametrs[0].ParameterType == typeof(string) && parametrs[1].ParameterType == typeof(object[])) 24 | return true; 25 | } 26 | } 27 | 28 | return false; 29 | }) 30 | .FirstOrDefault(); 31 | } 32 | 33 | public static string CreateTopicPattern(Expression> patternExp) 34 | { 35 | var constBuilder = new StringBuilder(); 36 | var members = new List(); 37 | new TopicPatternVisitor(constBuilder, members).Visit(patternExp); 38 | 39 | var format = constBuilder.ToString(); 40 | string pattern = members.Count > 0 ? string.Format(format, members.ToArray()) : format; 41 | return pattern; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Reflection/TopicPatternVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Text; 5 | 6 | namespace MQTTnet.EventBus.Reflection 7 | { 8 | internal class TopicPatternVisitor : ExpressionVisitor 9 | { 10 | private readonly StringBuilder _constBuilder; 11 | private readonly List _members; 12 | 13 | public TopicPatternVisitor(StringBuilder builder, List members) 14 | { 15 | _constBuilder = builder; 16 | _members = members; 17 | } 18 | 19 | public override Expression Visit(Expression node) 20 | { 21 | if (node != null) 22 | { 23 | if (node.NodeType == ExpressionType.Constant) 24 | { 25 | var value = Convert.ToString(((ConstantExpression)node).Value); 26 | if (value.Contains("{0}")) 27 | _constBuilder.Append(value); 28 | else 29 | _members.Add(value); 30 | } 31 | else if (node.NodeType == ExpressionType.MemberAccess) 32 | { 33 | _members.Add("{" + ((MemberExpression)node).Member.Name + "}"); 34 | } 35 | } 36 | 37 | return base.Visit(node); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Serializers/IEventConverter.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.Serializers 2 | { 3 | public interface IEventConverter : IEventSerializer, IEventDeserializer { } 4 | } 5 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Serializers/IEventDeserializer.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.Serializers 2 | { 3 | public interface IEventDeserializer 4 | { 5 | T Deserialize(byte[] value); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Serializers/IEventSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.Serializers 2 | { 3 | public interface IEventSerializer 4 | { 5 | byte[] Serialize(T value); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Serializers/Text/StringConverter.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.EventBus.Serializers.Text 2 | { 3 | public interface IStringConverter : IEventConverter { } 4 | 5 | public class StringConverter : IStringConverter 6 | { 7 | public virtual string Deserialize(byte[] value) => TextConvert.ToUTF8String(value); 8 | 9 | public virtual byte[] Serialize(string value) => TextConvert.ToUTF8ByteArray(value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/Serializers/TextConvert.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace MQTTnet.EventBus.Serializers 4 | { 5 | public static class TextConvert 6 | { 7 | public static string ToUTF8String(byte[] value) => Encoding.UTF8.GetString(value); 8 | 9 | public static byte[] ToUTF8ByteArray(string value) => Encoding.UTF8.GetBytes(value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/StaticCache.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.EventBus.Impl; 2 | 3 | namespace MQTTnet.EventBus 4 | { 5 | public static class StaticCache 6 | { 7 | internal static IEventProvider EventProvider = new EventProvider(); 8 | 9 | public static void Clear() 10 | { 11 | EventProvider = null; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/MQTTnet.EventBus/SubscriptionInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MQTTnet.EventBus 5 | { 6 | public class SubscriptionInfo 7 | { 8 | public string EventName { get; set; } 9 | public string Topic { get; set; } 10 | public Type EventType { get; set; } 11 | public Type ConsumerType { get; set; } 12 | } 13 | 14 | public class SubscriptionInfoEqualityComparer : IEqualityComparer 15 | { 16 | public bool Equals(SubscriptionInfo x, SubscriptionInfo y) 17 | { 18 | if (ReferenceEquals(x, y)) 19 | return true; 20 | if (x is null) 21 | return false; 22 | if (y is null) 23 | return false; 24 | 25 | return x.EventType.Equals(y.EventType) && x.ConsumerType.Equals(y.ConsumerType); 26 | } 27 | 28 | public int GetHashCode(SubscriptionInfo obj) 29 | { 30 | unchecked { return obj.EventType.GetHashCode() ^ obj.ConsumerType.GetHashCode(); } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /tests/MQTTnet.EventBus.Tests/ConsumeMethodInvokerTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using MQTTnet.EventBus.Reflection; 3 | using MQTTnet.EventBus.Serializers; 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace MQTTnet.EventBus.Tests 12 | { 13 | [TestClass] 14 | public class ConsumeMethodInvokerTest 15 | { 16 | [TestMethod] 17 | public async Task TestConsmerMethod() 18 | { 19 | //var eventArgs = new MyEvent { Id = 1, Name = "A1" }; 20 | //var consumer = new MyConsumer(); 21 | 22 | //IConsumeMethodInvoker invoker = new ConsumeMethodInvoker(); 23 | //await invoker.InvokeAsync(consumer, typeof(MyEvent), typeof(JsonDeserializer<>), null); 24 | 25 | //Assert.IsTrue(consumer.Cache.Count == 1); 26 | 27 | //var kvp = consumer.Cache.First(); 28 | //Assert.IsTrue(kvp.Key == eventArgs.Id && kvp.Value == eventArgs.Name); 29 | } 30 | } 31 | 32 | public class MyEvent 33 | { 34 | public int Id { get; set; } 35 | public string Name { get; set; } 36 | } 37 | 38 | public class MyConsumer : IConsumer 39 | { 40 | public Dictionary Cache { get; set; } = new Dictionary(); 41 | 42 | public Task ConsumeAsync(EventContext context) 43 | { 44 | var arg = context.EventArg; 45 | Cache.Add(arg.Id, arg.Name); 46 | return Task.CompletedTask; 47 | } 48 | } 49 | 50 | class JsonDeserializer : IEventDeserializer 51 | { 52 | public T Deserialize(byte[] value) 53 | { 54 | //string jsonMessage = Encoding.UTF8.GetString(value); 55 | string jsonMessage = JsonConvert.SerializeObject(new { Id = 1, Name = "A1" }); 56 | return JsonConvert.DeserializeObject(jsonMessage); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/MQTTnet.EventBus.Tests/MQTTnet.EventBus.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------