├── .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 | 
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 | )
--------------------------------------------------------------------------------