├── .github ├── dependabot.yml └── workflows │ ├── pr_validation.yml │ └── publish_nuget.yml ├── .gitignore ├── Akka.Templates.csproj ├── LICENSE ├── README.md ├── RELEASE_NOTES.md ├── build.ps1 ├── docs ├── AkkaStreamsTemplate.md ├── ConsoleTemplate.md └── WebApiTemplate.md ├── install-dev-templates.ps1 ├── logo.png ├── nuget.config ├── scripts ├── bumpVersion.ps1 ├── getReleaseNotes.ps1 └── test-templates.ps1 └── src ├── csharp ├── AkkaConsoleTemplate │ ├── .template.config │ │ ├── dotnetcli.host.json │ │ ├── icon.png │ │ ├── ide.host.json │ │ └── template.json │ ├── AkkaConsoleTemplate.csproj │ ├── HelloActor.cs │ ├── Program.cs │ ├── README.md │ ├── TimerActor.cs │ └── Usings.cs ├── AkkaStreamsTemplate │ ├── .template.config │ │ ├── dotnetcli.host.json │ │ ├── icon.png │ │ ├── ide.host.json │ │ └── template.json │ ├── AkkaStreamsTemplate.csproj │ ├── Program.cs │ ├── README.md │ ├── TransformerActor.cs │ └── Usings.cs └── WebApiTemplate │ ├── .gitattributes │ ├── .github │ ├── dependabot.yaml │ └── workflows │ │ └── pr_validation.yaml │ ├── .gitignore │ ├── .template.config │ ├── dotnetcli.host.json │ ├── icon.png │ ├── ide.host.json │ └── template.json │ ├── Directory.Build.props │ ├── Directory.Packages.props │ ├── README.md │ ├── WebApiTemplate.sln │ ├── docker │ ├── build-docker.cmd │ ├── build-docker.sh │ └── docker-compose.yaml │ ├── global.json │ ├── nuget.config │ ├── src │ ├── WebApiTemplate.App │ │ ├── Actors │ │ │ ├── CounterActor.cs │ │ │ └── GenericChildPerEntityParent.cs │ │ ├── Configuration │ │ │ ├── AkkaConfiguration.cs │ │ │ ├── AkkaSettings.cs │ │ │ └── PetabridgeCmdConfiguration.cs │ │ ├── Controllers │ │ │ └── CounterController.cs │ │ ├── Program.cs │ │ ├── WebApiTemplate.App.csproj │ │ ├── appsettings.Azure.json │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ └── WebApiTemplate.Domain │ │ ├── CounterCommands.cs │ │ ├── CounterEvents.cs │ │ ├── CounterQueries.cs │ │ ├── IWithCounterId.cs │ │ ├── Usings.cs │ │ └── WebApiTemplate.Domain.csproj │ ├── start-azurite.cmd │ ├── start-azurite.sh │ └── tests │ └── WebApiTemplate.App.Tests │ ├── CounterActorSpecs.cs │ ├── Usings.cs │ └── WebApiTemplate.App.Tests.csproj └── fsharp ├── AkkaConsoleTemplate ├── .template.config │ ├── dotnetcli.host.json │ ├── icon.png │ ├── ide.host.json │ └── template.json ├── AkkaConsoleTemplate.fsproj ├── HelloActor.fs ├── Program.fs └── TimerActor.fs └── AkkaStreamsTemplate ├── .template.config ├── dotnetcli.host.json ├── icon.png ├── ide.host.json └── template.json ├── AkkaStreamsTemplate.fsproj ├── Program.fs └── TransformActor.fs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | time: "11:00" 9 | 10 | - package-ecosystem: github-actions 11 | directory: "/src/csharp/AkkaConsoleTemplate" 12 | schedule: 13 | interval: daily 14 | time: "11:00" 15 | 16 | - package-ecosystem: github-actions 17 | directory: "/src/csharp/AkkaStreamsTemplate" 18 | schedule: 19 | interval: daily 20 | time: "11:00" 21 | 22 | - package-ecosystem: github-actions 23 | directory: "/src/WebApiTemplate" 24 | schedule: 25 | interval: daily 26 | time: "11:00" 27 | 28 | - package-ecosystem: nuget 29 | directory: "/src/csharp/AkkaConsoleTemplate" 30 | schedule: 31 | interval: daily 32 | time: "11:00" 33 | 34 | - package-ecosystem: nuget 35 | directory: "/src/csharp/AkkaStreamsTemplate" 36 | schedule: 37 | interval: daily 38 | time: "11:00" 39 | 40 | - package-ecosystem: nuget 41 | directory: "/src/WebApiTemplate" 42 | schedule: 43 | interval: daily 44 | time: "11:00" 45 | 46 | - package-ecosystem: nuget 47 | directory: "/src/fsharp/AkkaConsoleTemplate" 48 | schedule: 49 | interval: daily 50 | time: "11:00" 51 | -------------------------------------------------------------------------------- /.github/workflows/pr_validation.yml: -------------------------------------------------------------------------------- 1 | name: pr_validation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | pull_request: 9 | branches: 10 | - master 11 | - dev 12 | merge_group: 13 | 14 | jobs: 15 | test: 16 | name: Test-${{matrix.os}} 17 | runs-on: ${{matrix.os}} 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest,] 21 | steps: 22 | - name: "Checkout" 23 | uses: actions/checkout@v4 24 | with: 25 | lfs: true 26 | fetch-depth: 0 27 | - name: "Install .NET SDK" 28 | uses: actions/setup-dotnet@v4 29 | with: 30 | dotnet-version: | 31 | 8.0.x 32 | 9.0.x 33 | - name: "Update release notes and version" 34 | shell: pwsh 35 | run: | 36 | ./build.ps1 37 | 38 | - name: "dotnet pack" 39 | run: dotnet pack -c Release -o bin/nuget 40 | 41 | - name: "Install templates" 42 | run: dotnet new install bin/nuget/*.nupkg 43 | 44 | - name: "Test dev templates" 45 | shell: pwsh 46 | run: | 47 | ./scripts/test-templates.ps1 48 | 49 | - name: Archive production artifacts 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: package 53 | path: | 54 | bin/**/*.nupkg -------------------------------------------------------------------------------- /.github/workflows/publish_nuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish NuGet 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish-nuget: 10 | 11 | name: publish-nuget 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Setup .NET Core 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: ${{ env.DOTNET_VERSION }} 23 | 24 | - name: "Update release notes and version" 25 | shell: pwsh 26 | run: | 27 | ./build.ps1 28 | 29 | - name: Create Packages 30 | run: dotnet pack -c Release -o ./output 31 | 32 | - name: Push Packages 33 | run: dotnet nuget push "output/*.nupkg" -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json 34 | 35 | - name: release 36 | uses: actions/create-release@v1 37 | id: create_release 38 | with: 39 | draft: false 40 | prerelease: false 41 | release_name: 'Akka.Templates ${{ github.ref_name }}' 42 | tag_name: ${{ github.ref }} 43 | body_path: RELEASE_NOTES.md 44 | env: 45 | GITHUB_TOKEN: ${{ github.token }} 46 | 47 | - name: Upload Release Asset 48 | uses: AButler/upload-release-assets@v3.0 49 | with: 50 | repo-token: ${{ github.token }} 51 | release-tag: ${{ github.ref_name }} 52 | files: 'output/*.nupkg' 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | [oO]utput/ 25 | x64/ 26 | x86/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # StyleCop 66 | StyleCopReport.xml 67 | 68 | # Files built by Visual Studio 69 | *_i.c 70 | *_p.c 71 | *_h.h 72 | *.ilk 73 | *.meta 74 | *.obj 75 | *.iobj 76 | *.pch 77 | *.pdb 78 | *.ipdb 79 | *.pgc 80 | *.pgd 81 | *.rsp 82 | *.sbr 83 | *.tlb 84 | *.tli 85 | *.tlh 86 | *.tmp 87 | *.tmp_proj 88 | *_wpftmp.csproj 89 | *.log 90 | *.vspscc 91 | *.vssscc 92 | .builds 93 | *.pidb 94 | *.svclog 95 | *.scc 96 | 97 | # Chutzpah Test files 98 | _Chutzpah* 99 | 100 | # Visual C++ cache files 101 | ipch/ 102 | *.aps 103 | *.ncb 104 | *.opendb 105 | *.opensdf 106 | *.sdf 107 | *.cachefile 108 | *.VC.db 109 | *.VC.VC.opendb 110 | 111 | # Visual Studio profiler 112 | *.psess 113 | *.vsp 114 | *.vspx 115 | *.sap 116 | 117 | # Visual Studio Trace Files 118 | *.e2e 119 | 120 | # TFS 2012 Local Workspace 121 | $tf/ 122 | 123 | # Guidance Automation Toolkit 124 | *.gpState 125 | 126 | # ReSharper is a .NET coding add-in 127 | _ReSharper*/ 128 | *.[Rr]e[Ss]harper 129 | *.DotSettings.user 130 | 131 | # TeamCity is a build add-in 132 | _TeamCity* 133 | 134 | # DotCover is a Code Coverage Tool 135 | *.dotCover 136 | 137 | # AxoCover is a Code Coverage Tool 138 | .axoCover/* 139 | !.axoCover/settings.json 140 | 141 | # Visual Studio code coverage results 142 | *.coverage 143 | *.coveragexml 144 | 145 | # NCrunch 146 | _NCrunch_* 147 | .*crunch*.local.xml 148 | nCrunchTemp_* 149 | 150 | # MightyMoose 151 | *.mm.* 152 | AutoTest.Net/ 153 | 154 | # Web workbench (sass) 155 | .sass-cache/ 156 | 157 | # Installshield output folder 158 | [Ee]xpress/ 159 | 160 | # DocProject is a documentation generator add-in 161 | DocProject/buildhelp/ 162 | DocProject/Help/*.HxT 163 | DocProject/Help/*.HxC 164 | DocProject/Help/*.hhc 165 | DocProject/Help/*.hhk 166 | DocProject/Help/*.hhp 167 | DocProject/Help/Html2 168 | DocProject/Help/html 169 | 170 | # Click-Once directory 171 | publish/ 172 | 173 | # Publish Web Output 174 | *.[Pp]ublish.xml 175 | *.azurePubxml 176 | # Note: Comment the next line if you want to checkin your web deploy settings, 177 | # but database connection strings (with potential passwords) will be unencrypted 178 | *.pubxml 179 | *.publishproj 180 | 181 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 182 | # checkin your Azure Web App publish settings, but sensitive information contained 183 | # in these scripts will be unencrypted 184 | PublishScripts/ 185 | 186 | # NuGet Packages 187 | *.nupkg 188 | # NuGet Symbol Packages 189 | *.snupkg 190 | # The packages folder can be ignored because of Package Restore 191 | **/[Pp]ackages/* 192 | # except build/, which is used as an MSBuild target. 193 | !**/[Pp]ackages/build/ 194 | # Uncomment if necessary however generally it will be regenerated when needed 195 | #!**/[Pp]ackages/repositories.config 196 | # NuGet v3's project.json files produces more ignorable files 197 | *.nuget.props 198 | *.nuget.targets 199 | 200 | # Microsoft Azure Build Output 201 | csx/ 202 | *.build.csdef 203 | 204 | # Microsoft Azure Emulator 205 | ecf/ 206 | rcf/ 207 | 208 | # Windows Store app package directories and files 209 | AppPackages/ 210 | BundleArtifacts/ 211 | Package.StoreAssociation.xml 212 | _pkginfo.txt 213 | *.appx 214 | *.appxbundle 215 | *.appxupload 216 | 217 | # Visual Studio cache files 218 | # files ending in .cache can be ignored 219 | *.[Cc]ache 220 | # but keep track of directories ending in .cache 221 | !?*.[Cc]ache/ 222 | 223 | # Others 224 | ClientBin/ 225 | ~$* 226 | *~ 227 | *.dbmdl 228 | *.dbproj.schemaview 229 | *.jfm 230 | *.pfx 231 | *.publishsettings 232 | orleans.codegen.cs 233 | 234 | # Including strong name files can present a security risk 235 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 236 | #*.snk 237 | 238 | # Since there are multiple workflows, uncomment next line to ignore bower_components 239 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 240 | #bower_components/ 241 | 242 | # RIA/Silverlight projects 243 | Generated_Code/ 244 | 245 | # Backup & report files from converting an old project file 246 | # to a newer Visual Studio version. Backup files are not needed, 247 | # because we have git ;-) 248 | _UpgradeReport_Files/ 249 | Backup*/ 250 | UpgradeLog*.XML 251 | UpgradeLog*.htm 252 | ServiceFabricBackup/ 253 | *.rptproj.bak 254 | 255 | # SQL Server files 256 | *.mdf 257 | *.ldf 258 | *.ndf 259 | 260 | # Business Intelligence projects 261 | *.rdl.data 262 | *.bim.layout 263 | *.bim_*.settings 264 | *.rptproj.rsuser 265 | *- [Bb]ackup.rdl 266 | *- [Bb]ackup ([0-9]).rdl 267 | *- [Bb]ackup ([0-9][0-9]).rdl 268 | 269 | # Microsoft Fakes 270 | FakesAssemblies/ 271 | 272 | # GhostDoc plugin setting file 273 | *.GhostDoc.xml 274 | 275 | # Node.js Tools for Visual Studio 276 | .ntvs_analysis.dat 277 | node_modules/ 278 | 279 | # Visual Studio 6 build log 280 | *.plg 281 | 282 | # Visual Studio 6 workspace options file 283 | *.opt 284 | 285 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 286 | *.vbw 287 | 288 | # Visual Studio LightSwitch build output 289 | **/*.HTMLClient/GeneratedArtifacts 290 | **/*.DesktopClient/GeneratedArtifacts 291 | **/*.DesktopClient/ModelManifest.xml 292 | **/*.Server/GeneratedArtifacts 293 | **/*.Server/ModelManifest.xml 294 | _Pvt_Extensions 295 | 296 | # Paket dependency manager 297 | .paket/paket.exe 298 | paket-files/ 299 | 300 | # FAKE - F# Make 301 | .fake/ 302 | 303 | # JetBrains Rider 304 | .idea/ 305 | *.sln.iml 306 | 307 | # CodeRush personal settings 308 | .cr/personal 309 | 310 | # Python Tools for Visual Studio (PTVS) 311 | __pycache__/ 312 | *.pyc 313 | 314 | # Cake - Uncomment if you are using it 315 | # tools/** 316 | # !tools/packages.config 317 | 318 | # Tabs Studio 319 | *.tss 320 | 321 | # Telerik's JustMock configuration file 322 | *.jmconfig 323 | 324 | # BizTalk build output 325 | *.btp.cs 326 | *.btm.cs 327 | *.odx.cs 328 | *.xsd.cs 329 | 330 | # OpenCover UI analysis results 331 | OpenCover/ 332 | 333 | # Azure Stream Analytics local run output 334 | ASALocalRun/ 335 | 336 | # MSBuild Binary and Structured Log 337 | *.binlog 338 | 339 | # NVidia Nsight GPU debugger configuration file 340 | *.nvuser 341 | 342 | # MFractors (Xamarin productivity tool) working folder 343 | .mfractor/ 344 | 345 | # Local History for Visual Studio 346 | .localhistory/ 347 | 348 | # BeatPulse healthcheck temp database 349 | healthchecksdb 350 | 351 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 352 | MigrationBackup/ 353 | 354 | # Ionide (cross platform F# VS Code tools) working folder 355 | .ionide/ 356 | -------------------------------------------------------------------------------- /Akka.Templates.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Template 4 | 1.2.0 5 | Akka.Templates 6 | Akka.NET Project Templates 7 | Copyright © 2013-2025 Akka.NET Team 8 | AkkaDotNet 9 | Templates to use when creating new Akka.NET applications. 10 | dotnet-new;templates;akkadotnet;akka; 11 | * Added F# template support for the [Akka.Streams template](https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/AkkaStreamsTemplate.md) - see the docs for an example 12 | netstandard2.0 13 | true 14 | false 15 | content 16 | $(NoWarn);NU5128 17 | true 18 | logo.png 19 | https://github.com/akkadotnet/akkadotnet-templates 20 | Apache-2.0 21 | $(NoWarn);CS1591;xUnit1013 22 | README.md 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015-2023 .NET Foundation 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # akkadotnet-templates 2 | 3 | ![Akka.NET logo](https://raw.githubusercontent.com/akkadotnet/akkadotnet-templates/dev/logo.png) 4 | 5 | Production-ready `dotnet new` templates for [Akka.NET](https://getakka.net/). 6 | 7 | All of these templates are designed to be simple and provide you with a relatively complete structure to get started developing your own Akka.NET applications from scratch. 8 | 9 | **Upon installing these templates via the `dotnet` CLI, you will have access to them from both the `dotnet` CLI itself and as "new project" template options in any .NET IDE - such as Visual Studio and JetBrains Rider!** 10 | 11 | ## Installation 12 | 13 | To install these templates, just install the `Akka.Templates` package from NuGet: 14 | 15 | ```shell 16 | dotnet new install "Akka.Templates::*" 17 | ``` 18 | 19 | To upgrade these templates to a newer version: 20 | 21 | ```shell 22 | dotnet new update 23 | ``` 24 | 25 | To uninstall these templates from your local machine: 26 | 27 | 28 | ```shell 29 | dotnet new uninstall Akka.Templates 30 | ``` 31 | 32 | ## Available Templates 33 | 34 | The following templates are available as part of the `Akka.Templates` package: 35 | 36 | | Template | Short Name | Description | Languages | 37 | |--------------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------| 38 | | [Akka.Cluster.WebApi](https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/WebApiTemplate.md) | akka.cluster.webapi | A template for building ASP.NET HTTP APIs on top of an Akka.NET Cluster. Uses Akka.Cluster.Sharding and, optionally: Akka.Management + Akka.Persistence.Azure + Akka.Azure.Discovery. This template is meant as a starter for building distributed systems with Akka.NET | C# | 39 | | [Akka.Console](https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/ConsoleTemplate.md) | akka.console | This is a simple template designed to incorporate local [Akka.NET](https://getakka.net/) into a console application. | C#, F# | 40 | | [Akka.Streams](https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/AkkaStreamsTemplate.md) | akka.streams | This is a simple template designed to incorporate [Akka.NET](https://getakka.net/)'s [Akka.Streams APIs](https://getakka.net/articles/streams/introduction.html) into a local console template. | C#, F# | 41 | 42 | See [the official `dotnet new` documentation](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-new) for more information on the sorts of options that are available when using project templates. 43 | 44 | ## Questions, Comments, and Suggestions 45 | We accept pull requests! Please let us know what we can do to make these templates more useful, extensible, or easier to use. 46 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | #### 1.2.0 February 25th 2025 #### 2 | 3 | * Added F# template support for the [Akka.Streams template](https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/AkkaStreamsTemplate.md) - see the docs for an example 4 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | . "$PSScriptRoot\scripts\getReleaseNotes.ps1" 2 | . "$PSScriptRoot\scripts\bumpVersion.ps1" 3 | 4 | Set-StrictMode -Version latest 5 | $ErrorActionPreference = "Stop" 6 | 7 | ###################################################################### 8 | # Step 1: Grab release notes and update solution metadata 9 | ###################################################################### 10 | $releaseNotes = Get-ReleaseNotes -MarkdownFile (Join-Path -Path $PSScriptRoot -ChildPath "RELEASE_NOTES.md") 11 | 12 | # inject release notes into Directory.Buil 13 | UpdateVersionAndReleaseNotes -ReleaseNotesResult $releaseNotes -XmlFilePath (Join-Path -Path $PSScriptRoot -ChildPath "Akka.Templates.csproj") 14 | 15 | Write-Output "Added release notes $releaseNotes" -------------------------------------------------------------------------------- /docs/AkkaStreamsTemplate.md: -------------------------------------------------------------------------------- 1 | # AkkaStreamsTemplate 2 | 3 | This is a simple template designed to incorporate [Akka.NET](https://getakka.net/)'s [Akka.Streams APIs](https://getakka.net/articles/streams/introduction.html) into a local console template. 4 | 5 | ## Installation 6 | 7 | To use this template, first you must install the `Akka.Templates` package from NuGet: 8 | 9 | ```shell 10 | dotnet new -i "Akka.Templates::*" 11 | ``` 12 | 13 | From there, you can use this template via the following command: 14 | 15 | ``` 16 | # For C# 17 | dotnet new akka.streams -n "your-project-name" 18 | 19 | # For F# 20 | dotnet new akka.streams -n "your-project-name" -lang F# 21 | ``` 22 | 23 | ## How It Works 24 | 25 | This template uses [Akka.Hosting](https://github.com/akkadotnet/Akka.Hosting), as a best practice for managing the lifecycle of Akka.NET applications and for integrating with the Microsoft.Extensions ecosystem. 26 | 27 | ```csharp 28 | hostBuilder.ConfigureServices((context, services) => 29 | { 30 | services.AddAkka("MyActorSystem", (builder, sp) => 31 | { 32 | builder 33 | .WithActors((system, registry, resolver) => 34 | { 35 | var helloActor = system.ActorOf(Props.Create(() => new TransformerActor()), "transformer"); 36 | registry.Register(helloActor); 37 | }); 38 | }); 39 | }); 40 | ``` 41 | 42 | However, the real guts of the application happens further down in `Program.cs` - where we use the `IServiceProvider` to resolve both the `ActorSystem` and the `IRequiredActor` in order to use both of those inputs inside our Akka.NET stream: 43 | 44 | ```csharp 45 | var host = hostBuilder.Build(); 46 | 47 | var completionTask = host.RunAsync(); 48 | 49 | // grab the ActorSystem from the DI container 50 | var system = host.Services.GetRequiredService(); 51 | 52 | // grab the ActorRef from the DI container 53 | IActorRef transformer = host.Services.GetRequiredService>().ActorRef; 54 | ``` 55 | 56 | The real guts of this application is, of course, [Akka.Streams](https://getakka.net/articles/streams/introduction.html): 57 | 58 |
59 | C# Implementation 60 | 61 | ```csharp 62 | // create a stream that iterates over the numbers 1 to 100 63 | await Source.From(Enumerable.Range(1, 1000)) 64 | .Where(i => i % 2 == 0) // even numbers only 65 | .Select(i => i.ToString()) // convert to string 66 | .Throttle(10, TimeSpan.FromSeconds(1), 10, ThrottleMode.Shaping) // throttle stream to 10 elements per second 67 | .SelectAsync(5, async str => // invoke actor, up to 5 times in parallel, to convert string 68 | { 69 | using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); 70 | return await transformer.Ask(str, cts.Token); 71 | }) 72 | .RunForeach(Console.WriteLine, system); // write all output to console 73 | ``` 74 |
75 | 76 |
77 | F# Implementation 78 | 79 | ```fsharp 80 | let hostbuilder = HostBuilder() 81 | 82 | hostbuilder.ConfigureServices(fun services -> 83 | services.AddAkka("MyActorSystem", fun b -> 84 | 85 | b.WithActors(fun sys reg -> 86 | let helloActor = sys.ActorOf(Props.Create(fun () -> HelloActor()), "hello-actor") 87 | reg.Register(helloActor)) |> ignore 88 | 89 | b.WithActors(fun sys reg resolver -> 90 | let timerActorProps = resolver.Props() 91 | let timerActor = sys.ActorOf(timerActorProps, "timer-actor") 92 | reg.Register(timerActor)) |> ignore 93 | 94 | ) |> ignore 95 | ) |> ignore 96 | 97 | let host = hostbuilder.Build() 98 | host.RunAsync().Wait() 99 | 100 | // example transform actor 101 | type TransformActor() as this = 102 | inherit ReceiveActor() 103 | 104 | do 105 | this.Receive (fun (message:string)-> 106 | let actor = this :> IInternalActor 107 | actor.ActorContext.Sender.Tell (message.ToUpper()) 108 | ) 109 | 110 | ``` 111 | 112 |
113 | 114 | This is a simple, finite stream that uses some of [Akka.Streams' built-in stages](https://getakka.net/articles/streams/builtinstages.html) to demonstrate asynchronous stream processing as well as [Akka.NET actor integration with Akka.Streams](https://getakka.net/articles/streams/integration.html). 115 | -------------------------------------------------------------------------------- /docs/ConsoleTemplate.md: -------------------------------------------------------------------------------- 1 | # AkkaConsoleTemplate 2 | 3 | This is a simple template designed to incorporate local [Akka.NET](https://getakka.net/) into a console application. The template supports both C# and F#. 4 | 5 | ## Installation 6 | 7 | To use this template, first you must install the `Akka.Templates` package from NuGet: 8 | 9 | ```shell 10 | dotnet new install "Akka.Templates::*" 11 | ``` 12 | 13 | From there, you can use this template via the following command: 14 | 15 | ```shell 16 | # For C# 17 | dotnet new akka.console -n "your-project-name" 18 | 19 | # For F# 20 | dotnet new akka.console -n "your-project-name" -lang F# 21 | ``` 22 | 23 | ## How It Works 24 | 25 | This template uses [Akka.Hosting](https://github.com/akkadotnet/Akka.Hosting), as a best practice for managing the lifecycle of Akka.NET applications and for integrating with the Microsoft.Extensions ecosystem. 26 | 27 |
28 | C# Implementation 29 | 30 | ```csharp 31 | hostBuilder.ConfigureServices((context, services) => 32 | { 33 | services.AddAkka("MyActorSystem", (builder, sp) => 34 | { 35 | builder 36 | .WithActors((system, registry, resolver) => 37 | { 38 | var helloActor = system.ActorOf(Props.Create(() => new HelloActor()), "hello-actor"); 39 | registry.Register(helloActor); 40 | }) 41 | .WithActors((system, registry, resolver) => 42 | { 43 | var timerActorProps = 44 | resolver.Props(); // uses Msft.Ext.DI to inject reference to helloActor 45 | var timerActor = system.ActorOf(timerActorProps, "timer-actor"); 46 | registry.Register(timerActor); 47 | }); 48 | }); 49 | }); 50 | 51 | // Example actor with dependency injection 52 | public class TimerActor : ReceiveActor 53 | { 54 | private readonly IActorRef _helloActor; 55 | 56 | public TimerActor(IRequiredActor helloActor) 57 | { 58 | _helloActor = helloActor.ActorRef; 59 | Receive(message => 60 | { 61 | _helloActor.Tell(message); 62 | }); 63 | } 64 | } 65 | ``` 66 | 67 |
68 | 69 |
70 | F# Implementation 71 | 72 | ```fsharp 73 | let configureAkka (builder: IServiceCollection) = 74 | builder.AddAkka("MyActorSystem", (fun (builder: AkkaConfigurationBuilder) (sp: IServiceProvider) -> 75 | builder 76 | .WithActors(fun (system, registry, _) -> 77 | let helloActor = spawn system "hello-actor" (actorOf HelloActor.actorBehavior) 78 | registry.Register(helloActor)) 79 | .WithActors(fun (system, registry, resolver) -> 80 | let timerActorProps = resolver.Props() 81 | let timerActor = system.ActorOf(timerActorProps, "timer-actor") 82 | registry.Register(timerActor)) 83 | |> ignore)) 84 | 85 | // Example actor with dependency injection 86 | type TimerActor = 87 | inherit ReceiveActor 88 | [] val mutable timer: ITimerScheduler 89 | val mutable helloActor: IActorRef 90 | 91 | new(helloActor: IRequiredActor) = 92 | {helloActor = helloActor.ActorRef} 93 | then 94 | base.Receive(fun message -> helloActor.ActorRef.Tell(message)) 95 | 96 | interface IWithTimers with 97 | member this.Timers with get() = this.timer and set(value) = this.timer <- value 98 | 99 | override this.PreStart() = 100 | let timer = this :> IWithTimers 101 | timer.Timers.StartPeriodicTimer("key", "hello", System.TimeSpan.FromSeconds(1.0) ) 102 | ``` 103 | 104 |
105 | 106 | In both implementations, the `TimerActor` depends on the `HelloActor`, demonstrating how to use the `DependencyResolver` (the `resolver` parameter) to inject a `IRequiredActor` into the `TimerActor`'s constructor. 107 | 108 | In a real-world scenario, you could just resolve the `HelloActor`'s `IActorRef` via a `registry.Get` call, which would technically be simpler and cleaner - but we wanted to demonstrate how to use [`Akka.DependencyInjection`](https://getakka.net/articles/actors/dependency-injection.html) here. 109 | 110 | ## Additional Resources 111 | - [Akka.Hosting Documentation](https://github.com/akkadotnet/Akka.Hosting) 112 | - [Akka.NET Dependency Injection](https://getakka.net/articles/actors/dependency-injection.html) 113 | - [Microsoft.Extensions.Hosting](https://docs.microsoft.com/en-us/dotnet/core/extensions/hosting) -------------------------------------------------------------------------------- /docs/WebApiTemplate.md: -------------------------------------------------------------------------------- 1 | # Akka.Cluster WebApiTemplate 2 | 3 | This template is designed to integrate [Akka.NET](https://getakka.net/) Clusters with ASP.NET Web APIs. 4 | 5 | ## Installation 6 | 7 | To use this template, first you must install the `Akka.Templates` package from NuGet: 8 | 9 | ```shell 10 | dotnet new -i "Akka.Templates::*" 11 | ``` 12 | 13 | From there, you can use this template via the following command: 14 | 15 | ``` 16 | dotnet new akka.cluster.webapi -n "your project name" 17 | ``` 18 | 19 | ## Usage 20 | 21 | ### Key HTTP Routes 22 | 23 | * https://localhost:{ASP_NET_PORT}/swagger/index.html - Swagger endpoint for testing out Akka.NET-powered APIs 24 | * https://localhost:{ASP_NET_PORT}/healthz/akka - Akka.HealthCheck HTTP endpoint 25 | 26 | ### Configuration 27 | 28 | This application is highly configurable and supports the following configuration settings out of the box: 29 | 30 | ```csharp 31 | public class AkkaManagementOptions 32 | { 33 | public bool Enabled { get; set; } = false; 34 | public string Hostname { get; set; } = Dns.GetHostName(); 35 | public int Port { get; set; } = 8558; 36 | public string PortName { get; set; } = "management"; 37 | 38 | public string ServiceName { get; set; } = "akka-management"; 39 | 40 | /// 41 | /// Determines the number of nodes we need to make contact with in order to form a cluster initially. 42 | /// 43 | /// 3 is a safe default value. 44 | /// 45 | public int RequiredContactPointsNr { get; set; } = 3; 46 | 47 | public DiscoveryMethod DiscoveryMethod { get; set; } = DiscoveryMethod.Config; 48 | } 49 | 50 | /// 51 | /// Determines which Akka.Discovery method to use when discovering other nodes to form and join clusters. 52 | /// 53 | public enum DiscoveryMethod 54 | { 55 | Config, 56 | Kubernetes, 57 | AwsEcsTagBased, 58 | AwsEc2TagBased, 59 | AzureTableStorage 60 | } 61 | 62 | public enum PersistenceMode 63 | { 64 | InMemory, 65 | Azure 66 | } 67 | 68 | public class AzureStorageSettings 69 | { 70 | public string ConnectionStringName { get; set; } = "Azurite"; 71 | } 72 | 73 | public class AkkaSettings 74 | { 75 | public string ActorSystemName { get; set; } = "AkkaWeb"; 76 | 77 | public bool UseClustering { get; set; } = true; 78 | 79 | public bool LogConfigOnStart { get; set; } = false; 80 | 81 | public RemoteOptions RemoteOptions { get; set; } = new() 82 | { 83 | // can be overridden via config, but is dynamic by default 84 | PublicHostName = Dns.GetHostName() 85 | }; 86 | 87 | public ClusterOptions ClusterOptions { get; set; } = new ClusterOptions() 88 | { 89 | // use our dynamic local host name by default 90 | SeedNodes = new[] { $"akka.tcp://AkkaWebApi@{Dns.GetHostName()}:8081" } 91 | }; 92 | 93 | public ShardOptions ShardOptions { get; set; } = new ShardOptions(); 94 | 95 | public PersistenceMode PersistenceMode { get; set; } = PersistenceMode.InMemory; 96 | 97 | public AkkaManagementOptions? AkkaManagementOptions { get; set; } 98 | } 99 | ``` 100 | 101 | These will all be extracted some `appSettings.json`, `appSettings.{ASPNET_ENVIRONMENT}.json`, and environment variables. Please see the [Akka.Hosting](https://github.com/akkadotnet/Akka.Hosting) documentation for relevant details. 102 | 103 | #### Running with Azure Persistence and Discovery 104 | 105 | To run this template locally using [Akka.Persistence.Azure](https://github.com/petabridge/Akka.Persistence.Azure) and [Akka.Azure.Discovery](https://github.com/akkadotnet/Akka.Management/tree/dev/src/discovery/azure/Akka.Discovery.Azure), use the `AzureDiscoveryAndStorage` profile included in the `launchSettings.json` _after_ you launch [Azurite](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio), the local emulator for Azure Table / Blob / Queue storage: 106 | 107 | **Step 1 - Launch Azurite** 108 | 109 | Go to the root of your `WebApiTemplate.App` directory and execute `start-azurite.sh` (Linux / OS X) or `start-azurite.cmd` (Windows): 110 | 111 | ```shell 112 | ./start-azurite.sh 113 | ``` 114 | 115 | This will launch a local Azurite instance using Docker behind the scenes, running on all of the default ports. 116 | 117 | **Step 2 - Launch your application using `ASPNET_ENVIRONMENT=Azure`** 118 | 119 | You can set the `ASPNET_ENVIRONMEN` enviroment variable in any number of ways, but we've included a default `launchSettings.json` profile that will do this for you from the `dotnet` CLI: 120 | 121 | ```shell 122 | dotnet run --launch-profile "AzureDiscoveryAndStorage" 123 | ``` 124 | 125 | ## Docker 126 | 127 | By default, if you run the following `dotnet` CLI instruction you will produce a Docker image of your application: 128 | 129 | ```shell 130 | dotnet publish --os linux --arch x64 -c Release -p:PublishProfile=DefaultContainer 131 | ``` 132 | 133 | This will use the [.NET SDk's built-in container support](https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk/) to automatically produce a Linux image of your application using the same .NET version as your application. 134 | 135 | Upon running the command you will see a Docker image added to your local registry that looks like the following: 136 | 137 | ```shell 138 | λ dotnet publish --os linux --arch x64 -c Release -p:PublishProfile=DefaultContainer 139 | MSBuild version 17.4.0+18d5aef85 for .NET 140 | Determining projects to restore... 141 | All projects are up-to-date for restore. 142 | WebApiTemplate.Domain -> E:\Repositories\olympus\akkadotnet-templates\src\WebApiTemplate\src\WebApiTemplate.Domain\bi 143 | n\Release\net7.0\WebApiTemplate.Domain.dll 144 | WebApiTemplate.App -> E:\Repositories\olympus\akkadotnet-templates\src\WebApiTemplate\src\WebApiTemplate.App\bin\Rele 145 | ase\net7.0\linux-x64\WebApiTemplate.App.dll 146 | WebApiTemplate.App -> E:\Repositories\olympus\akkadotnet-templates\src\WebApiTemplate\src\WebApiTemplate.App\bin\Rele 147 | ase\net7.0\linux-x64\publish\ 148 | Building image 'webpapitemplate-app' with tags 1.0.0,latest on top of base image mcr.microsoft.com/dotnet/aspnet:7.0 149 | Pushed container 'webpapitemplate-app:1.0.0' to Docker daemon 150 | Pushed container 'webpapitemplate-app:latest' to Docker daemon 151 | ``` 152 | 153 | The Docker image will take the following forms: 154 | 155 | * `{APPNAME}:{VersionPrefix}` - the name you gave this app when you ran `dotnet new` plus the current `VersionPrefix` and 156 | * `{APPNAME}:latest` - the name you gave this app when you ran `dotnet new` plus the `latest` tag. 157 | 158 | > You can launch a multi-node cluster by updating the [`docker-compose.yml`](https://github.com/akkadotnet/akkadotnet-templates/blob/dev/src/WebApiTemplate/docker/docker-compose.yaml) file to use your specific image name. -------------------------------------------------------------------------------- /install-dev-templates.ps1: -------------------------------------------------------------------------------- 1 | # Inspired by https://github.com/AvaloniaUI/avalonia-dotnet-templates/blob/main/install-dev-templates.ps1 2 | 3 | ###################################################################### 4 | # Step 2: Uninstall previous templates and clean output 5 | ###################################################################### 6 | dotnet new uninstall Akka.Templates 7 | Remove-Item bin/**/*.nupkg 8 | 9 | ###################################################################### 10 | # Step 3: Pack new templates 11 | ###################################################################### 12 | dotnet pack -c Release 13 | # Search Directory 14 | $directoryPath = ".\bin\Release" 15 | 16 | $latestNupkgFile = Get-ChildItem -Path $directoryPath -Recurse -Filter "*.nupkg" | 17 | Where-Object { -not $_.PSIsContainer } | 18 | Sort-Object LastWriteTime -Descending | 19 | Select-Object -First 1 20 | 21 | ###################################################################### 22 | # Step 4: install the templates 23 | ###################################################################### 24 | if ($latestNupkgFile) { 25 | $latestNupkgPath = $latestNupkgFile.FullName 26 | dotnet new install $latestNupkgPath 27 | } -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/akkadotnet-templates/13094cc7f2ec0dd6e9d4392e51caf6dbba3b180e/logo.png -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /scripts/bumpVersion.ps1: -------------------------------------------------------------------------------- 1 | function UpdateVersionAndReleaseNotes { 2 | param ( 3 | [Parameter(Mandatory=$true)] 4 | [PSCustomObject]$ReleaseNotesResult, 5 | 6 | [Parameter(Mandatory=$true)] 7 | [string]$XmlFilePath 8 | ) 9 | 10 | # Load XML 11 | $xmlContent = New-Object XML 12 | $xmlContent.Load($XmlFilePath) 13 | 14 | # Update VersionPrefix and PackageReleaseNotes 15 | $versionPrefixElement = $xmlContent.SelectSingleNode("//VersionPrefix") 16 | $versionPrefixElement.InnerText = $ReleaseNotesResult.Version 17 | 18 | $packageReleaseNotesElement = $xmlContent.SelectSingleNode("//PackageReleaseNotes") 19 | $packageReleaseNotesElement.InnerText = $ReleaseNotesResult.ReleaseNotes 20 | 21 | # Save the updated XML 22 | $xmlContent.Save($XmlFilePath) 23 | } 24 | 25 | # Usage example: 26 | # $notes = Get-ReleaseNotes -MarkdownFile "$PSScriptRoot\RELEASE_NOTES.md" 27 | # $propsPath = Join-Path -Path (Get-Item $PSScriptRoot).Parent.FullName -ChildPath "Directory.Build.props" 28 | # UpdateVersionAndReleaseNotes -ReleaseNotesResult $notes -XmlFilePath $propsPath 29 | -------------------------------------------------------------------------------- /scripts/getReleaseNotes.ps1: -------------------------------------------------------------------------------- 1 | function Get-ReleaseNotes { 2 | param ( 3 | [Parameter(Mandatory=$true)] 4 | [string]$MarkdownFile 5 | ) 6 | 7 | # Read markdown file content 8 | $content = Get-Content -Path $MarkdownFile -Raw 9 | 10 | # Split content based on headers 11 | $sections = $content -split "####" 12 | 13 | # Output object to store result 14 | $outputObject = [PSCustomObject]@{ 15 | Version = $null 16 | Date = $null 17 | ReleaseNotes = $null 18 | } 19 | 20 | # Check if we have at least 3 sections (1. Before the header, 2. Header, 3. Release notes) 21 | if ($sections.Count -ge 3) { 22 | $header = $sections[1].Trim() 23 | $releaseNotes = $sections[2].Trim() 24 | 25 | # Extract version and date from the header 26 | $headerParts = $header -split " ", 2 27 | if ($headerParts.Count -eq 2) { 28 | $outputObject.Version = $headerParts[0] 29 | $outputObject.Date = $headerParts[1] 30 | } 31 | 32 | $outputObject.ReleaseNotes = $releaseNotes 33 | } 34 | 35 | # Return the output object 36 | return $outputObject 37 | } 38 | 39 | # Call function example: 40 | #$result = Get-ReleaseNotes -MarkdownFile "$PSScriptRoot\RELEASE_NOTES.md" 41 | #Write-Output "Version: $($result.Version)" 42 | #Write-Output "Date: $($result.Date)" 43 | #Write-Output "Release Notes:" 44 | #Write-Output $result.ReleaseNotes 45 | -------------------------------------------------------------------------------- /scripts/test-templates.ps1: -------------------------------------------------------------------------------- 1 | # Inspired by https://github.com/AvaloniaUI/avalonia-dotnet-templates/blob/main/tests/build-test.ps1 2 | # Enable common parameters e.g. -Verbose 3 | [CmdletBinding()] 4 | param() 5 | 6 | Set-StrictMode -Version latest 7 | $ErrorActionPreference = "Stop" 8 | 9 | # Taken from psake https://github.com/psake/psake 10 | <# 11 | .SYNOPSIS 12 | This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode 13 | to see if an error occcured. If an error is detected then an exception is thrown. 14 | This function allows you to run command-line programs without having to 15 | explicitly check the $lastexitcode variable. 16 | .EXAMPLE 17 | exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" 18 | #> 19 | function Exec 20 | { 21 | [CmdletBinding()] 22 | param( 23 | [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, 24 | [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ("Error executing command {0}" -f $cmd) 25 | ) 26 | 27 | # Convert the ScriptBlock to a string and expand the variables 28 | $expandedCmdString = $ExecutionContext.InvokeCommand.ExpandString($cmd.ToString()) 29 | Write-Verbose "Executing command: $expandedCmdString" 30 | 31 | Invoke-Command -ScriptBlock $cmd 32 | 33 | if ($lastexitcode -ne 0) { 34 | throw ("Exec: " + $errorMessage) 35 | } 36 | } 37 | 38 | function Test-Template { 39 | param ( 40 | [Parameter(Position=0,Mandatory=1)][string]$template, 41 | [Parameter(Position=1,Mandatory=1)][string]$name, 42 | [Parameter(Position=2,Mandatory=1)][string]$lang, 43 | [Parameter(Position=3,Mandatory=1)][string]$parameterName, 44 | [Parameter(Position=4,Mandatory=1)][string]$value, 45 | [Parameter(Position=5,Mandatory=0)][string]$bl 46 | ) 47 | 48 | $folderName = $name + $parameterName + $value 49 | 50 | # Remove dots and - from folderName because in sln it will cause errors when building project 51 | $folderName = $folderName -replace "[.-]" 52 | 53 | # Create the project 54 | Exec { dotnet new $template -o output//$lang/$folderName -$parameterName $value -lang $lang } 55 | 56 | # Build 57 | Exec { dotnet build output/$lang/$folderName -bl:$bl } 58 | Exec { dotnet test output/$lang/$folderName -bl:$bl } # some templates might include unit tests 59 | Exec { dotnet publish -c Release -t:PublishContainer output/$lang/$folderName -bl:$bl } 60 | } 61 | 62 | function Create-And-Build { 63 | param ( 64 | [Parameter(Position=0,Mandatory=1)][string]$template, 65 | [Parameter(Position=1,Mandatory=1)][string]$name, 66 | [Parameter(Position=2,Mandatory=1)][string]$lang, 67 | [Parameter(Position=3,Mandatory=1)][string]$parameterName, 68 | [Parameter(Position=4,Mandatory=1)][string]$value, 69 | [Parameter(Position=5,Mandatory=0)][string]$bl 70 | ) 71 | 72 | $folderName = $name + $parameterName + $value 73 | 74 | # Remove dots and - from folderName because in sln it will cause errors when building project 75 | $folderName = $folderName -replace "[.-]" 76 | 77 | # Create the project 78 | Exec { dotnet new $template -o output/$lang/$folderName -$parameterName $value -lang $lang } 79 | 80 | # Build 81 | Exec { dotnet build output/$lang/$folderName -bl:$bl } 82 | } 83 | 84 | # Clear file system from possible previous runs 85 | Write-Output "Clearing outputs from possible previous runs" 86 | if (Test-Path "output" -ErrorAction SilentlyContinue) { 87 | Remove-Item -Recurse -Force "output" 88 | } 89 | $outDir = [IO.Path]::GetFullPath([IO.Path]::Combine($pwd, "..", "output")) 90 | if (Test-Path $outDir -ErrorAction SilentlyContinue) { 91 | Remove-Item -Recurse -Force $outDir 92 | } 93 | $binLogDir = [IO.Path]::GetFullPath([IO.Path]::Combine($pwd, "..", "binlog")) 94 | if (Test-Path $binLogDir -ErrorAction SilentlyContinue) { 95 | Remove-Item -Recurse -Force $binLogDir 96 | } 97 | 98 | # Use same log file for all executions 99 | $binlog = [IO.Path]::GetFullPath([IO.Path]::Combine($pwd, "..", "binlog", "test.binlog")) 100 | 101 | Create-And-Build "akka.console" "AkkaConsole" "C#" "f" "net9.0" $binlog 102 | Create-And-Build "akka.console" "AkkaConsole" "C#" "f" "net8.0" $binlog 103 | 104 | Create-And-Build "akka.console" "AkkaConsole" "F#" "f" "net9.0" $binlog 105 | Create-And-Build "akka.console" "AkkaConsole" "F#" "f" "net8.0" $binlog 106 | 107 | Create-And-Build "akka.streams" "AkkaStreams" "C#" "f" "net9.0" $binlog 108 | Create-And-Build "akka.streams" "AkkaStreams" "C#" "f" "net8.0" $binlog 109 | 110 | Create-And-Build "akka.streams" "AkkaStreams" "F#" "f" "net9.0" $binlog 111 | Create-And-Build "akka.streams" "AkkaStreams" "F#" "f" "net8.0" $binlog 112 | 113 | Test-Template "akka.cluster.webapi" "ClusterWebTemplate" "C#" "f" "net9.0" $binlog 114 | Test-Template "akka.cluster.webapi" "ClusterWebTemplate" "C#" "f" "net8.0" $binlog 115 | 116 | # Ignore errors when files are still used by another process -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/.template.config/dotnetcli.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/dotnetcli.host", 3 | "symbolInfo": { 4 | "Framework": { 5 | "longName": "framework" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/akkadotnet-templates/13094cc7f2ec0dd6e9d4392e51caf6dbba3b180e/src/csharp/AkkaConsoleTemplate/.template.config/icon.png -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "defaultSymbolVisibility": true, 4 | "icon": "icon.png", 5 | "learnMoreLink": "https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/ConsoleTemplate.md", 6 | "order": 0, 7 | "symbolInfo": [ 8 | ] 9 | } -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "Akka", 4 | "classifications": ["Akka.NET", "Actors", "Console", "Windows", "Linux", "macOS"], 5 | "name": "Akka.NET Console Application", 6 | "description": "A simple console application that uses Akka.NET and Akka.Hosting.", 7 | "groupIdentity": "Akka.Console", 8 | "identity": "Akka.Console.CSharp", 9 | "shortName": "akka.console", 10 | "defaultName": "AkkaConsole1", 11 | "tags": { 12 | "language": "C#", 13 | "type": "project" 14 | }, 15 | "symbols": { 16 | "Framework": { 17 | "type": "parameter", 18 | "description": "The target framework for the project.", 19 | "datatype": "choice", 20 | "choices": [ 21 | { 22 | "choice": "net8.0", 23 | "description": "Target net8.0" 24 | }, 25 | { 26 | "choice": "net9.0", 27 | "description": "Target net9.0" 28 | } 29 | ], 30 | "replaces": "FrameworkParameter", 31 | "defaultValue": "net9.0" 32 | } 33 | }, 34 | "sourceName": "AkkaConsoleTemplate", 35 | "preferNameDirectory": true, 36 | "primaryOutputs": [ 37 | { "path": "AkkaConsoleTemplate.csproj" }, 38 | { 39 | "condition": "(HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", 40 | "path": "Program.cs" 41 | } 42 | ], 43 | "postActions": [ 44 | ] 45 | } -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/AkkaConsoleTemplate.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | FrameworkParameter 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/HelloActor.cs: -------------------------------------------------------------------------------- 1 | namespace AkkaConsoleTemplate; 2 | 3 | public class HelloActor : ReceiveActor 4 | { 5 | private readonly ILoggingAdapter _log = Context.GetLogger(); 6 | private int _helloCounter = 0; 7 | 8 | public HelloActor() 9 | { 10 | Receive(message => 11 | { 12 | _log.Info("{0} {1}", message, _helloCounter++); 13 | }); 14 | } 15 | } -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/Program.cs: -------------------------------------------------------------------------------- 1 | using Akka.Hosting; 2 | using AkkaConsoleTemplate; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | var hostBuilder = new HostBuilder(); 6 | 7 | hostBuilder.ConfigureServices((context, services) => 8 | { 9 | services.AddAkka("MyActorSystem", (builder, sp) => 10 | { 11 | builder 12 | .WithActors((system, registry, resolver) => 13 | { 14 | var helloActor = system.ActorOf(Props.Create(() => new HelloActor()), "hello-actor"); 15 | registry.Register(helloActor); 16 | }) 17 | .WithActors((system, registry, resolver) => 18 | { 19 | var timerActorProps = 20 | resolver.Props(); // uses Msft.Ext.DI to inject reference to helloActor 21 | var timerActor = system.ActorOf(timerActorProps, "timer-actor"); 22 | registry.Register(timerActor); 23 | }); 24 | }); 25 | }); 26 | 27 | var host = hostBuilder.Build(); 28 | 29 | await host.RunAsync(); -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/README.md: -------------------------------------------------------------------------------- 1 | # Akka.NET Console Template 2 | 3 | This is a simple template designed to incorporate local [Akka.NET](https://getakka.net/) into a console application. 4 | 5 | See https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/ConsoleTemplate.md for complete and current documentation on this template. -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/TimerActor.cs: -------------------------------------------------------------------------------- 1 | using Akka.Hosting; 2 | 3 | namespace AkkaConsoleTemplate; 4 | 5 | public class TimerActor : ReceiveActor, IWithTimers 6 | { 7 | private readonly IActorRef _helloActor; 8 | 9 | public TimerActor(IRequiredActor helloActor) 10 | { 11 | _helloActor = helloActor.ActorRef; 12 | Receive(message => 13 | { 14 | _helloActor.Tell(message); 15 | }); 16 | } 17 | 18 | protected override void PreStart() 19 | { 20 | Timers.StartPeriodicTimer("hello-key", "hello", TimeSpan.FromSeconds(1)); 21 | } 22 | 23 | public ITimerScheduler Timers { get; set; } = null!; // gets set by Akka.NET 24 | } -------------------------------------------------------------------------------- /src/csharp/AkkaConsoleTemplate/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Akka.Actor; 2 | global using Akka.Event; -------------------------------------------------------------------------------- /src/csharp/AkkaStreamsTemplate/.template.config/dotnetcli.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/dotnetcli.host", 3 | "symbolInfo": { 4 | "Framework": { 5 | "longName": "framework" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/csharp/AkkaStreamsTemplate/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/akkadotnet-templates/13094cc7f2ec0dd6e9d4392e51caf6dbba3b180e/src/csharp/AkkaStreamsTemplate/.template.config/icon.png -------------------------------------------------------------------------------- /src/csharp/AkkaStreamsTemplate/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "defaultSymbolVisibility": true, 4 | "icon": "icon.png", 5 | "learnMoreLink": "https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/ConsoleTemplate.md", 6 | "order": 0, 7 | "symbolInfo": [ 8 | ] 9 | } -------------------------------------------------------------------------------- /src/csharp/AkkaStreamsTemplate/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "Akka", 4 | "classifications": ["Akka.NET", "Akka.Streams", "Streaming"], 5 | "name": "Akka.Streams Console Application", 6 | "description": "A simple console application that uses Akka.Streams and Akka.Hosting to asynchonrously process data.", 7 | "groupIdentity": "Akka.Streams", 8 | "identity": "Akka.Streams.CSharp", 9 | "shortName": "akka.streams", 10 | "defaultName": "AkkaStreams1", 11 | "tags": { 12 | "language": "C#", 13 | "type": "project" 14 | }, 15 | "sourceName": "AkkaStreamsTemplate", 16 | "preferNameDirectory": true, 17 | "primaryOutputs": [ 18 | { "path": "AkkaStreamsTemplate.csproj" }, 19 | { 20 | "condition": "(HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", 21 | "path": "Program.cs" 22 | } 23 | ], 24 | "symbols": { 25 | "Framework": { 26 | "type": "parameter", 27 | "description": "The target framework for the project.", 28 | "datatype": "choice", 29 | "choices": [ 30 | { 31 | "choice": "net8.0", 32 | "description": "Target net8.0" 33 | }, 34 | { 35 | "choice": "net9.0", 36 | "description": "Target net9.0" 37 | } 38 | ], 39 | "replaces": "FrameworkParameter", 40 | "defaultValue": "net9.0" 41 | } 42 | }, 43 | "postActions": [ 44 | { 45 | "id": "restore", 46 | "condition": "(!skipRestore)", 47 | "description": "Restore NuGet packages required by this project.", 48 | "manualInstructions": [ 49 | { "text": "Run 'dotnet restore'" } 50 | ], 51 | "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", 52 | "continueOnError": true 53 | }, 54 | { 55 | "id": "editor", 56 | "condition": "(HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", 57 | "description": "Opens Program.cs in the editor", 58 | "manualInstructions": [ ], 59 | "actionId": "84C0DA21-51C8-4541-9940-6CA19AF04EE6", 60 | "args": { 61 | "files": "1" 62 | }, 63 | "continueOnError": true 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /src/csharp/AkkaStreamsTemplate/AkkaStreamsTemplate.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | FrameworkParameter 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/csharp/AkkaStreamsTemplate/Program.cs: -------------------------------------------------------------------------------- 1 | using Akka.Hosting; 2 | using Akka.Streams; 3 | using AkkaStreamsTemplate; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | var hostBuilder = new HostBuilder(); 8 | 9 | hostBuilder.ConfigureServices((context, services) => 10 | { 11 | services.AddAkka("MyActorSystem", (builder, sp) => 12 | { 13 | builder 14 | .WithActors((system, registry, resolver) => 15 | { 16 | var helloActor = system.ActorOf(Props.Create(() => new TransformerActor()), "transformer"); 17 | registry.Register(helloActor); 18 | }); 19 | }); 20 | }); 21 | 22 | var host = hostBuilder.Build(); 23 | 24 | var completionTask = host.RunAsync(); 25 | 26 | // grab the ActorSystem from the DI container 27 | var system = host.Services.GetRequiredService(); 28 | 29 | // grab the ActorRef from the DI container 30 | IActorRef transformer = host.Services.GetRequiredService>().ActorRef; 31 | 32 | // create a stream that iterates over the numbers 1 to 100 33 | await Source.From(Enumerable.Range(1, 1000)) 34 | .Where(i => i % 2 == 0) // even numbers only 35 | .Select(i => i.ToString()) // convert to string 36 | .Throttle(10, TimeSpan.FromSeconds(1), 10, ThrottleMode.Shaping) // throttle stream to 10 elements per second 37 | .SelectAsync(5, async str => // invoke actor, up to 5 times in parallel, to convert string 38 | { 39 | using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); 40 | return await transformer.Ask(str, cts.Token); 41 | }) 42 | .RunForeach(Console.WriteLine, system); // write all output to console 43 | 44 | await completionTask; // wait for the host to shut down -------------------------------------------------------------------------------- /src/csharp/AkkaStreamsTemplate/README.md: -------------------------------------------------------------------------------- 1 | # Akka.NET Streams Template 2 | 3 | This is a simple template designed to incorporate [Akka.NET](https://getakka.net/)'s [Akka.Streams APIs](https://getakka.net/articles/streams/introduction.html) into a local console template. 4 | 5 | See https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/AkkaStreamsTemplate.md for complete and current documentation on this template. -------------------------------------------------------------------------------- /src/csharp/AkkaStreamsTemplate/TransformerActor.cs: -------------------------------------------------------------------------------- 1 | namespace AkkaStreamsTemplate; 2 | 3 | public class TransformerActor : ReceiveActor 4 | { 5 | public TransformerActor() 6 | { 7 | Receive(str => 8 | { 9 | Sender.Tell(str.ToUpperInvariant()); 10 | }); 11 | } 12 | } -------------------------------------------------------------------------------- /src/csharp/AkkaStreamsTemplate/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Akka.Actor; 2 | global using Akka.Event; 3 | global using Akka.Streams.Dsl; -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | 5 | # Custom for Visual Studio 6 | *.cs diff=csharp 7 | *.sln merge=union 8 | *.csproj merge=union 9 | *.vbproj merge=union 10 | *.fsproj merge=union 11 | *.dbproj merge=union 12 | 13 | # Standard to msysgit 14 | *.doc diff=astextplain 15 | *.DOC diff=astextplain 16 | *.docx diff=astextplain 17 | *.DOCX diff=astextplain 18 | *.dot diff=astextplain 19 | *.DOT diff=astextplain 20 | *.pdf diff=astextplain 21 | *.PDF diff=astextplain 22 | *.rtf diff=astextplain 23 | *.RTF diff=astextplain 24 | 25 | # Needed for Mono build shell script 26 | *.sh -text eol=lf 27 | 28 | # Needed for API Approvals 29 | *.txt text eol=crlf 30 | 31 | build.sh eol=lf -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | time: "11:00" 9 | 10 | - package-ecosystem: nuget 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | time: "11:00" 15 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/.github/workflows/pr_validation.yaml: -------------------------------------------------------------------------------- 1 | name: pr_validation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | - main 9 | pull_request: 10 | branches: 11 | - master 12 | - dev 13 | - main 14 | 15 | jobs: 16 | test: 17 | name: Test-${{matrix.os}} 18 | runs-on: ${{matrix.os}} 19 | 20 | strategy: 21 | matrix: 22 | os: [ubuntu-latest, windows-latest] 23 | 24 | steps: 25 | - name: "Checkout" 26 | uses: actions/checkout@v3.0.2 27 | with: 28 | lfs: true 29 | fetch-depth: 0 30 | 31 | - name: "Install .NET SDK" 32 | uses: actions/setup-dotnet@v2.1.0 33 | with: 34 | dotnet-version: | 35 | 8.0.x 36 | global-json-file: "./global.json" 37 | 38 | - name: "dotnet build" 39 | run: dotnet build -c Release 40 | 41 | - name: "dotnet test" 42 | run: dotnet test -c Release 43 | 44 | docker: 45 | name: Docker 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: "Checkout" 49 | uses: actions/checkout@v3.0.2 50 | with: 51 | lfs: true 52 | fetch-depth: 0 53 | 54 | - name: "Install .NET SDK" 55 | uses: actions/setup-dotnet@v2.1.0 56 | with: 57 | dotnet-version: | 58 | 8.0.x 59 | global-json-file: "./global.json" 60 | 61 | - name: "dotnet docker" 62 | run: dotnet publish --os linux --arch x64 -c Release -p:PublishProfile=DefaultContainer 63 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | tools/ 3 | .nuget/ 4 | .dotnet/ 5 | .[Dd][Ss]_[Ss]tore 6 | 7 | ## NBench output 8 | [Pp]erf[Rr]esult*/ 9 | 10 | ## Ignore Visual Studio temporary files, build results, and 11 | ## files generated by popular Visual Studio add-ons. 12 | ## 13 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 14 | 15 | # User-specific files 16 | *.suo 17 | *.user 18 | *.userosscache 19 | *.sln.docstates 20 | 21 | # User-specific files (MonoDevelop/Xamarin Studio) 22 | *.userprefs 23 | 24 | # VS Code files 25 | .vscode/ 26 | 27 | # Build results 28 | [Dd]ebug/ 29 | [Dd]ebugPublic/ 30 | [Rr]elease/ 31 | [Rr]eleases/ 32 | x64/ 33 | x86/ 34 | bld/ 35 | [Bb]in/ 36 | [Oo]bj/ 37 | [Ll]og/ 38 | 39 | #FAKE 40 | .fake 41 | tools/ 42 | 43 | #DocFx output 44 | _site/ 45 | 46 | # Visual Studio 2015 cache/options directory 47 | .vs/ 48 | # Uncomment if you have tasks that create the project's static files in wwwroot 49 | #wwwroot/ 50 | 51 | # MSTest test Results 52 | [Tt]est[Rr]esult*/ 53 | [Bb]uild[Ll]og.* 54 | 55 | # NUNIT 56 | *.VisualState.xml 57 | TestResult.xml 58 | 59 | # Build Results of an ATL Project 60 | [Dd]ebugPS/ 61 | [Rr]eleasePS/ 62 | dlldata.c 63 | 64 | # .NET Core 65 | project.lock.json 66 | project.fragment.lock.json 67 | artifacts/ 68 | **/Properties/launchSettings.json 69 | 70 | *_i.c 71 | *_p.c 72 | *_i.h 73 | *.ilk 74 | *.meta 75 | *.obj 76 | *.pch 77 | *.pdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *.log 88 | *.vspscc 89 | *.vssscc 90 | .builds 91 | *.pidb 92 | *.svclog 93 | *.scc 94 | 95 | # Chutzpah Test files 96 | _Chutzpah* 97 | 98 | # Visual C++ cache files 99 | ipch/ 100 | *.aps 101 | *.ncb 102 | *.opendb 103 | *.opensdf 104 | *.sdf 105 | *.cachefile 106 | *.VC.db 107 | *.VC.VC.opendb 108 | 109 | # Visual Studio profiler 110 | *.psess 111 | *.vsp 112 | *.vspx 113 | *.sap 114 | 115 | # TFS 2012 Local Workspace 116 | $tf/ 117 | 118 | # Guidance Automation Toolkit 119 | *.gpState 120 | 121 | # ReSharper is a .NET coding add-in 122 | _ReSharper*/ 123 | *.[Rr]e[Ss]harper 124 | *.DotSettings.user 125 | 126 | # JustCode is a .NET coding add-in 127 | .JustCode 128 | 129 | # TeamCity is a build add-in 130 | _TeamCity* 131 | 132 | # DotCover is a Code Coverage Tool 133 | *.dotCover 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # TODO: Comment the next line if you want to checkin your web deploy settings 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/packages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/packages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/packages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | 206 | # Visual Studio cache files 207 | # files ending in .cache can be ignored 208 | *.[Cc]ache 209 | # but keep track of directories ending in .cache 210 | !*.[Cc]ache/ 211 | 212 | # Others 213 | ClientBin/ 214 | ~$* 215 | *~ 216 | *.dbmdl 217 | *.dbproj.schemaview 218 | *.jfm 219 | *.pfx 220 | *.publishsettings 221 | orleans.codegen.cs 222 | 223 | # Since there are multiple workflows, uncomment next line to ignore bower_components 224 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 225 | #bower_components/ 226 | 227 | # RIA/Silverlight projects 228 | Generated_Code/ 229 | 230 | # Backup & report files from converting an old project file 231 | # to a newer Visual Studio version. Backup files are not needed, 232 | # because we have git ;-) 233 | _UpgradeReport_Files/ 234 | Backup*/ 235 | UpgradeLog*.XML 236 | UpgradeLog*.htm 237 | 238 | # SQL Server files 239 | *.mdf 240 | *.ldf 241 | *.ndf 242 | 243 | # Business Intelligence projects 244 | *.rdl.data 245 | *.bim.layout 246 | *.bim_*.settings 247 | 248 | # Microsoft Fakes 249 | FakesAssemblies/ 250 | 251 | # GhostDoc plugin setting file 252 | *.GhostDoc.xml 253 | 254 | # Node.js Tools for Visual Studio 255 | .ntvs_analysis.dat 256 | node_modules/ 257 | 258 | # Typescript v1 declaration files 259 | typings/ 260 | 261 | # Visual Studio 6 build log 262 | *.plg 263 | 264 | # Visual Studio 6 workspace options file 265 | *.opt 266 | 267 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 268 | *.vbw 269 | 270 | # Visual Studio LightSwitch build output 271 | **/*.HTMLClient/GeneratedArtifacts 272 | **/*.DesktopClient/GeneratedArtifacts 273 | **/*.DesktopClient/ModelManifest.xml 274 | **/*.Server/GeneratedArtifacts 275 | **/*.Server/ModelManifest.xml 276 | _Pvt_Extensions 277 | 278 | # Paket dependency manager 279 | .paket/paket.exe 280 | paket-files/ 281 | 282 | # FAKE - F# Make 283 | .fake/ 284 | 285 | # JetBrains Rider 286 | .idea/ 287 | *.sln.iml 288 | 289 | # CodeRush 290 | .cr/ 291 | 292 | # Python Tools for Visual Studio (PTVS) 293 | __pycache__/ 294 | *.pyc 295 | 296 | # Cake - Uncomment if you are using it 297 | # tools/** 298 | # !tools/packages.config 299 | 300 | # Telerik's JustMock configuration file 301 | *.jmconfig 302 | 303 | # BizTalk build output 304 | *.btp.cs 305 | *.btm.cs 306 | *.odx.cs 307 | *.xsd.cs -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/.template.config/dotnetcli.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/dotnetcli.host", 3 | "symbolInfo": { 4 | "Framework": { 5 | "longName": "framework" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/akkadotnet-templates/13094cc7f2ec0dd6e9d4392e51caf6dbba3b180e/src/csharp/WebApiTemplate/.template.config/icon.png -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "defaultSymbolVisibility": true, 4 | "icon": "icon.png", 5 | "learnMoreLink": "https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/WebApiTemplate.md", 6 | "order": 0, 7 | "symbolInfo": [ 8 | ] 9 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "Akka", 4 | "classifications": ["Akka.NET", "Akka.Cluster", "Web API","Cloud", "Service", "Web"], 5 | "name": "Akka.NET + Akka.Cluster + ASP.NET Core HTTP API", 6 | "description": "An out-of-the-box solution for creating an ASP.NET API integrated with Akka.NET.", 7 | "groupIdentity": "Akka.Cluster.WebApi", 8 | "identity": "Akka.Cluster.WebApi.CSharp", 9 | "shortName": "akka.cluster.webapi", 10 | "defaultName": "AkkaHttpApi1", 11 | "tags": { 12 | "language": "C#", 13 | "type": "solution" 14 | }, 15 | "symbols": { 16 | "Framework": { 17 | "type": "parameter", 18 | "description": "The target framework for the project.", 19 | "datatype": "choice", 20 | "choices": [ 21 | { 22 | "choice": "net8.0", 23 | "description": "Target net8.0" 24 | }, 25 | { 26 | "choice": "net9.0", 27 | "description": "Target net9.0" 28 | } 29 | ], 30 | "replaces": "FrameworkParameter", 31 | "defaultValue": "net9.0" 32 | }, 33 | }, 34 | "sourceName": "WebApiTemplate", 35 | "preferNameDirectory": true, 36 | "primaryOutputs": [{ "path": "WebApiTemplate.sln" }] 37 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Copyright © 2023 Your Company 4 | $(NoWarn);CS1591 5 | 1.0.0 6 | 7 | 8 | 9 | latest 10 | enable 11 | enable 12 | 13 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | 7 | 8 | 1.5.38 9 | 1.5.38 10 | 1.5.37 11 | 1.4.4 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/README.md: -------------------------------------------------------------------------------- 1 | # Akka.NET WebApi Template 2 | 3 | This template is designed to integrate [Akka.NET](https://getakka.net/) with ASP.NET Web APIs. 4 | 5 | See https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/WebApiTemplate.md for complete and current documentation on this template. 6 | 7 | ## Key HTTP Routes 8 | 9 | * https://localhost:{ASP_NET_PORT}/swagger/index.html - Swagger endpoint for testing out Akka.NET-powered APIs 10 | * https://localhost:{ASP_NET_PORT}/healthz/akka - Akka.HealthCheck HTTP endpoint 11 | 12 | ## Petabridge.Cmd Support 13 | 14 | This project is designed to work with [Petabridge.Cmd](https://cmd.petabridge.com/). For instance, if you want to check with the status of your Akka.NET Cluster, just run: 15 | 16 | ```shell 17 | pbm cluster show 18 | ``` 19 | 20 | > NOTE: Petabridge.Cmd binds to [0.0.0.0:9110] on all hosts by default - if you launch multiple instances of this application on the same host you'll see "socket already in use" exceptions raised by the .NET runtime. These are fine - it just means that we can't open Petabridge.Cmd again on that process. 21 | > 22 | > You can configure the Petabridge.Cmd host to run on port 0 if you want it to be accessible across multiple instances on the same host. -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/WebApiTemplate.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiTemplate.Domain", "src\WebApiTemplate.Domain\WebApiTemplate.Domain.csproj", "{13DA159F-364C-4CD3-87C7-6DB70FE2E474}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{9A1C59AB-1427-46BB-96E9-514C66BC84B8}" 9 | ProjectSection(SolutionItems) = preProject 10 | Directory.Build.props = Directory.Build.props 11 | Directory.Packages.props = Directory.Packages.props 12 | nuget.config = nuget.config 13 | EndProjectSection 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiTemplate.App", "src\WebApiTemplate.App\WebApiTemplate.App.csproj", "{D6A03DB7-E660-4FF2-B584-EE01B4A56A59}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiTemplate.App.Tests", "tests\WebApiTemplate.App.Tests\WebApiTemplate.App.Tests.csproj", "{61DB61BB-62AC-49D4-823B-A3A0E4A766BD}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {13DA159F-364C-4CD3-87C7-6DB70FE2E474}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {13DA159F-364C-4CD3-87C7-6DB70FE2E474}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {13DA159F-364C-4CD3-87C7-6DB70FE2E474}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {13DA159F-364C-4CD3-87C7-6DB70FE2E474}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {D6A03DB7-E660-4FF2-B584-EE01B4A56A59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {D6A03DB7-E660-4FF2-B584-EE01B4A56A59}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {D6A03DB7-E660-4FF2-B584-EE01B4A56A59}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {D6A03DB7-E660-4FF2-B584-EE01B4A56A59}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {61DB61BB-62AC-49D4-823B-A3A0E4A766BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {61DB61BB-62AC-49D4-823B-A3A0E4A766BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {61DB61BB-62AC-49D4-823B-A3A0E4A766BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {61DB61BB-62AC-49D4-823B-A3A0E4A766BD}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/docker/build-docker.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | dotnet publish ../src/WebApiTemplate.App/WebApiTemplate.App.csproj --os linux --arch x64 -c Release -p:PublishProfile=DefaultContainer -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/docker/build-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | dotnet publish ../src/WebApiTemplate.App/WebApiTemplate.App.csproj --os linux --arch x64 -c Release -p:PublishProfile=DefaultContainer -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | azurite: 5 | image: mcr.microsoft.com/azure-storage/azurite:latest 6 | hostname: azurite 7 | ports: 8 | - '10000:10000' 9 | - '10001:10001' 10 | - '10002:10002' 11 | pbmhost: 12 | image: webpapitemplate-app:latest 13 | hostname: pbm-host 14 | ports: 15 | - '9110:9110' 16 | - '8080:80' 17 | - '8079:443' 18 | environment: 19 | ASPNETCORE_ENVIRONMENT: Azure 20 | ConnectionStrings__Azurite: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;QueueEndpoint=http://azurite:10001/devstoreaccount1;TableEndpoint=http://azurite:10002/devstoreaccount1;" 21 | AkkaSettings__AkkaManagementOptions__Port: "8558" 22 | AkkaSettings__RemoteOptions__Port: "8557" 23 | depends_on: 24 | - "azurite" 25 | 26 | hosts: 27 | image: webpapitemplate-app:latest 28 | deploy: 29 | replicas: 3 30 | environment: 31 | ASPNETCORE_ENVIRONMENT: Azure 32 | ConnectionStrings__Azurite: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;QueueEndpoint=http://azurite:10001/devstoreaccount1;TableEndpoint=http://azurite:10002/devstoreaccount1;" 33 | AkkaSettings__AkkaManagementOptions__Port: "8558" 34 | AkkaSettings__RemoteOptions__Port: "8557" 35 | depends_on: 36 | - "azurite" -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "rollForward": "latestMinor", 4 | "version": "8.0.101" 5 | } 6 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/Actors/CounterActor.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using Akka.Event; 3 | using Akka.Persistence; 4 | using WebApiTemplate.Domain; 5 | 6 | namespace WebApiTemplate.App.Actors; 7 | 8 | public record Counter(string CounterId, int CurrentValue) 9 | { 10 | } 11 | 12 | public static class CounterExtensions 13 | { 14 | public static CounterCommandResponse ProcessCommand(this Counter counter, ICounterCommand command) 15 | { 16 | return command switch 17 | { 18 | IncrementCounterCommand increment => new CounterCommandResponse(counter.CounterId, true, 19 | new CounterValueIncremented(counter.CounterId, increment.Amount, 20 | increment.Amount + counter.CurrentValue)), 21 | SetCounterCommand set => new CounterCommandResponse(counter.CounterId, true, 22 | new CounterValueSet(counter.CounterId, set.Value)), 23 | _ => throw new InvalidOperationException($"Unknown command type: {command.GetType().Name}") 24 | }; 25 | } 26 | 27 | public static Counter ApplyEvent(this Counter counter, ICounterEvent @event) 28 | { 29 | return @event switch 30 | { 31 | CounterValueIncremented increment => counter with {CurrentValue = increment.NewValue}, 32 | CounterValueSet set => counter with {CurrentValue = set.NewValue}, 33 | _ => throw new InvalidOperationException($"Unknown event type: {@event.GetType().Name}") 34 | }; 35 | } 36 | } 37 | 38 | public sealed class CounterActor : ReceivePersistentActor 39 | { 40 | // currently, do not persist subscribers, but would be easy to add 41 | private readonly HashSet _subscribers = new(); 42 | private Counter _counter; 43 | private readonly ILoggingAdapter _log = Context.GetLogger(); 44 | 45 | public CounterActor(string counterName) 46 | { 47 | // distinguish both type and entity Id in the EventJournal 48 | PersistenceId = $"Counter_{counterName}"; 49 | _counter = new Counter(counterName, 0); 50 | 51 | 52 | Recover(offer => 53 | { 54 | if (offer.Snapshot is Counter c) 55 | { 56 | _counter = c; 57 | _log.Info("Recovered initial count value of [{0}]", c); 58 | } 59 | }); 60 | 61 | Recover(@event => 62 | { 63 | _counter = _counter.ApplyEvent(@event); 64 | }); 65 | 66 | Command(f => Sender.Tell(_counter)); 67 | 68 | Command(subscribe => 69 | { 70 | _subscribers.Add(subscribe.Subscriber); 71 | Sender.Tell(new CounterCommandResponse(_counter.CounterId, true)); 72 | Context.Watch(subscribe.Subscriber); 73 | }); 74 | 75 | Command(counter => 76 | { 77 | Context.Unwatch(counter.Subscriber); 78 | _subscribers.Remove(counter.Subscriber); 79 | }); 80 | 81 | Command(cmd => 82 | { 83 | var response = _counter.ProcessCommand(cmd); 84 | 85 | if (!response.IsSuccess) 86 | { 87 | Sender.Tell(response); 88 | return; 89 | } 90 | 91 | if (response.Event != null) // only persist if there is an event to persist 92 | { 93 | Persist(response.Event, @event => 94 | { 95 | _counter = _counter.ApplyEvent(@event); 96 | _log.Info("Updated counter via {0} - new value is {1}", @event, _counter.CurrentValue); 97 | Sender.Tell(response); 98 | 99 | // push events to all subscribers 100 | foreach (var s in _subscribers) 101 | { 102 | s.Tell(@event); 103 | } 104 | SaveSnapshotWhenAble(); 105 | }); 106 | } 107 | }); 108 | 109 | Command(success => 110 | { 111 | // delete all older snapshots (but leave journal intact, in case we want to do projections with that data) 112 | DeleteSnapshots(new SnapshotSelectionCriteria(success.Metadata.SequenceNr - 1)); 113 | }); 114 | } 115 | 116 | private void SaveSnapshotWhenAble() 117 | { 118 | // save a new snapshot every 25 events, in order to keep recovery times bounded 119 | if (LastSequenceNr % 25 == 0) 120 | { 121 | SaveSnapshot(_counter); 122 | } 123 | } 124 | 125 | public override string PersistenceId { get; } 126 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/Actors/GenericChildPerEntityParent.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using Akka.Cluster.Sharding; 3 | 4 | namespace WebApiTemplate.App.Actors; 5 | 6 | /// 7 | /// A generic "child per entity" parent actor. 8 | /// 9 | /// 10 | /// Intended for simplifying unit tests where we don't want to use Akka.Cluster.Sharding. 11 | /// 12 | public sealed class GenericChildPerEntityParent : ReceiveActor 13 | { 14 | public static Props Props(IMessageExtractor extractor, Func propsFactory) 15 | { 16 | return Akka.Actor.Props.Create(() => new GenericChildPerEntityParent(extractor, propsFactory)); 17 | } 18 | 19 | /* 20 | * Re-use Akka.Cluster.Sharding's infrastructure here to keep things simple. 21 | */ 22 | private readonly IMessageExtractor _extractor; 23 | private Func _propsFactory; 24 | 25 | public GenericChildPerEntityParent(IMessageExtractor extractor, Func propsFactory) 26 | { 27 | _extractor = extractor; 28 | _propsFactory = propsFactory; 29 | 30 | ReceiveAny(o => 31 | { 32 | var entityId = _extractor.EntityId(o); 33 | if (string.IsNullOrEmpty(entityId)) 34 | return; 35 | Context.Child(entityId).GetOrElse(() => Context.ActorOf(propsFactory(entityId), entityId)) 36 | .Forward(_extractor.EntityMessage(o)); 37 | }); 38 | } 39 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/Configuration/AkkaConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Akka.Actor; 3 | using Akka.Cluster.Hosting; 4 | using Akka.Cluster.Sharding; 5 | using Akka.Configuration; 6 | using Akka.Discovery.Azure; 7 | using Akka.Discovery.Config.Hosting; 8 | using Akka.Hosting; 9 | using Akka.Management; 10 | using Akka.Management.Cluster.Bootstrap; 11 | using Akka.Persistence.Azure; 12 | using Akka.Persistence.Azure.Hosting; 13 | using Akka.Persistence.Hosting; 14 | using Akka.Remote.Hosting; 15 | using Akka.Util; 16 | using WebApiTemplate.App.Actors; 17 | using WebApiTemplate.Domain; 18 | 19 | namespace WebApiTemplate.App.Configuration; 20 | 21 | public static class AkkaConfiguration 22 | { 23 | public static IServiceCollection ConfigureWebApiAkka(this IServiceCollection services, IConfiguration configuration, 24 | Action additionalConfig) 25 | { 26 | var akkaSettings = configuration.GetRequiredSection("AkkaSettings").Get(); 27 | Debug.Assert(akkaSettings != null, nameof(akkaSettings) + " != null"); 28 | 29 | services.AddSingleton(akkaSettings); 30 | 31 | return services.AddAkka(akkaSettings.ActorSystemName, (builder, sp) => 32 | { 33 | builder.ConfigureActorSystem(sp); 34 | additionalConfig(builder, sp); 35 | }); 36 | } 37 | 38 | public static AkkaConfigurationBuilder ConfigureActorSystem(this AkkaConfigurationBuilder builder, 39 | IServiceProvider sp) 40 | { 41 | var settings = sp.GetRequiredService(); 42 | 43 | return builder 44 | .ConfigureLoggers(configBuilder => 45 | { 46 | configBuilder.LogConfigOnStart = settings.LogConfigOnStart; 47 | configBuilder.AddLoggerFactory(); 48 | }) 49 | .ConfigureNetwork(sp) 50 | .ConfigurePersistence(sp) 51 | .ConfigureCounterActors(sp); 52 | } 53 | 54 | public static AkkaConfigurationBuilder ConfigureNetwork(this AkkaConfigurationBuilder builder, 55 | IServiceProvider serviceProvider) 56 | { 57 | var settings = serviceProvider.GetRequiredService(); 58 | var configuration = serviceProvider.GetRequiredService(); 59 | 60 | if (!settings.UseClustering) 61 | return builder; 62 | 63 | builder 64 | .WithRemoting(settings.RemoteOptions); 65 | 66 | if (settings.AkkaManagementOptions is { Enabled: true }) 67 | { 68 | // need to delete seed-nodes so Akka.Management will take precedence 69 | var clusterOptions = settings.ClusterOptions; 70 | clusterOptions.SeedNodes = Array.Empty(); 71 | 72 | builder 73 | .WithClustering(clusterOptions) 74 | .WithAkkaManagement(hostName: settings.AkkaManagementOptions.Hostname, 75 | settings.AkkaManagementOptions.Port) 76 | .WithClusterBootstrap(serviceName: settings.AkkaManagementOptions.ServiceName, 77 | portName: settings.AkkaManagementOptions.PortName, 78 | requiredContactPoints: settings.AkkaManagementOptions.RequiredContactPointsNr); 79 | 80 | switch (settings.AkkaManagementOptions.DiscoveryMethod) 81 | { 82 | case DiscoveryMethod.Kubernetes: 83 | break; 84 | case DiscoveryMethod.AwsEcsTagBased: 85 | break; 86 | case DiscoveryMethod.AwsEc2TagBased: 87 | break; 88 | case DiscoveryMethod.AzureTableStorage: 89 | { 90 | var connectionStringName = configuration.GetSection("AzureStorageSettings") 91 | .Get()?.ConnectionStringName; 92 | Debug.Assert(connectionStringName != null, nameof(connectionStringName) + " != null"); 93 | var connectionString = configuration.GetConnectionString(connectionStringName); 94 | 95 | builder.WithAzureDiscovery(options => 96 | { 97 | options.ServiceName = settings.AkkaManagementOptions.ServiceName; 98 | options.ConnectionString = connectionString; 99 | }); 100 | break; 101 | } 102 | case DiscoveryMethod.Config: 103 | { 104 | builder 105 | .WithConfigDiscovery(options => 106 | { 107 | options.Services.Add(new Service 108 | { 109 | Name = settings.AkkaManagementOptions.ServiceName, 110 | Endpoints = new[] 111 | { 112 | $"{settings.AkkaManagementOptions.Hostname}:{settings.AkkaManagementOptions.Port}", 113 | } 114 | }); 115 | }); 116 | break; 117 | } 118 | default: 119 | throw new ArgumentOutOfRangeException(); 120 | } 121 | } 122 | else 123 | { 124 | builder.WithClustering(settings.ClusterOptions); 125 | } 126 | 127 | return builder; 128 | } 129 | 130 | public static AkkaConfigurationBuilder ConfigurePersistence(this AkkaConfigurationBuilder builder, 131 | IServiceProvider serviceProvider) 132 | { 133 | var settings = serviceProvider.GetRequiredService(); 134 | var configuration = serviceProvider.GetRequiredService(); 135 | 136 | switch (settings.PersistenceMode) 137 | { 138 | case PersistenceMode.InMemory: 139 | return builder.WithInMemoryJournal().WithInMemorySnapshotStore(); 140 | case PersistenceMode.Azure: 141 | { 142 | var connectionStringName = configuration.GetSection("AzureStorageSettings") 143 | .Get()?.ConnectionStringName; 144 | Debug.Assert(connectionStringName != null, nameof(connectionStringName) + " != null"); 145 | var connectionString = configuration.GetConnectionString(connectionStringName); 146 | Debug.Assert(connectionString != null, nameof(connectionString) + " != null"); 147 | 148 | return builder.WithAzurePersistence(connectionString); 149 | } 150 | default: 151 | throw new ArgumentOutOfRangeException(); 152 | } 153 | } 154 | 155 | public static AkkaConfigurationBuilder ConfigureCounterActors(this AkkaConfigurationBuilder builder, 156 | IServiceProvider serviceProvider) 157 | { 158 | var settings = serviceProvider.GetRequiredService(); 159 | var extractor = CreateCounterMessageRouter(); 160 | 161 | if (settings.UseClustering) 162 | { 163 | return builder.WithShardRegion("counter", 164 | (system, registry, resolver) => s => Props.Create(() => new CounterActor(s)), 165 | extractor, settings.ShardOptions); 166 | } 167 | 168 | return builder.WithActors((system, registry, resolver) => 169 | { 170 | var parent = 171 | system.ActorOf( 172 | GenericChildPerEntityParent.Props(extractor, s => Props.Create(() => new CounterActor(s))), 173 | "counters"); 174 | registry.Register(parent); 175 | }); 176 | } 177 | 178 | public static HashCodeMessageExtractor CreateCounterMessageRouter() 179 | { 180 | return HashCodeMessageExtractor.Create(30, o => 181 | { 182 | return o switch 183 | { 184 | IWithCounterId counterId => counterId.CounterId, 185 | _ => null 186 | }; 187 | }, o => o); 188 | } 189 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/Configuration/AkkaSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Akka.Cluster.Hosting; 3 | using Akka.Remote.Hosting; 4 | 5 | namespace WebApiTemplate.App.Configuration; 6 | 7 | public class AkkaManagementOptions 8 | { 9 | public bool Enabled { get; set; } = false; 10 | public string Hostname { get; set; } = Dns.GetHostName(); 11 | public int Port { get; set; } = 8558; 12 | public string PortName { get; set; } = "management"; 13 | 14 | public string ServiceName { get; set; } = "akka-management"; 15 | 16 | /// 17 | /// Determines the number of nodes we need to make contact with in order to form a cluster initially. 18 | /// 19 | /// 3 is a safe default value. 20 | /// 21 | public int RequiredContactPointsNr { get; set; } = 3; 22 | 23 | public DiscoveryMethod DiscoveryMethod { get; set; } = DiscoveryMethod.Config; 24 | } 25 | 26 | /// 27 | /// Determines which Akka.Discovery method to use when discovering other nodes to form and join clusters. 28 | /// 29 | public enum DiscoveryMethod 30 | { 31 | Config, 32 | Kubernetes, 33 | AwsEcsTagBased, 34 | AwsEc2TagBased, 35 | AzureTableStorage 36 | } 37 | 38 | public enum PersistenceMode 39 | { 40 | InMemory, 41 | Azure 42 | } 43 | 44 | public class AzureStorageSettings 45 | { 46 | public string ConnectionStringName { get; set; } = "Azurite"; 47 | } 48 | 49 | public class AkkaSettings 50 | { 51 | public string ActorSystemName { get; set; } = "AkkaWeb"; 52 | 53 | public bool UseClustering { get; set; } = true; 54 | 55 | public bool LogConfigOnStart { get; set; } = false; 56 | 57 | public RemoteOptions RemoteOptions { get; set; } = new() 58 | { 59 | // can be overridden via config, but is dynamic by default 60 | PublicHostName = Dns.GetHostName() 61 | }; 62 | 63 | public ClusterOptions ClusterOptions { get; set; } = new ClusterOptions() 64 | { 65 | // use our dynamic local host name by default 66 | SeedNodes = new[] { $"akka.tcp://AkkaWebApi@{Dns.GetHostName()}:8081" } 67 | }; 68 | 69 | public ShardOptions ShardOptions { get; set; } = new ShardOptions(); 70 | 71 | public PersistenceMode PersistenceMode { get; set; } = PersistenceMode.InMemory; 72 | 73 | public AkkaManagementOptions? AkkaManagementOptions { get; set; } 74 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/Configuration/PetabridgeCmdConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Akka.Hosting; 2 | using Petabridge.Cmd.Cluster; 3 | using Petabridge.Cmd.Cluster.Sharding; 4 | using Petabridge.Cmd.Host; 5 | using Petabridge.Cmd.Remote; 6 | 7 | namespace WebApiTemplate.App.Configuration; 8 | 9 | public static class PetabridgeCmdConfiguration 10 | { 11 | public static AkkaConfigurationBuilder ConfigurePetabridgeCmd(this AkkaConfigurationBuilder builder) 12 | { 13 | return builder.AddPetabridgeCmd(cmd => 14 | { 15 | cmd.RegisterCommandPalette(ClusterCommands.Instance); 16 | cmd.RegisterCommandPalette(new RemoteCommands()); 17 | cmd.RegisterCommandPalette(ClusterShardingCommands.Instance); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/Controllers/CounterController.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using Akka.Hosting; 3 | using Microsoft.AspNetCore.Mvc; 4 | using WebApiTemplate.App.Actors; 5 | using WebApiTemplate.Domain; 6 | 7 | namespace WebApiTemplate.App.Controllers; 8 | 9 | [ApiController] 10 | [Route("[controller]")] 11 | public class CounterController : ControllerBase 12 | { 13 | private readonly ILogger _logger; 14 | private readonly IActorRef _counterActor; 15 | 16 | public CounterController(ILogger logger, IRequiredActor counterActor) 17 | { 18 | _logger = logger; 19 | _counterActor = counterActor.ActorRef; 20 | } 21 | 22 | [HttpGet("{counterId}")] 23 | public async Task Get(string counterId) 24 | { 25 | var counter = await _counterActor.Ask(new FetchCounter(counterId), TimeSpan.FromSeconds(5)); 26 | return counter; 27 | } 28 | 29 | [HttpPost("{counterId}")] 30 | public async Task Post(string counterId, [FromBody] int increment) 31 | { 32 | var result = await _counterActor.Ask(new IncrementCounterCommand(counterId, increment), TimeSpan.FromSeconds(5)); 33 | if (!result.IsSuccess) 34 | { 35 | return BadRequest(); 36 | } 37 | 38 | return Ok(result.Event); 39 | } 40 | 41 | [HttpPut("{counterId}")] 42 | public async Task Put(string counterId, [FromBody] int counterValue) 43 | { 44 | var result = await _counterActor.Ask(new SetCounterCommand(counterId, counterValue), TimeSpan.FromSeconds(5)); 45 | if (!result.IsSuccess) 46 | { 47 | return BadRequest(); 48 | } 49 | 50 | return Ok(result.Event); 51 | } 52 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/Program.cs: -------------------------------------------------------------------------------- 1 | using Akka.HealthCheck.Hosting; 2 | using Akka.HealthCheck.Hosting.Web; 3 | using WebApiTemplate.App.Configuration; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Development"; 8 | 9 | /* 10 | * CONFIGURATION SOURCES 11 | */ 12 | builder.Configuration 13 | .AddJsonFile("appsettings.json") 14 | .AddJsonFile($"appsettings.{environment}.json", optional: true) 15 | .AddEnvironmentVariables(); 16 | 17 | // Add services to the container. 18 | builder.Services.WithAkkaHealthCheck(HealthCheckType.All); 19 | builder.Services.ConfigureWebApiAkka(builder.Configuration, (akkaConfigurationBuilder, serviceProvider) => 20 | { 21 | // we configure instrumentation separately from the internals of the ActorSystem 22 | akkaConfigurationBuilder.ConfigurePetabridgeCmd(); 23 | akkaConfigurationBuilder.WithWebHealthCheck(serviceProvider); 24 | }); 25 | 26 | builder.Services.AddControllers(); 27 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 28 | builder.Services.AddEndpointsApiExplorer(); 29 | builder.Services.AddSwaggerGen(); 30 | 31 | var app = builder.Build(); 32 | 33 | // Configure the HTTP request pipeline. 34 | if (app.Environment.IsDevelopment() || app.Environment.EnvironmentName.Equals("Azure")) 35 | { 36 | app.UseSwagger(); 37 | app.UseSwaggerUI(); 38 | } 39 | 40 | app.UseHttpsRedirection(); 41 | app.MapAkkaHealthCheckRoutes(optionConfigure: (_, opt) => 42 | { 43 | // Use a custom response writer to output a json of all reported statuses 44 | opt.ResponseWriter = Helper.JsonResponseWriter; 45 | }); // needed for Akka.HealthCheck 46 | app.UseAuthorization(); 47 | 48 | app.MapControllers(); 49 | 50 | app.Run(); -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/WebApiTemplate.App.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FrameworkParameter 5 | enable 6 | enable 7 | Linux 8 | webpapitemplate-app 9 | $(VersionPrefix);latest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/appsettings.Azure.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "Azurite": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;" 4 | }, 5 | "AzureStorageSettings": { 6 | "ConnectionStringName": "Azurite" 7 | }, 8 | "AkkaSettings": { 9 | "RemoteOptions": { 10 | "Port": 0 11 | }, 12 | "AkkaManagementOptions": { 13 | "Enabled": true, 14 | "DiscoveryMethod": "AzureTableStorage" 15 | }, 16 | "PersistenceMode": "Azure" 17 | } 18 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.App/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "AkkaSettings": { 10 | "ActorSystemName": "AkkaWebApi", 11 | "UseClustering": true, 12 | "RemoteOptions": { 13 | "HostName": "0.0.0.0", 14 | "Port": 8081 15 | }, 16 | "ClusterOptions": { 17 | "Roles": [ 18 | "web-api" 19 | ] 20 | }, 21 | "ShardOptions": { 22 | "StateStoreMode": "DData", 23 | "RememberEntities": false, 24 | "Role": "web-api" 25 | }, 26 | "AkkaManagementOptions": { 27 | "Enabled": true, 28 | "Hostname": "localhost", 29 | "PortName": "management", 30 | "ServiceName": "akka-management", 31 | "RequiredContactPointsNr": 1, 32 | "DiscoveryMethod": "Config" 33 | }, 34 | "PersistenceMode": "InMemory" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.Domain/CounterCommands.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiTemplate.Domain; 2 | 3 | /// 4 | /// Defines a command that is related to a counter. 5 | /// 6 | public interface ICounterCommand : IWithCounterId 7 | { 8 | } 9 | 10 | public sealed record IncrementCounterCommand(string CounterId, int Amount) : ICounterCommand; 11 | 12 | public sealed record SetCounterCommand(string CounterId, int Value) : ICounterCommand; 13 | 14 | public sealed record CounterCommandResponse 15 | (string CounterId, bool IsSuccess, ICounterEvent? Event = null, string? ErrorMessage = null) : ICounterCommand; 16 | 17 | public sealed record SubscribeToCounter(string CounterId, IActorRef Subscriber) : ICounterCommand; 18 | 19 | public sealed record UnsubscribeToCounter(string CounterId, IActorRef Subscriber) : ICounterCommand; -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.Domain/CounterEvents.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiTemplate.Domain; 2 | 3 | /// 4 | /// Events are facts of the system. Counter events deal in definitive state changes with the counter. 5 | /// 6 | public interface ICounterEvent : IWithCounterId 7 | { 8 | } 9 | 10 | public sealed record CounterValueIncremented(string CounterId, int Amount, int NewValue) : ICounterEvent; 11 | 12 | public sealed record CounterValueSet(string CounterId, int NewValue) : ICounterEvent; -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.Domain/CounterQueries.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiTemplate.Domain; 2 | 3 | /// 4 | /// Queries are similar to commands, but they have no side effects. 5 | /// 6 | /// They are used to retrieve information from the actors. 7 | /// 8 | public interface ICounterQuery : IWithCounterId{ } 9 | 10 | public sealed record FetchCounter(string CounterId) : ICounterQuery; -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.Domain/IWithCounterId.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiTemplate.Domain; 2 | 3 | /// 4 | /// Counters are the only entities that have a counter id. 5 | /// 6 | /// All messages decorated with this interface belong to a specific counter. 7 | /// 8 | public interface IWithCounterId 9 | { 10 | string CounterId { get; } 11 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.Domain/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Akka.Actor; -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/src/WebApiTemplate.Domain/WebApiTemplate.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FrameworkParameter 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/start-azurite.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/start-azurite.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/tests/WebApiTemplate.App.Tests/CounterActorSpecs.cs: -------------------------------------------------------------------------------- 1 | using Akka.Hosting; 2 | using Akka.Hosting.TestKit; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | using WebApiTemplate.App.Actors; 6 | using WebApiTemplate.App.Configuration; 7 | using WebApiTemplate.Domain; 8 | using Xunit.Abstractions; 9 | 10 | namespace WebApiTemplate.App.Tests; 11 | 12 | public class CounterActorSpecs : TestKit 13 | { 14 | public CounterActorSpecs(ITestOutputHelper output) : base(output:output) 15 | { 16 | } 17 | 18 | [Fact] 19 | public void CounterActor_should_follow_Protocol() 20 | { 21 | // arrange (counter actor parent is already running) 22 | var counterActor = ActorRegistry.Get(); 23 | var counterId1 = "counterId"; 24 | var counter1Messages = new IWithCounterId[] 25 | { 26 | new SetCounterCommand(counterId1, 3), 27 | new IncrementCounterCommand(counterId1, 10), 28 | new IncrementCounterCommand(counterId1, -5), 29 | new IncrementCounterCommand(counterId1, 2), 30 | 31 | new FetchCounter(counterId1) 32 | }; 33 | 34 | // act 35 | 36 | foreach (var msg in counter1Messages) 37 | { 38 | counterActor.Tell(msg, TestActor); 39 | } 40 | 41 | // assert 42 | var counter = (Counter)FishForMessage(c => c is Counter); 43 | counter.CounterId.Should().Be(counterId1); 44 | counter.CurrentValue.Should().Be(3+10-5+2); 45 | } 46 | 47 | protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) 48 | { 49 | var settings = new AkkaSettings() { UseClustering = false, PersistenceMode = PersistenceMode.InMemory }; 50 | services.AddSingleton(settings); 51 | base.ConfigureServices(context, services); 52 | } 53 | 54 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 55 | { 56 | builder.ConfigureCounterActors(provider).ConfigurePersistence(provider); 57 | } 58 | } -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/tests/WebApiTemplate.App.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using FluentAssertions; 3 | global using Akka.Actor; -------------------------------------------------------------------------------- /src/csharp/WebApiTemplate/tests/WebApiTemplate.App.Tests/WebApiTemplate.App.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FrameworkParameter 5 | enable 6 | enable 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/fsharp/AkkaConsoleTemplate/.template.config/dotnetcli.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/dotnetcli.host", 3 | "symbolInfo": { 4 | "Framework": { 5 | "longName": "framework" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/fsharp/AkkaConsoleTemplate/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/akkadotnet-templates/13094cc7f2ec0dd6e9d4392e51caf6dbba3b180e/src/fsharp/AkkaConsoleTemplate/.template.config/icon.png -------------------------------------------------------------------------------- /src/fsharp/AkkaConsoleTemplate/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "defaultSymbolVisibility": true, 4 | "icon": "icon.png", 5 | "learnMoreLink": "https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/ConsoleTemplate.md", 6 | "order": 0, 7 | "symbolInfo": [ 8 | ] 9 | } -------------------------------------------------------------------------------- /src/fsharp/AkkaConsoleTemplate/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "Akka", 4 | "classifications": ["Akka.NET", "Actors", "Console", "Windows", "Linux", "macOS"], 5 | "name": "Akka.NET Console Application", 6 | "description": "A simple console application that uses Akka.NET and Akka.Hosting.", 7 | "groupIdentity": "Akka.Console", 8 | "identity": "Akka.Console.FSharp", 9 | "shortName": "akka.console", 10 | "defaultName": "AkkaConsole1", 11 | "tags": { 12 | "language": "F#", 13 | "type": "project" 14 | }, 15 | "symbols": { 16 | "Framework": { 17 | "type": "parameter", 18 | "description": "The target framework for the project.", 19 | "datatype": "choice", 20 | "choices": [ 21 | { 22 | "choice": "net8.0", 23 | "description": "Target net8.0" 24 | }, 25 | { 26 | "choice": "net9.0", 27 | "description": "Target net9.0" 28 | } 29 | ], 30 | "replaces": "FrameworkParameter", 31 | "defaultValue": "net9.0" 32 | } 33 | }, 34 | "sourceName": "AkkaConsoleTemplate", 35 | "preferNameDirectory": true, 36 | "primaryOutputs": [ 37 | { "path": "AkkaConsoleTemplate.fsproj" }, 38 | { 39 | "condition": "(HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", 40 | "path": "Program.cs" 41 | } 42 | ], 43 | "postActions": [ 44 | ] 45 | } -------------------------------------------------------------------------------- /src/fsharp/AkkaConsoleTemplate/AkkaConsoleTemplate.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | FrameworkParameter 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/fsharp/AkkaConsoleTemplate/HelloActor.fs: -------------------------------------------------------------------------------- 1 | namespace AkkaConsoleTemplate 2 | 3 | open Akka.Actor 4 | open Akka.Event 5 | 6 | [] 7 | type HelloActor() as this = 8 | inherit ReceiveActor() 9 | 10 | let log = UntypedActor.Context.GetLogger() 11 | let mutable helloCounter = 0 12 | 13 | do 14 | this.Receive (fun message -> 15 | log.Info (sprintf "%s %i" message helloCounter) 16 | helloCounter <- helloCounter + 1 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /src/fsharp/AkkaConsoleTemplate/Program.fs: -------------------------------------------------------------------------------- 1 | open Akka.Actor 2 | open Akka.Hosting 3 | open Microsoft.Extensions.Hosting 4 | open AkkaConsoleTemplate 5 | 6 | let hostbuilder = HostBuilder() 7 | 8 | hostbuilder.ConfigureServices(fun services -> 9 | services.AddAkka("MyActorSystem", fun b -> 10 | 11 | b.WithActors(fun sys reg -> 12 | let helloActor = sys.ActorOf(Props.Create(fun () -> HelloActor()), "hello-actor") 13 | reg.Register(helloActor)) |> ignore 14 | 15 | b.WithActors(fun sys reg resolver -> 16 | let timerActorProps = resolver.Props() 17 | let timerActor = sys.ActorOf(timerActorProps, "timer-actor") 18 | reg.Register(timerActor)) |> ignore 19 | 20 | ) |> ignore 21 | ) |> ignore 22 | 23 | let host = hostbuilder.Build() 24 | host.RunAsync().Wait() -------------------------------------------------------------------------------- /src/fsharp/AkkaConsoleTemplate/TimerActor.fs: -------------------------------------------------------------------------------- 1 | namespace AkkaConsoleTemplate 2 | 3 | open Akka.Actor 4 | open Akka.Hosting 5 | 6 | type TimerActor = 7 | inherit ReceiveActor 8 | [] val mutable timer: ITimerScheduler 9 | val mutable helloActor: IActorRef 10 | 11 | new(helloActor: IRequiredActor) = 12 | {helloActor = helloActor.ActorRef} 13 | then 14 | base.Receive(fun message -> helloActor.ActorRef.Tell(message)) 15 | 16 | interface IWithTimers with 17 | member this.Timers with get() = this.timer and set(value) = this.timer <- value 18 | 19 | override this.PreStart() = 20 | let timer = this :> IWithTimers 21 | timer.Timers.StartPeriodicTimer("key", "hello", System.TimeSpan.FromSeconds(1.0) ) 22 | -------------------------------------------------------------------------------- /src/fsharp/AkkaStreamsTemplate/.template.config/dotnetcli.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/dotnetcli.host", 3 | "symbolInfo": { 4 | "Framework": { 5 | "longName": "framework" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/fsharp/AkkaStreamsTemplate/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/akkadotnet-templates/13094cc7f2ec0dd6e9d4392e51caf6dbba3b180e/src/fsharp/AkkaStreamsTemplate/.template.config/icon.png -------------------------------------------------------------------------------- /src/fsharp/AkkaStreamsTemplate/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "defaultSymbolVisibility": true, 4 | "icon": "icon.png", 5 | "learnMoreLink": "https://github.com/akkadotnet/akkadotnet-templates/blob/dev/docs/ConsoleTemplate.md", 6 | "order": 0, 7 | "symbolInfo": [ 8 | ] 9 | } -------------------------------------------------------------------------------- /src/fsharp/AkkaStreamsTemplate/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "Akka", 4 | "classifications": ["Akka.NET", "Akka.Streams", "Streaming"], 5 | "name": "Akka.Streams Console Application", 6 | "description": "A simple console application that uses Akka.Streams and Akka.Hosting to asynchonrously process data.", 7 | "groupIdentity": "Akka.Streams", 8 | "identity": "Akka.Streams.FSharp", 9 | "shortName": "akka.streams", 10 | "defaultName": "AkkaStreams1", 11 | "tags": { 12 | "language": "F#", 13 | "type": "project" 14 | }, 15 | "sourceName": "AkkaStreamsTemplate", 16 | "preferNameDirectory": true, 17 | "primaryOutputs": [ 18 | { "path": "AkkaStreamsTemplate.fsproj" }, 19 | { 20 | "condition": "(HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", 21 | "path": "Program.fs" 22 | } 23 | ], 24 | "symbols": { 25 | "Framework": { 26 | "type": "parameter", 27 | "description": "The target framework for the project.", 28 | "datatype": "choice", 29 | "choices": [ 30 | { 31 | "choice": "net8.0", 32 | "description": "Target net8.0" 33 | }, 34 | { 35 | "choice": "net9.0", 36 | "description": "Target net9.0" 37 | } 38 | ], 39 | "replaces": "FrameworkParameter", 40 | "defaultValue": "net9.0" 41 | } 42 | }, 43 | "postActions": [ 44 | { 45 | "id": "restore", 46 | "condition": "(!skipRestore)", 47 | "description": "Restore NuGet packages required by this project.", 48 | "manualInstructions": [ 49 | { "text": "Run 'dotnet restore'" } 50 | ], 51 | "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", 52 | "continueOnError": true 53 | }, 54 | { 55 | "id": "editor", 56 | "condition": "(HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", 57 | "description": "Opens Program.cs in the editor", 58 | "manualInstructions": [ ], 59 | "actionId": "84C0DA21-51C8-4541-9940-6CA19AF04EE6", 60 | "args": { 61 | "files": "1" 62 | }, 63 | "continueOnError": true 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /src/fsharp/AkkaStreamsTemplate/AkkaStreamsTemplate.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | FrameworkParameter 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/fsharp/AkkaStreamsTemplate/Program.fs: -------------------------------------------------------------------------------- 1 | open Akka.Actor 2 | open Akka.Hosting 3 | open Akka.Streams 4 | open Akka.Streams.Dsl 5 | open Microsoft.Extensions.Hosting 6 | open Microsoft.Extensions.DependencyInjection 7 | open AkkaStreamsTemplate 8 | open System 9 | open System.Threading 10 | 11 | let hostbuilder = HostBuilder() 12 | 13 | hostbuilder.ConfigureServices(fun services -> 14 | services.AddAkka("MyActorSystem", fun b -> 15 | 16 | b.WithActors(fun sys reg resolver -> 17 | let transformActorProps = resolver.Props() 18 | let transformActor = sys.ActorOf(transformActorProps, "transform-actor") 19 | reg.Register(transformActor)) |> ignore 20 | 21 | ) |> ignore 22 | ) |> ignore 23 | 24 | let host = hostbuilder.Build() 25 | let completionTask = host.RunAsync() 26 | 27 | let system = host.Services.GetRequiredService() 28 | 29 | let transformer = host.Services.GetRequiredService>().ActorRef 30 | 31 | let transformTask number = 32 | let cts = new CancellationTokenSource(TimeSpan.FromSeconds(3.0)) 33 | transformer.Ask(number.ToString(), cts.Token) 34 | 35 | 36 | Source.From(seq {1..1000}) 37 | .Where(fun i -> i % 2 = 0) 38 | .Select(fun i -> i.ToString()) 39 | .Throttle(10, TimeSpan.FromSeconds(1.0), 10, ThrottleMode.Shaping) 40 | .SelectAsync(5, transformTask ) 41 | .RunForeach((fun s -> printfn "%s" s), system) 42 | |> Async.AwaitTask |> Async.RunSynchronously 43 | 44 | completionTask |> Async.AwaitTask |> Async.RunSynchronously 45 | -------------------------------------------------------------------------------- /src/fsharp/AkkaStreamsTemplate/TransformActor.fs: -------------------------------------------------------------------------------- 1 | namespace AkkaStreamsTemplate 2 | 3 | open Akka.Actor 4 | 5 | [] 6 | type TransformActor() as this = 7 | inherit ReceiveActor() 8 | 9 | do 10 | this.Receive (fun (message:string)-> 11 | let actor = this :> IInternalActor 12 | actor.ActorContext.Sender.Tell (message.ToUpper()) 13 | ) --------------------------------------------------------------------------------