├── .config
└── dotnet-tools.json
├── .github
└── workflows
│ ├── github-actions.yml
│ └── release.yml
├── .gitignore
├── .paket
└── Paket.Restore.targets
├── LICENSE
├── README.md
├── WebFrame.sln
├── paket.dependencies
├── paket.lock
├── samples
├── AdvancedServer
│ ├── AdvancedServer.fsproj
│ ├── Program.fs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Test.html
│ ├── Text.txt
│ ├── appsettings.Development.json
│ └── appsettings.json
├── LocalServer
│ ├── LocalServer.fsproj
│ ├── Program.fs
│ └── Properties
│ │ └── launchSettings.json
├── Minimal
│ ├── Minimal.fsproj
│ ├── Program.fs
│ └── Properties
│ │ └── launchSettings.json
├── Modules
│ ├── Modules.fsproj
│ ├── Program.fs
│ └── Properties
│ │ └── launchSettings.json
├── StandardServer
│ ├── Pages
│ │ ├── About.html
│ │ └── Index.html
│ ├── Program.fs
│ ├── Properties
│ │ └── launchSettings.json
│ └── StandardServer.fsproj
└── TestServer
│ ├── Program.fs
│ ├── Properties
│ └── launchSettings.json
│ └── TestServer.fsproj
├── src
└── WebFrame
│ ├── AuthParts.fs
│ ├── BodyParts.fs
│ ├── ConfigParts.fs
│ ├── Configuration.fs
│ ├── Converters.fs
│ ├── CookieParts.fs
│ ├── Endpoints.fs
│ ├── Exceptions.fs
│ ├── Extensions.fs
│ ├── GlobalizationParts.fs
│ ├── HeaderParts.fs
│ ├── Http.fs
│ ├── Library.fs
│ ├── Logging.fs
│ ├── NUGET_README.md
│ ├── QueryParts.fs
│ ├── RouteParts.fs
│ ├── RouteTypes.fs
│ ├── Services.fs
│ ├── ServicesParts.fs
│ ├── SystemConfig.fs
│ ├── Templating.fs
│ ├── WebFrame.fsproj
│ ├── paket.references
│ └── paket.template
├── templates
├── All
│ └── Minimal
│ │ ├── .template.config
│ │ ├── ide.host.json
│ │ └── template.json
│ │ ├── Minimal.fsproj
│ │ └── Program.fs
├── README.md
└── templatepack.fsproj
└── tests
└── WebFrame.Tests
├── About.html
├── BasicTests.fs
├── Helpers.fs
├── Index.liquid
├── Program.fs
├── Sample.txt
├── WebFrame.Tests.fsproj
├── _Inner.liquid
└── paket.references
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "paket": {
6 | "version": "7.0.2",
7 | "commands": [
8 | "paket"
9 | ]
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/.github/workflows/github-actions.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build_ubuntu:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | dotnet-version: [ '6.0.x' ]
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Setup dotnet ${{ matrix.dotnet-version }}
16 | uses: actions/setup-dotnet@v1
17 | with:
18 | dotnet-version: ${{ matrix.dotnet-version }}
19 | - name: Install tools
20 | run: dotnet tool restore
21 | - name: Install dependencies
22 | run: dotnet restore
23 | - name: Build Core
24 | run: dotnet build ./src/WebFrame
25 | - name: Build Tests
26 | run: dotnet build ./tests/WebFrame.Tests
27 | - name: Test with the dotnet CLI
28 | run: dotnet test -v m ./tests/WebFrame.Tests
29 | build_windows:
30 |
31 | runs-on: windows-latest
32 | strategy:
33 | matrix:
34 | dotnet-version: [ '6.0.x' ]
35 |
36 | steps:
37 | - uses: actions/checkout@v2
38 | - name: Setup dotnet ${{ matrix.dotnet-version }}
39 | uses: actions/setup-dotnet@v1
40 | with:
41 | dotnet-version: ${{ matrix.dotnet-version }}
42 | - name: Install tools
43 | run: dotnet tool restore
44 | - name: Install dependencies
45 | run: dotnet restore
46 | - name: Build Core
47 | run: dotnet build ./src/WebFrame
48 | - name: Build Tests
49 | run: dotnet build ./tests/WebFrame.Tests
50 | - name: Test with the dotnet CLI
51 | run: dotnet test -v m ./tests/WebFrame.Tests
52 |
53 | build_macos:
54 |
55 | runs-on: macos-latest
56 | strategy:
57 | matrix:
58 | dotnet-version: [ '6.0.x' ]
59 |
60 | steps:
61 | - uses: actions/checkout@v2
62 | - name: Setup dotnet ${{ matrix.dotnet-version }}
63 | uses: actions/setup-dotnet@v1
64 | with:
65 | dotnet-version: ${{ matrix.dotnet-version }}
66 | - name: Install tools
67 | run: dotnet tool restore
68 | - name: Install dependencies
69 | run: dotnet restore
70 | - name: Build Core
71 | run: dotnet build ./src/WebFrame
72 | - name: Build Tests
73 | run: dotnet build ./tests/WebFrame.Tests
74 | - name: Test with the dotnet CLI
75 | run: dotnet test -v m ./tests/WebFrame.Tests
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core Release
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | check_tag:
9 | runs-on: ubuntu-latest
10 | outputs:
11 | release_all: ${{ steps.tag.outputs.release_all }}
12 | release_core: ${{ steps.tag.outputs.release_core }}
13 | release_templates: ${{ steps.tag.outputs.release_templates }}
14 | release_version: ${{ steps.version.outputs.release_version }}
15 | steps:
16 | - name: Check the tag ${{ github.ref }}
17 | id: tag
18 | run: |
19 | if [[ ${{ github.ref }} =~ refs\/tags\/all@v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
20 | echo "::set-output name=release_all::true"
21 | else
22 | echo "::set-output name=release_all::false"
23 | fi
24 | if [[ ${{ github.ref }} =~ refs\/tags\/core@v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
25 | echo "::set-output name=release_core::true"
26 | else
27 | echo "::set-output name=release_core::false"
28 | fi
29 | if [[ ${{ github.ref }} =~ refs\/tags\/templates@v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
30 | echo "::set-output name=release_templates::true"
31 | else
32 | echo "::set-output name=release_templates::false"
33 | fi
34 | - name: Get the version
35 | id: version
36 | run: echo "::set-output name=release_version::${GITHUB_REF#refs/tags/*@v}"
37 | release_core:
38 | needs: check_tag
39 | if: (needs.check_tag.outputs.release_all == 'true') || (needs.check_tag.outputs.release_core == 'true')
40 | runs-on: ubuntu-latest
41 | env:
42 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
43 | strategy:
44 | matrix:
45 | dotnet-version: [ '6.0.x' ]
46 |
47 | steps:
48 | - uses: actions/checkout@v2
49 | - name: Setup dotnet ${{ matrix.dotnet-version }}
50 | uses: actions/setup-dotnet@v1
51 | with:
52 | dotnet-version: ${{ matrix.dotnet-version }}
53 | - name: Install tools
54 | run: dotnet tool restore
55 | - name: Install dependencies
56 | run: dotnet restore
57 | - name: Build Core
58 | run: dotnet build ./src/WebFrame
59 | - name: Build Tests
60 | run: dotnet build ./tests/WebFrame.Tests
61 | - name: Test with the dotnet CLI
62 | run: dotnet test -v m ./tests/WebFrame.Tests
63 | - run: dotnet build --configuration Release ./src/WebFrame/WebFrame.fsproj
64 | - name: Create the package
65 | run: dotnet paket pack --template ./src/WebFrame/paket.template --specific-version RussBaz.WebFrame ${{ needs.check_tag.outputs.release_version }} --build-config Release .
66 | - name: Publish the package to nuget
67 | run: dotnet nuget push ./*.nupkg --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json
68 | - name: Remove nuget packages
69 | run: rm -f *.nupkg
70 | release_templates:
71 | needs: check_tag
72 | if: (needs.check_tag.outputs.release_all == 'true') || (needs.check_tag.outputs.release_templates == 'true')
73 | runs-on: ubuntu-latest
74 | env:
75 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
76 | strategy:
77 | matrix:
78 | dotnet-version: [ '6.0.x' ]
79 |
80 | steps:
81 | - uses: actions/checkout@v2
82 | - name: Setup dotnet ${{ matrix.dotnet-version }}
83 | uses: actions/setup-dotnet@v1
84 | with:
85 | dotnet-version: ${{ matrix.dotnet-version }}
86 | - name: Install tools
87 | run: dotnet tool restore
88 | - name: Install dependencies
89 | run: dotnet restore
90 | - run: dotnet build --configuration Release ./templates/templatepack.fsproj
91 | - name: Create the package
92 | run: dotnet pack ./templates/templatepack.fsproj -c Release -p:PackageVersion=${{ needs.check_tag.outputs.release_version }} -o .
93 | - name: Publish the package to nuget
94 | run: dotnet nuget push ./*.nupkg --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json
95 | - name: Remove nuget packages
96 | run: rm -f *.nupkg
--------------------------------------------------------------------------------
/.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 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
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 | # Tye
66 | .tye/
67 |
68 | # ASP.NET Scaffolding
69 | ScaffoldingReadMe.txt
70 |
71 | # StyleCop
72 | StyleCopReport.xml
73 |
74 | # Files built by Visual Studio
75 | *_i.c
76 | *_p.c
77 | *_h.h
78 | *.ilk
79 | *.meta
80 | *.obj
81 | *.iobj
82 | *.pch
83 | *.pdb
84 | *.ipdb
85 | *.pgc
86 | *.pgd
87 | *.rsp
88 | *.sbr
89 | *.tlb
90 | *.tli
91 | *.tlh
92 | *.tmp
93 | *.tmp_proj
94 | *_wpftmp.csproj
95 | *.log
96 | *.vspscc
97 | *.vssscc
98 | .builds
99 | *.pidb
100 | *.svclog
101 | *.scc
102 |
103 | # Chutzpah Test files
104 | _Chutzpah*
105 |
106 | # Visual C++ cache files
107 | ipch/
108 | *.aps
109 | *.ncb
110 | *.opendb
111 | *.opensdf
112 | *.sdf
113 | *.cachefile
114 | *.VC.db
115 | *.VC.VC.opendb
116 |
117 | # Visual Studio profiler
118 | *.psess
119 | *.vsp
120 | *.vspx
121 | *.sap
122 |
123 | # Visual Studio Trace Files
124 | *.e2e
125 |
126 | # TFS 2012 Local Workspace
127 | $tf/
128 |
129 | # Guidance Automation Toolkit
130 | *.gpState
131 |
132 | # ReSharper is a .NET coding add-in
133 | _ReSharper*/
134 | *.[Rr]e[Ss]harper
135 | *.DotSettings.user
136 |
137 | # TeamCity is a build add-in
138 | _TeamCity*
139 |
140 | # DotCover is a Code Coverage Tool
141 | *.dotCover
142 |
143 | # AxoCover is a Code Coverage Tool
144 | .axoCover/*
145 | !.axoCover/settings.json
146 |
147 | # Coverlet is a free, cross platform Code Coverage Tool
148 | coverage*.json
149 | coverage*.xml
150 | coverage*.info
151 |
152 | # Visual Studio code coverage results
153 | *.coverage
154 | *.coveragexml
155 |
156 | # NCrunch
157 | _NCrunch_*
158 | .*crunch*.local.xml
159 | nCrunchTemp_*
160 |
161 | # MightyMoose
162 | *.mm.*
163 | AutoTest.Net/
164 |
165 | # Web workbench (sass)
166 | .sass-cache/
167 |
168 | # Installshield output folder
169 | [Ee]xpress/
170 |
171 | # DocProject is a documentation generator add-in
172 | DocProject/buildhelp/
173 | DocProject/Help/*.HxT
174 | DocProject/Help/*.HxC
175 | DocProject/Help/*.hhc
176 | DocProject/Help/*.hhk
177 | DocProject/Help/*.hhp
178 | DocProject/Help/Html2
179 | DocProject/Help/html
180 |
181 | # Click-Once directory
182 | publish/
183 |
184 | # Publish Web Output
185 | *.[Pp]ublish.xml
186 | *.azurePubxml
187 | # Note: Comment the next line if you want to checkin your web deploy settings,
188 | # but database connection strings (with potential passwords) will be unencrypted
189 | *.pubxml
190 | *.publishproj
191 |
192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
193 | # checkin your Azure Web App publish settings, but sensitive information contained
194 | # in these scripts will be unencrypted
195 | PublishScripts/
196 |
197 | # NuGet Packages
198 | *.nupkg
199 | # NuGet Symbol Packages
200 | *.snupkg
201 | # The packages folder can be ignored because of Package Restore
202 | **/[Pp]ackages/*
203 | # except build/, which is used as an MSBuild target.
204 | !**/[Pp]ackages/build/
205 | # Uncomment if necessary however generally it will be regenerated when needed
206 | #!**/[Pp]ackages/repositories.config
207 | # NuGet v3's project.json files produces more ignorable files
208 | *.nuget.props
209 | *.nuget.targets
210 |
211 | # Microsoft Azure Build Output
212 | csx/
213 | *.build.csdef
214 |
215 | # Microsoft Azure Emulator
216 | ecf/
217 | rcf/
218 |
219 | # Windows Store app package directories and files
220 | AppPackages/
221 | BundleArtifacts/
222 | Package.StoreAssociation.xml
223 | _pkginfo.txt
224 | *.appx
225 | *.appxbundle
226 | *.appxupload
227 |
228 | # Visual Studio cache files
229 | # files ending in .cache can be ignored
230 | *.[Cc]ache
231 | # but keep track of directories ending in .cache
232 | !?*.[Cc]ache/
233 |
234 | # Others
235 | ClientBin/
236 | ~$*
237 | *~
238 | *.dbmdl
239 | *.dbproj.schemaview
240 | *.jfm
241 | *.pfx
242 | *.publishsettings
243 | orleans.codegen.cs
244 |
245 | # Including strong name files can present a security risk
246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
247 | #*.snk
248 |
249 | # Since there are multiple workflows, uncomment next line to ignore bower_components
250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
251 | #bower_components/
252 |
253 | # RIA/Silverlight projects
254 | Generated_Code/
255 |
256 | # Backup & report files from converting an old project file
257 | # to a newer Visual Studio version. Backup files are not needed,
258 | # because we have git ;-)
259 | _UpgradeReport_Files/
260 | Backup*/
261 | UpgradeLog*.XML
262 | UpgradeLog*.htm
263 | ServiceFabricBackup/
264 | *.rptproj.bak
265 |
266 | # SQL Server files
267 | *.mdf
268 | *.ldf
269 | *.ndf
270 |
271 | # Business Intelligence projects
272 | *.rdl.data
273 | *.bim.layout
274 | *.bim_*.settings
275 | *.rptproj.rsuser
276 | *- [Bb]ackup.rdl
277 | *- [Bb]ackup ([0-9]).rdl
278 | *- [Bb]ackup ([0-9][0-9]).rdl
279 |
280 | # Microsoft Fakes
281 | FakesAssemblies/
282 |
283 | # GhostDoc plugin setting file
284 | *.GhostDoc.xml
285 |
286 | # Node.js Tools for Visual Studio
287 | .ntvs_analysis.dat
288 | node_modules/
289 |
290 | # Visual Studio 6 build log
291 | *.plg
292 |
293 | # Visual Studio 6 workspace options file
294 | *.opt
295 |
296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
297 | *.vbw
298 |
299 | # Visual Studio LightSwitch build output
300 | **/*.HTMLClient/GeneratedArtifacts
301 | **/*.DesktopClient/GeneratedArtifacts
302 | **/*.DesktopClient/ModelManifest.xml
303 | **/*.Server/GeneratedArtifacts
304 | **/*.Server/ModelManifest.xml
305 | _Pvt_Extensions
306 |
307 | # Paket dependency manager
308 | .paket/paket.exe
309 | paket-files/
310 |
311 | # FAKE - F# Make
312 | .fake/
313 |
314 | # CodeRush personal settings
315 | .cr/personal
316 |
317 | # Python Tools for Visual Studio (PTVS)
318 | __pycache__/
319 | *.pyc
320 |
321 | # Cake - Uncomment if you are using it
322 | # tools/**
323 | # !tools/packages.config
324 |
325 | # Tabs Studio
326 | *.tss
327 |
328 | # Telerik's JustMock configuration file
329 | *.jmconfig
330 |
331 | # BizTalk build output
332 | *.btp.cs
333 | *.btm.cs
334 | *.odx.cs
335 | *.xsd.cs
336 |
337 | # OpenCover UI analysis results
338 | OpenCover/
339 |
340 | # Azure Stream Analytics local run output
341 | ASALocalRun/
342 |
343 | # MSBuild Binary and Structured Log
344 | *.binlog
345 |
346 | # NVidia Nsight GPU debugger configuration file
347 | *.nvuser
348 |
349 | # MFractors (Xamarin productivity tool) working folder
350 | .mfractor/
351 |
352 | # Local History for Visual Studio
353 | .localhistory/
354 |
355 | # BeatPulse healthcheck temp database
356 | healthchecksdb
357 |
358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
359 | MigrationBackup/
360 |
361 | # Ionide (cross platform F# VS Code tools) working folder
362 | .ionide/
363 |
364 | # Fody - auto-generated XML schema
365 | FodyWeavers.xsd
366 |
367 | ##
368 | ## Visual studio for Mac
369 | ##
370 |
371 |
372 | # globs
373 | Makefile.in
374 | *.userprefs
375 | *.usertasks
376 | config.make
377 | config.status
378 | aclocal.m4
379 | install-sh
380 | autom4te.cache/
381 | *.tar.gz
382 | tarballs/
383 | test-results/
384 |
385 | # Mac bundle stuff
386 | *.dmg
387 | *.app
388 |
389 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
390 | # General
391 | .DS_Store
392 | .AppleDouble
393 | .LSOverride
394 |
395 | # Icon must end with two \r
396 | Icon
397 |
398 |
399 | # Thumbnails
400 | ._*
401 |
402 | # Files that might appear in the root of a volume
403 | .DocumentRevisions-V100
404 | .fseventsd
405 | .Spotlight-V100
406 | .TemporaryItems
407 | .Trashes
408 | .VolumeIcon.icns
409 | .com.apple.timemachine.donotpresent
410 |
411 | # Directories potentially created on remote AFP share
412 | .AppleDB
413 | .AppleDesktop
414 | Network Trash Folder
415 | Temporary Items
416 | .apdisk
417 |
418 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
419 | # Windows thumbnail cache files
420 | Thumbs.db
421 | ehthumbs.db
422 | ehthumbs_vista.db
423 |
424 | # Dump file
425 | *.stackdump
426 |
427 | # Folder config file
428 | [Dd]esktop.ini
429 |
430 | # Recycle Bin used on file shares
431 | $RECYCLE.BIN/
432 |
433 | # Windows Installer files
434 | *.cab
435 | *.msi
436 | *.msix
437 | *.msm
438 | *.msp
439 |
440 | # Windows shortcuts
441 | *.lnk
442 |
443 | # JetBrains Rider
444 | .idea/
445 | *.sln.iml
446 |
447 | ##
448 | ## Visual Studio Code
449 | ##
450 | .vscode/*
451 | !.vscode/settings.json
452 | !.vscode/tasks.json
453 | !.vscode/launch.json
454 | !.vscode/extensions.json
455 |
--------------------------------------------------------------------------------
/.paket/Paket.Restore.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
8 |
9 | $(MSBuildVersion)
10 | 15.0.0
11 | false
12 | true
13 |
14 | true
15 | $(MSBuildThisFileDirectory)
16 | $(MSBuildThisFileDirectory)..\
17 | $(PaketRootPath)paket-files\paket.restore.cached
18 | $(PaketRootPath)paket.lock
19 | classic
20 | proj
21 | assembly
22 | native
23 | /Library/Frameworks/Mono.framework/Commands/mono
24 | mono
25 |
26 |
27 | $(PaketRootPath)paket.bootstrapper.exe
28 | $(PaketToolsPath)paket.bootstrapper.exe
29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\
30 |
31 | "$(PaketBootStrapperExePath)"
32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)"
33 |
34 |
35 |
36 |
37 | true
38 | true
39 |
40 |
41 | True
42 |
43 |
44 | False
45 |
46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/'))
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | $(PaketRootPath)paket
56 | $(PaketToolsPath)paket
57 |
58 |
59 |
60 |
61 |
62 | $(PaketRootPath)paket.exe
63 | $(PaketToolsPath)paket.exe
64 |
65 |
66 |
67 |
68 |
69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json"))
70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"'))
71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | <_PaketCommand>dotnet paket
83 |
84 |
85 |
86 |
87 |
88 | $(PaketToolsPath)paket
89 | $(PaketBootStrapperExeDir)paket
90 |
91 |
92 | paket
93 |
94 |
95 |
96 |
97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)"))
98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)"
99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)"
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | true
122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608
123 | false
124 | true
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))
134 |
135 |
136 |
137 |
138 |
139 |
141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``))
142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``))
143 |
144 |
145 |
146 |
147 | %(PaketRestoreCachedKeyValue.Value)
148 | %(PaketRestoreCachedKeyValue.Value)
149 |
150 |
151 |
152 |
153 | true
154 | false
155 | true
156 |
157 |
158 |
162 |
163 | true
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached
183 |
184 | $(MSBuildProjectFullPath).paket.references
185 |
186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references
187 |
188 | $(MSBuildProjectDirectory)\paket.references
189 |
190 | false
191 | true
192 | true
193 | references-file-or-cache-not-found
194 |
195 |
196 |
197 |
198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)'))
199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)'))
200 | references-file
201 | false
202 |
203 |
204 |
205 |
206 | false
207 |
208 |
209 |
210 |
211 | true
212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths)
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | false
224 | true
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length)
236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])
237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])
238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4])
239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])
240 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6])
241 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7])
242 |
243 |
244 | %(PaketReferencesFileLinesInfo.PackageVersion)
245 | All
246 | runtime
247 | $(ExcludeAssets);contentFiles
248 | $(ExcludeAssets);build;buildMultitargeting;buildTransitive
249 | true
250 | true
251 |
252 |
253 |
254 |
255 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0])
265 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1])
266 |
267 |
268 | %(PaketCliToolFileLinesInfo.PackageVersion)
269 |
270 |
271 |
272 |
276 |
277 |
278 |
279 |
280 |
281 | false
282 |
283 |
284 |
285 |
286 |
287 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/>
288 |
289 |
290 |
291 |
292 |
293 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile)
294 | true
295 | false
296 | true
297 | false
298 | true
299 | false
300 | true
301 | false
302 | true
303 | false
304 | true
305 | $(PaketIntermediateOutputPath)\$(Configuration)
306 | $(PaketIntermediateOutputPath)
307 |
308 |
309 |
310 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/>
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
370 |
371 |
420 |
421 |
466 |
467 |
511 |
512 |
555 |
556 |
557 |
558 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 RussBaz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/WebFrame.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{10075972-9166-4D47-87B1-47DC4567CE57}"
7 | EndProject
8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Minimal", "samples\Minimal\Minimal.fsproj", "{3D7380B8-3BD7-4D58-97AD-731D1463CE54}"
9 | EndProject
10 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LocalServer", "samples\LocalServer\LocalServer.fsproj", "{4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}"
11 | EndProject
12 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Modules", "samples\Modules\Modules.fsproj", "{8D830FBE-7B0C-44C4-8B27-914E8F051D2C}"
13 | EndProject
14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TestServer", "samples\TestServer\TestServer.fsproj", "{AB0DE2F3-CB50-4F7C-9EA3-292109341F12}"
15 | EndProject
16 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AdvancedServer", "samples\AdvancedServer\AdvancedServer.fsproj", "{D2304055-B211-4815-9267-A5B193440530}"
17 | EndProject
18 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "StandardServer", "samples\StandardServer\StandardServer.fsproj", "{B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}"
19 | EndProject
20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{829596DB-A8F4-4DF8-89C8-A372C9B7E88D}"
21 | ProjectSection(SolutionItems) = preProject
22 | README.md = README.md
23 | LICENSE = LICENSE
24 | paket.dependencies = paket.dependencies
25 | EndProjectSection
26 | EndProject
27 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WebFrame", "src\WebFrame\WebFrame.fsproj", "{8457CA84-6CFA-4CDD-8A7D-254E24150956}"
28 | EndProject
29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F49A36A9-1D60-4CB3-BCE0-7FE4B998DA65}"
30 | EndProject
31 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WebFrame.Tests", "tests\WebFrame.Tests\WebFrame.Tests.fsproj", "{75131042-ADF7-4065-804A-194095945DCA}"
32 | EndProject
33 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "templatepack", "templates\templatepack.fsproj", "{D93A115F-325B-4433-9EFC-1849311C5574}"
34 | EndProject
35 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{7FC46BB0-9E13-4C1D-8540-AB6DF62285E0}"
36 | ProjectSection(SolutionItems) = preProject
37 | .github\workflows\github-actions.yml = .github\workflows\github-actions.yml
38 | .github\workflows\release.yml = .github\workflows\release.yml
39 | EndProjectSection
40 | EndProject
41 | Global
42 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
43 | Debug|Any CPU = Debug|Any CPU
44 | Debug|x64 = Debug|x64
45 | Debug|x86 = Debug|x86
46 | Release|Any CPU = Release|Any CPU
47 | Release|x64 = Release|x64
48 | Release|x86 = Release|x86
49 | EndGlobalSection
50 | GlobalSection(SolutionProperties) = preSolution
51 | HideSolutionNode = FALSE
52 | EndGlobalSection
53 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
54 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Debug|x64.ActiveCfg = Debug|Any CPU
57 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Debug|x64.Build.0 = Debug|Any CPU
58 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Debug|x86.ActiveCfg = Debug|Any CPU
59 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Debug|x86.Build.0 = Debug|Any CPU
60 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Release|Any CPU.Build.0 = Release|Any CPU
62 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Release|x64.ActiveCfg = Release|Any CPU
63 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Release|x64.Build.0 = Release|Any CPU
64 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Release|x86.ActiveCfg = Release|Any CPU
65 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54}.Release|x86.Build.0 = Release|Any CPU
66 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Debug|Any CPU.Build.0 = Debug|Any CPU
68 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Debug|x64.ActiveCfg = Debug|Any CPU
69 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Debug|x64.Build.0 = Debug|Any CPU
70 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Debug|x86.ActiveCfg = Debug|Any CPU
71 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Debug|x86.Build.0 = Debug|Any CPU
72 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Release|Any CPU.ActiveCfg = Release|Any CPU
73 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Release|Any CPU.Build.0 = Release|Any CPU
74 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Release|x64.ActiveCfg = Release|Any CPU
75 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Release|x64.Build.0 = Release|Any CPU
76 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Release|x86.ActiveCfg = Release|Any CPU
77 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C}.Release|x86.Build.0 = Release|Any CPU
78 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
79 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
80 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Debug|x64.ActiveCfg = Debug|Any CPU
81 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Debug|x64.Build.0 = Debug|Any CPU
82 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Debug|x86.ActiveCfg = Debug|Any CPU
83 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Debug|x86.Build.0 = Debug|Any CPU
84 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
85 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Release|Any CPU.Build.0 = Release|Any CPU
86 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Release|x64.ActiveCfg = Release|Any CPU
87 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Release|x64.Build.0 = Release|Any CPU
88 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Release|x86.ActiveCfg = Release|Any CPU
89 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C}.Release|x86.Build.0 = Release|Any CPU
90 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
91 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Debug|Any CPU.Build.0 = Debug|Any CPU
92 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Debug|x64.ActiveCfg = Debug|Any CPU
93 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Debug|x64.Build.0 = Debug|Any CPU
94 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Debug|x86.ActiveCfg = Debug|Any CPU
95 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Debug|x86.Build.0 = Debug|Any CPU
96 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Release|Any CPU.ActiveCfg = Release|Any CPU
97 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Release|Any CPU.Build.0 = Release|Any CPU
98 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Release|x64.ActiveCfg = Release|Any CPU
99 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Release|x64.Build.0 = Release|Any CPU
100 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Release|x86.ActiveCfg = Release|Any CPU
101 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12}.Release|x86.Build.0 = Release|Any CPU
102 | {D2304055-B211-4815-9267-A5B193440530}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
103 | {D2304055-B211-4815-9267-A5B193440530}.Debug|Any CPU.Build.0 = Debug|Any CPU
104 | {D2304055-B211-4815-9267-A5B193440530}.Debug|x64.ActiveCfg = Debug|Any CPU
105 | {D2304055-B211-4815-9267-A5B193440530}.Debug|x64.Build.0 = Debug|Any CPU
106 | {D2304055-B211-4815-9267-A5B193440530}.Debug|x86.ActiveCfg = Debug|Any CPU
107 | {D2304055-B211-4815-9267-A5B193440530}.Debug|x86.Build.0 = Debug|Any CPU
108 | {D2304055-B211-4815-9267-A5B193440530}.Release|Any CPU.ActiveCfg = Release|Any CPU
109 | {D2304055-B211-4815-9267-A5B193440530}.Release|Any CPU.Build.0 = Release|Any CPU
110 | {D2304055-B211-4815-9267-A5B193440530}.Release|x64.ActiveCfg = Release|Any CPU
111 | {D2304055-B211-4815-9267-A5B193440530}.Release|x64.Build.0 = Release|Any CPU
112 | {D2304055-B211-4815-9267-A5B193440530}.Release|x86.ActiveCfg = Release|Any CPU
113 | {D2304055-B211-4815-9267-A5B193440530}.Release|x86.Build.0 = Release|Any CPU
114 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
115 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
116 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Debug|x64.ActiveCfg = Debug|Any CPU
117 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Debug|x64.Build.0 = Debug|Any CPU
118 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Debug|x86.ActiveCfg = Debug|Any CPU
119 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Debug|x86.Build.0 = Debug|Any CPU
120 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
121 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Release|Any CPU.Build.0 = Release|Any CPU
122 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Release|x64.ActiveCfg = Release|Any CPU
123 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Release|x64.Build.0 = Release|Any CPU
124 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Release|x86.ActiveCfg = Release|Any CPU
125 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC}.Release|x86.Build.0 = Release|Any CPU
126 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
127 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Debug|Any CPU.Build.0 = Debug|Any CPU
128 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Debug|x64.ActiveCfg = Debug|Any CPU
129 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Debug|x64.Build.0 = Debug|Any CPU
130 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Debug|x86.ActiveCfg = Debug|Any CPU
131 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Debug|x86.Build.0 = Debug|Any CPU
132 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Release|Any CPU.ActiveCfg = Release|Any CPU
133 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Release|Any CPU.Build.0 = Release|Any CPU
134 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Release|x64.ActiveCfg = Release|Any CPU
135 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Release|x64.Build.0 = Release|Any CPU
136 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Release|x86.ActiveCfg = Release|Any CPU
137 | {8457CA84-6CFA-4CDD-8A7D-254E24150956}.Release|x86.Build.0 = Release|Any CPU
138 | {75131042-ADF7-4065-804A-194095945DCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
139 | {75131042-ADF7-4065-804A-194095945DCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
140 | {75131042-ADF7-4065-804A-194095945DCA}.Debug|x64.ActiveCfg = Debug|Any CPU
141 | {75131042-ADF7-4065-804A-194095945DCA}.Debug|x64.Build.0 = Debug|Any CPU
142 | {75131042-ADF7-4065-804A-194095945DCA}.Debug|x86.ActiveCfg = Debug|Any CPU
143 | {75131042-ADF7-4065-804A-194095945DCA}.Debug|x86.Build.0 = Debug|Any CPU
144 | {75131042-ADF7-4065-804A-194095945DCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
145 | {75131042-ADF7-4065-804A-194095945DCA}.Release|Any CPU.Build.0 = Release|Any CPU
146 | {75131042-ADF7-4065-804A-194095945DCA}.Release|x64.ActiveCfg = Release|Any CPU
147 | {75131042-ADF7-4065-804A-194095945DCA}.Release|x64.Build.0 = Release|Any CPU
148 | {75131042-ADF7-4065-804A-194095945DCA}.Release|x86.ActiveCfg = Release|Any CPU
149 | {75131042-ADF7-4065-804A-194095945DCA}.Release|x86.Build.0 = Release|Any CPU
150 | {D93A115F-325B-4433-9EFC-1849311C5574}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
151 | {D93A115F-325B-4433-9EFC-1849311C5574}.Debug|Any CPU.Build.0 = Debug|Any CPU
152 | {D93A115F-325B-4433-9EFC-1849311C5574}.Debug|x64.ActiveCfg = Debug|Any CPU
153 | {D93A115F-325B-4433-9EFC-1849311C5574}.Debug|x64.Build.0 = Debug|Any CPU
154 | {D93A115F-325B-4433-9EFC-1849311C5574}.Debug|x86.ActiveCfg = Debug|Any CPU
155 | {D93A115F-325B-4433-9EFC-1849311C5574}.Debug|x86.Build.0 = Debug|Any CPU
156 | {D93A115F-325B-4433-9EFC-1849311C5574}.Release|Any CPU.ActiveCfg = Release|Any CPU
157 | {D93A115F-325B-4433-9EFC-1849311C5574}.Release|Any CPU.Build.0 = Release|Any CPU
158 | {D93A115F-325B-4433-9EFC-1849311C5574}.Release|x64.ActiveCfg = Release|Any CPU
159 | {D93A115F-325B-4433-9EFC-1849311C5574}.Release|x64.Build.0 = Release|Any CPU
160 | {D93A115F-325B-4433-9EFC-1849311C5574}.Release|x86.ActiveCfg = Release|Any CPU
161 | {D93A115F-325B-4433-9EFC-1849311C5574}.Release|x86.Build.0 = Release|Any CPU
162 | EndGlobalSection
163 | GlobalSection(NestedProjects) = preSolution
164 | {3D7380B8-3BD7-4D58-97AD-731D1463CE54} = {10075972-9166-4D47-87B1-47DC4567CE57}
165 | {4E0C0625-E5E8-4EE7-A7F0-8C4D79DC191C} = {10075972-9166-4D47-87B1-47DC4567CE57}
166 | {8D830FBE-7B0C-44C4-8B27-914E8F051D2C} = {10075972-9166-4D47-87B1-47DC4567CE57}
167 | {AB0DE2F3-CB50-4F7C-9EA3-292109341F12} = {10075972-9166-4D47-87B1-47DC4567CE57}
168 | {D2304055-B211-4815-9267-A5B193440530} = {10075972-9166-4D47-87B1-47DC4567CE57}
169 | {B5D8BCB2-8231-44BD-846C-F53DEBE88DBC} = {10075972-9166-4D47-87B1-47DC4567CE57}
170 | {8457CA84-6CFA-4CDD-8A7D-254E24150956} = {829596DB-A8F4-4DF8-89C8-A372C9B7E88D}
171 | {75131042-ADF7-4065-804A-194095945DCA} = {F49A36A9-1D60-4CB3-BCE0-7FE4B998DA65}
172 | {7FC46BB0-9E13-4C1D-8540-AB6DF62285E0} = {829596DB-A8F4-4DF8-89C8-A372C9B7E88D}
173 | EndGlobalSection
174 | EndGlobal
175 |
--------------------------------------------------------------------------------
/paket.dependencies:
--------------------------------------------------------------------------------
1 | source https://api.nuget.org/v3/index.json
2 |
3 | storage: none
4 | framework: net6.0
5 |
6 | nuget FSharp.Core >= 6.0
7 |
8 | nuget Newtonsoft.Json >= 13.0.1 < 14
9 |
10 | nuget DotLiquid >= 2.2.585 < 2.3
11 |
12 | nuget Microsoft.AspNetCore.TestHost ~> 6.0
13 |
14 | nuget FsUnit ~> 4.2
15 | nuget NUnit ~> 3.13
16 | nuget NUnit3TestAdapter ~> 4.2
17 |
18 | nuget Microsoft.NET.Test.Sdk ~> 17.1
--------------------------------------------------------------------------------
/paket.lock:
--------------------------------------------------------------------------------
1 | STORAGE: NONE
2 | RESTRICTION: == net6.0
3 | NUGET
4 | remote: https://api.nuget.org/v3/index.json
5 | DotLiquid (2.2.595)
6 | FSharp.Core (6.0.3)
7 | FsUnit (4.2)
8 | FSharp.Core (>= 5.0.2)
9 | NETStandard.Library (>= 2.0.3)
10 | NUnit (>= 3.13.2 < 3.14)
11 | Microsoft.AspNetCore.TestHost (6.0.3)
12 | System.IO.Pipelines (>= 6.0.2)
13 | Microsoft.CodeCoverage (17.1)
14 | Microsoft.NET.Test.Sdk (17.1)
15 | Microsoft.CodeCoverage (>= 17.1)
16 | Microsoft.TestPlatform.TestHost (>= 17.1)
17 | Microsoft.NETCore.Platforms (6.0.2)
18 | Microsoft.TestPlatform.ObjectModel (17.1)
19 | NuGet.Frameworks (>= 5.11)
20 | System.Reflection.Metadata (>= 1.6)
21 | Microsoft.TestPlatform.TestHost (17.1)
22 | Microsoft.TestPlatform.ObjectModel (>= 17.1)
23 | Newtonsoft.Json (>= 9.0.1)
24 | NETStandard.Library (2.0.3)
25 | Microsoft.NETCore.Platforms (>= 1.1)
26 | Newtonsoft.Json (13.0.1)
27 | NuGet.Frameworks (6.1)
28 | NUnit (3.13.2)
29 | NETStandard.Library (>= 2.0)
30 | NUnit3TestAdapter (4.2.1)
31 | System.Collections.Immutable (6.0)
32 | System.Runtime.CompilerServices.Unsafe (>= 6.0)
33 | System.IO.Pipelines (6.0.2)
34 | System.Reflection.Metadata (6.0.1)
35 | System.Collections.Immutable (>= 6.0)
36 | System.Runtime.CompilerServices.Unsafe (6.0)
37 |
--------------------------------------------------------------------------------
/samples/AdvancedServer/AdvancedServer.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 | true
12 | PreserveNewest
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/samples/AdvancedServer/Program.fs:
--------------------------------------------------------------------------------
1 | namespace AdvancedServer
2 |
3 | open System
4 | open System.IO
5 |
6 | open Microsoft.AspNetCore.Hosting
7 | open Microsoft.AspNetCore.Builder
8 |
9 | open Microsoft.Extensions.DependencyInjection
10 | open Microsoft.Extensions.Hosting
11 |
12 | open WebFrame
13 | open WebFrame.Http
14 | open WebFrame.RouteTypes
15 | open WebFrame.SystemConfig
16 |
17 | open type WebFrame.Endpoints.Helpers
18 |
19 | type SampleRequestBody = {
20 | Value: decimal
21 | Name: string
22 | }
23 |
24 | // Sample Exception
25 | type CoffeeException () =
26 | inherit Exception "I am a teapot!"
27 |
28 | // Sample service to inject
29 | type IMyService =
30 | abstract member Print : string -> unit
31 |
32 | type MyService () =
33 | interface IMyService with
34 | member _.Print text = printfn $"Text: {text}"
35 |
36 | module MyService =
37 | let configureService: ServiceSetup = fun env config serv ->
38 | serv.AddScoped ()
39 |
40 | module MyApp =
41 | let configureApp: AppSetup = fun env config app ->
42 | if env.IsDevelopment () then
43 | app
44 | else
45 | app.UseExceptionHandler "/error"
46 |
47 |
48 | // These examples will try showing as many available helpers as possible
49 | // However, please refer to the docs for more information
50 | module Program =
51 | // The first item is always the full path to the executable
52 | let args = Environment.GetCommandLineArgs () |> Array.tail
53 |
54 | let app = App args
55 |
56 | // This is a host logger and it is configured in the system defaults
57 | // It is created even before the app is built and running
58 | // If the app raises an exception during the setup,
59 | // then it may terminate before the message is displayed in the console
60 | app.Log.Information "The app has been created"
61 |
62 | // Setting up in-memory overrides for config values
63 | app.Config.[ "MY_OPTION" ] <- "Brave New World!"
64 | app.Config.[ "COUNT" ] <- "12"
65 |
66 | // Returning 418 whenever a "CoffeeException" is raised within this module ("App" only in this case)
67 | app.Errors <- Error.codeFor 418
68 |
69 | // Registering a custom service before registering any services provided by the WebFrame
70 | app.Services.BeforeServices <- MyService.configureService
71 |
72 | // Adding an exception redirection when not in development
73 |
74 | // This is the earliest slot available for the app configuration
75 | // It happens before anything else is configured
76 | app.Services.BeforeApp <- MyApp.configureApp
77 |
78 | // Web Page route
79 | app.Get "/" <- page "Test.html" // It should end .html or Server Side exception would be thrown
80 | // Text response helper
81 | app.Get "/hello" <- always ( TextResponse "world" )
82 | // Few different ways to send a file over
83 | app.Get "/file" <- file "Text.txt" // You can also add a required content-type
84 | app.Get "/file2" <- fun _ -> FileResponse "Text.txt"
85 | app.Get "/file3" <- fun serv -> serv.File "Text.txt" // You can also add a required content-type
86 |
87 | // Accessing a custom service
88 | app.Get "/service" <- fun serv ->
89 | let s = serv.Services.Required ()
90 |
91 | s.Print "Hello"
92 |
93 | serv.EndResponse ()
94 |
95 | app.Get "/fail" <- fun _ ->
96 | failwith "I have failed you."
97 |
98 | // Accessing Configuration
99 | app.Get "/my" <- fun serv ->
100 | // Optional with default
101 | let myOption = serv.Config.Get "MY_OPTION" ""
102 | // Required property that can be parsed into int
103 | let count = serv.Config.Required "COUNT"
104 |
105 | // Returns a string
106 | serv.EndResponse $"[{count}] {myOption}"
107 |
108 | // Always return an empty page with a 404 status code
109 | app.Get "/error" <- fun serv ->
110 | serv.StatusCode <- 404
111 | // Let's log the error too
112 | serv.Log.Warning "A page was not found"
113 | EndResponse
114 |
115 | // EndResponse method returns the HttpWorkload type
116 | // So you do not have to import the type in this case
117 | app.Get "/coffee" <- fun serv ->
118 | CoffeeException () |> raise
119 | serv.EndResponse ()
120 |
121 | // Requesting ASP.NET Core services
122 | // and returning objects (json by default)
123 | app.Get "/env" <- fun serv ->
124 | let env = serv.Services.Required ()
125 |
126 | serv.EndResponse env
127 |
128 | // Showing route and query parameters
129 | // For the route pattern syntax please check ASP.NET Core docs
130 | app.Post "/new/{item:guid}/{groupId:int?}/{**slug}" <- fun serv ->
131 | // Route Values are always singular
132 | let slug = serv.Route.Get "slug" ""
133 | let item = serv.Route.Required "item"
134 | let groupId = serv.Route.Optional "groupId"
135 |
136 | // Query Values are normally returned as a list
137 | // All values found must match the specified type
138 |
139 | // Only Get method tries to return the first item
140 | // It returns the first item found in the list
141 | let order = serv.Query.Get "order" "desc"
142 | // Required will return all the items found in a list
143 | // But they all have to match the specified type
144 | // Otherwise it fails
145 | let q = serv.Query.Required "q"
146 | let nextPosition = serv.Query.Optional "next"
147 | // Will try find all the query parameters names "custom"
148 | // and returns them as a list of strings
149 | // Returns an empty list if nothing is found
150 | let allCustomQ = serv.Query.All "custom"
151 |
152 | // A json response with an anonymous record
153 | serv.EndResponse
154 | {|
155 | Item = item
156 | GroupId = groupId
157 | Slug = slug
158 | Ordering = order
159 | Q = q
160 | Next = nextPosition
161 | Custom = allCustomQ
162 | |}
163 |
164 | // Accessing headers and cookies
165 | app.Get "/name" <- fun serv ->
166 | // Headers and Cookies follow the same principal as the Route and Query Parameters
167 | // You can request Required, Optional and so on
168 | // Headers are Cookies are string values
169 | // In addition, Headers are represented as a list
170 |
171 | // Requesting Optional Header from the request with a default value
172 | let specialHeader = serv.Headers.Get "Custom" "None"
173 | // Reading Optional Cookie with a default value
174 | let randomCookie = serv.Cookies.Get "Random" "0"
175 |
176 | // Setting up a custom header on the response
177 | // Header methods that write to the response: Set, Append, Delete
178 | serv.Headers.Set "Custom" [ "Random" ]
179 |
180 | let rnd = Random ()
181 | let randomNumber = rnd.Next ( 1, 10 )
182 |
183 | if randomNumber < 8 then
184 | // Setting up a custom Cookie on the response
185 | serv.Cookies.Set "Random" $"{randomNumber}"
186 | else
187 | // Asking for the cookie to be marked as expired
188 | serv.Cookies.Delete "Random"
189 |
190 | // In addition, you can pass ASP.NET Core CookieOptions class to customise them even further
191 | // Please use SetWithOptions and DeleteWithOptions methods for that purpose
192 |
193 | serv.EndResponse $"Custom: {specialHeader} [{randomCookie}]"
194 |
195 | // Another example of a json response
196 | app.Put "/new/{name}" <- fun serv ->
197 | // Another way to declare expected type
198 | let name: string = serv.Route.Required "name"
199 |
200 | // Presence of Required Form Fields would imply that
201 | // the form is sent correctly
202 | let number: int list = serv.Body.Form.Required "number"
203 | // The Form works just like the queries
204 | let zipCode = serv.Body.Form.Get "zip" ""
205 |
206 | // You can check explicitly if the form is present
207 | let present = serv.Body.Form.IsPresent
208 |
209 | let number = number |> List.tryHead |> Option.defaultValue 0
210 |
211 | let response =
212 | {|
213 | Name = name
214 | Number = $"+44{number}"
215 | ZipCode = zipCode
216 | FormWasPresent = present
217 | |}
218 |
219 | JsonResponse response
220 |
221 | // Async Workflows
222 |
223 | // Sometimes the request handler needs to work with Tasks
224 | // Then you can use a separate set of helper methods
225 | app.PostTask "/new" <- fun serv -> task {
226 | // Asynchronously (Task) read the body
227 | // It must be present and of the specified type
228 | // You can use Optional instead if you want to do something special
229 | // when the body is not an anticipated json
230 | let! body = serv.Body.Json.Exact ()
231 |
232 | // A shortcut for accessing Content-Type header
233 | let contentType = serv.ContentType
234 |
235 | return JsonResponse {| Body = body; ContentType = contentType |}
236 | }
237 |
238 | // Another example of reading json
239 | app.PostTask "/new-item" <- fun serv -> task {
240 | // The type can be inlined and anonymous
241 | // Furthermore, it will discard additional fields if present
242 | let! body = serv.Body.Json.Exact<{| Name: string |}> ()
243 |
244 | return JsonResponse {| Body = body |}
245 | }
246 |
247 | // Reading a raw body as a stream
248 | app.PostTask "/new-item2" <- fun serv -> task {
249 | let bodyStream = serv.Body.Raw
250 |
251 | use reader = new StreamReader ( bodyStream )
252 |
253 | let! body = reader.ReadToEndAsync ()
254 |
255 | return serv.EndResponse {| Body = body |}
256 | }
257 |
258 | // Alternative method of setting up routes
259 | // Accepts Tasks only
260 | app.[ Delete "/" ] <- fun serv -> task {
261 | // You can access the entire asp.net core context
262 | // For example, here is the connection ip address
263 | let ip = serv.Context.Connection.RemoteIpAddress
264 |
265 | printfn $"IP: {ip}"
266 |
267 | // Terminates the response
268 | // Useful when you are manually preparing a response
269 | return EndResponse
270 | }
271 |
272 | app.Run ()
273 |
--------------------------------------------------------------------------------
/samples/AdvancedServer/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:43179",
7 | "sslPort": 44324
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "AdvancedServer": {
19 | "commandName": "Project",
20 | "dotnetRunMessages": "true",
21 | "launchBrowser": true,
22 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
23 | "environmentVariables": {
24 | "ASPNETCORE_ENVIRONMENT": "Development"
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/samples/AdvancedServer/Test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello
6 |
7 |
8 | 123
9 |
10 |
--------------------------------------------------------------------------------
/samples/AdvancedServer/Text.txt:
--------------------------------------------------------------------------------
1 | This is a sample text file.
--------------------------------------------------------------------------------
/samples/AdvancedServer/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/samples/AdvancedServer/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/samples/LocalServer/LocalServer.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/LocalServer/Program.fs:
--------------------------------------------------------------------------------
1 | open System
2 | open System.IO
3 |
4 | open WebFrame
5 |
6 | // The first argument is the current program name
7 | let argv = Environment.GetCommandLineArgs ()
8 |
9 | let app = App ()
10 |
11 | let location =
12 | argv
13 | |> Array.tail
14 | |> Array.tryHead
15 | |> Option.map Path.GetFullPath
16 | |> Option.defaultWith ( fun _ -> invalidArg "path" "Could not find the specified file path." )
17 |
18 | app.Log.Information $"Preparing to display contents of the following folder: {location}"
19 |
20 | app.Services.StaticFiles.Enabled <- true // Serving Static Files is disabled by default
21 | app.Services.StaticFiles.AllowBrowsing <- true // Only enable this if absolutely necessary
22 | app.Services.StaticFiles.WebRoot <- "." // Default location: wwwroot
23 |
24 | // The next line adds a prefix to all static files
25 | // and it must be a valid path
26 | // app.Services.StaticFiles.Route <- "/static"
27 |
28 | // The root location for serving any files
29 | app.Services.ContentRoot <- location
30 |
31 | app.Log.Information $"Displaying contents of the following folder: {location}"
32 |
33 | app.Run ()
34 |
--------------------------------------------------------------------------------
/samples/LocalServer/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true
5 | },
6 | "profiles": {
7 | "LocalServer": {
8 | "commandName": "Project",
9 | "launchBrowser": true,
10 | "applicationUrl": "http://localhost:15779",
11 | "environmentVariables": {
12 | "ASPNETCORE_ENVIRONMENT": "Development"
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/samples/Minimal/Minimal.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/Minimal/Program.fs:
--------------------------------------------------------------------------------
1 | open WebFrame
2 |
3 | []
4 | let main _ =
5 | let app = App ()
6 |
7 | app.Get "/" <- fun serv -> serv.EndResponse "Hello World!"
8 |
9 | app.Run ()
10 |
11 | 0 // return an integer exit code
12 |
--------------------------------------------------------------------------------
/samples/Minimal/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true
5 | },
6 | "profiles": {
7 | "Minimal": {
8 | "commandName": "Project",
9 | "launchBrowser": true,
10 | "applicationUrl": "http://localhost:1456",
11 | "environmentVariables": {
12 | "ASPNETCORE_ENVIRONMENT": "Development"
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/samples/Modules/Modules.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/Modules/Program.fs:
--------------------------------------------------------------------------------
1 | open WebFrame
2 |
3 | let api = AppModule "/api"
4 | api.Get "/" <- fun serv -> serv.EndResponse "Api"
5 |
6 | let app = App ()
7 | app.Get "/" <- fun serv -> serv.EndResponse "Main"
8 | app.Module "api" <- api
9 |
10 | app.Run ()
11 |
--------------------------------------------------------------------------------
/samples/Modules/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true
5 | },
6 | "profiles": {
7 | "Modules": {
8 | "commandName": "Project",
9 | "launchBrowser": true,
10 | "applicationUrl": "http://localhost:55839",
11 | "environmentVariables": {
12 | "ASPNETCORE_ENVIRONMENT": "Development"
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/samples/StandardServer/Pages/About.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | About
6 |
7 |
8 | About Page
9 |
10 |
--------------------------------------------------------------------------------
/samples/StandardServer/Pages/Index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Main
6 |
7 |
8 | Main Page
9 |
10 |
--------------------------------------------------------------------------------
/samples/StandardServer/Program.fs:
--------------------------------------------------------------------------------
1 | open System
2 |
3 | open WebFrame
4 | open type WebFrame.Endpoints.Helpers
5 |
6 | []
7 | let main argv =
8 | let items = [ "todo1"; "todo2"; "todo3" ]
9 |
10 | let api = AppModule "/api"
11 |
12 | // Returning items
13 | api.Get "/" <- fun serv ->
14 | serv.EndResponse items
15 |
16 | // Adding items
17 | // By sending an item Name as a string field in a form
18 | api.Post "/" <- fun serv ->
19 | // If a required property in user input is not found,
20 | // then 400 error is issued automatically
21 | let itemName = serv.Body.Form.Required "name"
22 |
23 | if items |> List.contains itemName then
24 | serv.StatusCode <- 409
25 | printfn $"Item {itemName} already exists"
26 | else
27 | serv.StatusCode <- 201
28 | printfn $"Adding a new item {itemName}"
29 |
30 | serv.EndResponse ()
31 |
32 | let app = App argv
33 |
34 | app.Get "/" <- page "Pages/Index.html"
35 | app.Get "/About" <- page "Pages/About.html"
36 |
37 | app.Module "ToDoApi" <- api
38 |
39 | app.Run ()
40 |
41 | 0 // exit code
42 |
--------------------------------------------------------------------------------
/samples/StandardServer/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true
5 | },
6 | "profiles": {
7 | "StandardServer": {
8 | "commandName": "Project",
9 | "launchBrowser": true,
10 | "applicationUrl": "http://localhost:20599",
11 | "environmentVariables": {
12 | "ASPNETCORE_ENVIRONMENT": "Development"
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/samples/StandardServer/StandardServer.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/TestServer/Program.fs:
--------------------------------------------------------------------------------
1 | open System.Threading.Tasks
2 |
3 | open Microsoft.AspNetCore.TestHost
4 |
5 | open WebFrame
6 |
7 | let app = App ()
8 |
9 | app.Get "/" <- fun serv -> serv.EndResponse "index"
10 |
11 | let t = [
12 | task {
13 | use! server = app.TestServer ()
14 | let client = server.GetTestClient ()
15 |
16 | let! r = client.GetAsync "/"
17 | let! c = r.Content.ReadAsStringAsync ()
18 |
19 | if c <> "index" then failwith "wrong content"
20 |
21 | return ()
22 | } :> Task
23 | ]
24 |
25 | t |> Array.ofList |> Task.WaitAll
26 |
--------------------------------------------------------------------------------
/samples/TestServer/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true
5 | },
6 | "profiles": {
7 | "TestServer": {
8 | "commandName": "Project",
9 | "launchBrowser": true,
10 | "applicationUrl": "http://localhost:61014",
11 | "environmentVariables": {
12 | "ASPNETCORE_ENVIRONMENT": "Development"
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/samples/TestServer/TestServer.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/WebFrame/AuthParts.fs:
--------------------------------------------------------------------------------
1 | module WebFrame.AuthParts
2 |
3 | open WebFrame.Exceptions
4 |
5 | type IAuthenticationProvider<'SP, 'T> =
6 | abstract member onAuthenticate: ( 'SP -> 'T option ) with get, set
7 |
8 | type AuthenticationProvider<'SP, 'T> () =
9 | interface IAuthenticationProvider<'SP, 'T> with
10 | member val onAuthenticate = raise ( MissingAuthenticationException () ) with get, set
11 |
12 | type UserManager<'SP, 'T> ( provider: Lazy>, serv: 'SP ) =
13 | member this.Required () = this.Optional () |> Option.defaultWith ( fun () -> raise ( NotAuthneticatedException () ) )
14 | member this.Optional () = provider.Value.onAuthenticate serv
15 |
16 | type Auth<'SP, 'T> ( authProvider: Lazy>, serv: 'SP ) =
17 | member val User = UserManager ( authProvider, serv )
18 |
--------------------------------------------------------------------------------
/src/WebFrame/BodyParts.fs:
--------------------------------------------------------------------------------
1 | module WebFrame.BodyParts
2 |
3 | open System
4 | open System.IO
5 | open System.Text
6 | open System.Threading.Tasks
7 |
8 | open Microsoft.AspNetCore.Http
9 | open Microsoft.Net.Http.Headers
10 | open Microsoft.Extensions.Primitives
11 |
12 | open Newtonsoft.Json
13 | open Newtonsoft.Json.Linq
14 |
15 | open WebFrame.Configuration
16 | open WebFrame.Converters
17 | open WebFrame.Exceptions
18 |
19 |
20 | type FormFiles ( files: IFormFileCollection ) =
21 | member _.All () = files |> Seq.toList
22 | member _.Required ( fileName: string ) =
23 | match files.GetFile fileName with
24 | | null -> raise ( MissingRequiredFormFileException fileName )
25 | | f -> f
26 | member this.Optional ( fileName: string ) =
27 | try
28 | this.Required fileName |> Some
29 | with
30 | | :? MissingRequiredFormFileException -> None
31 |
32 | member val Count = files.Count
33 |
34 | type FormEncodedBody ( req: HttpRequest ) =
35 | let mutable form: IFormCollection option = None
36 | let mutable files: FormFiles option = None
37 |
38 | let getForm () =
39 | match form with
40 | | Some v -> v
41 | | None ->
42 | if req.HasFormContentType then
43 | form <- Some req.Form
44 | else
45 | raise ( MissingRequiredFormException () )
46 |
47 | req.Form
48 |
49 | let getFiles () =
50 | match files with
51 | | Some v -> v
52 | | None ->
53 | let f = getForm ()
54 | let f = FormFiles f.Files
55 |
56 | files <- Some f
57 |
58 | f
59 |
60 | member this.String ( name: string ) : string option =
61 | name |> this.Optional
62 |
63 | member this.Required<'T when 'T : equality> ( name: string ) : 'T =
64 | name
65 | |> this.Optional<'T>
66 | |> Option.defaultWith ( fun _ -> raise ( MissingRequiredFormFieldException name ) )
67 |
68 | member this.Optional<'T when 'T : equality> ( name: string ) : 'T option =
69 | name
70 | |> this.AllString
71 | |> Option.bind List.tryHead
72 | |> Option.bind convertTo<'T>
73 |
74 | member this.Get<'T when 'T : equality> ( name: string ) ( d: 'T ) : 'T =
75 | name
76 | |> this.Optional<'T>
77 | |> Option.defaultValue d
78 |
79 | member this.AllString ( name: string ) : string list option =
80 | let form = getForm ()
81 |
82 | match form.TryGetValue name with
83 | | true, v -> Some v
84 | | _ -> None
85 | |> Option.map ( fun i -> i.ToArray () |> List.ofArray )
86 |
87 | member this.All<'T when 'T : equality> ( name: string ) : 'T list =
88 | name
89 | |> this.AllOptional<'T>
90 | |> Option.defaultValue []
91 |
92 | member this.AllRequired<'T when 'T : equality> ( name: string ) : 'T list =
93 | name
94 | |> this.AllOptional<'T>
95 | |> Option.defaultWith ( fun _ -> raise ( MissingRequiredFormFieldException name ) )
96 |
97 | member this.AllOptional<'T when 'T : equality> ( name: string ) : 'T list option =
98 | name
99 | |> this.AllString
100 | |> Option.map ( List.map convertTo<'T> )
101 | |> Option.bind ( fun i -> if i |> List.contains None then None else Some i )
102 | |> Option.map ( List.map Option.get )
103 | |> Option.bind ( fun i -> if i.Length = 0 then None else Some i )
104 |
105 | member this.Count ( name: string ) : int =
106 | name
107 | |> this.All<_>
108 | |> List.length
109 |
110 | member _.Raw with get () = try getForm () |> Some with | :? MissingRequiredFormException -> None
111 | member _.Files with get () = try getFiles () |> Some with | :? MissingRequiredFormException -> None
112 | member val IsPresent = req.HasFormContentType
113 |
114 | type JsonEncodedBody ( req: HttpRequest, settings: Lazy ) =
115 | let mutable unknownEncoding = false
116 | let mutable json: JObject option = None
117 | let jsonSerializer = lazy ( JsonSerializer.CreateDefault settings.Value )
118 |
119 | let jsonCharset =
120 | match MediaTypeHeaderValue.TryParse ( StringSegment req.ContentType ) with
121 | | true, v ->
122 | if v.MediaType.Equals ( "application/json", StringComparison.OrdinalIgnoreCase ) then
123 | Some v.Charset
124 | elif v.Suffix.Equals ( "json", StringComparison.OrdinalIgnoreCase ) then
125 | Some v.Charset
126 | else
127 | None
128 | | _ ->
129 | None
130 |
131 | let jsonEncoding =
132 | match jsonCharset with
133 | | Some c ->
134 | try
135 | if c.Equals ( "utf-8", StringComparison.OrdinalIgnoreCase ) then
136 | Encoding.UTF8 |> Some
137 | elif c.HasValue then
138 | Encoding.GetEncoding c.Value |> Some
139 | else
140 | None
141 | with
142 | | _ ->
143 | unknownEncoding <- true
144 | None
145 | | None -> None
146 |
147 | let notJsonContentType = jsonCharset.IsNone || unknownEncoding
148 |
149 | let getJson () = task {
150 | if notJsonContentType then raise ( MissingRequiredJsonException () )
151 |
152 | match json with
153 | | Some v -> return v
154 | | None ->
155 | let en = jsonEncoding |> Option.defaultValue Encoding.UTF8
156 |
157 | use br = new StreamReader ( req.Body, en )
158 |
159 | let! body = br.ReadToEndAsync ()
160 |
161 | try
162 | let v = JObject.Parse body
163 |
164 | json <- Some v
165 |
166 | return v
167 | with
168 | | :? JsonSerializationException -> return raise ( MissingRequiredJsonException () )
169 | }
170 |
171 | member private _.ReadJson<'T> () = task {
172 | let! j = getJson ()
173 |
174 | use tr = new JTokenReader ( j )
175 |
176 | try
177 | return jsonSerializer.Value.Deserialize<'T> tr
178 | with
179 | | :? JsonSerializationException -> return raise ( MissingRequiredJsonException () )
180 | }
181 |
182 | member private _.GetField<'T> ( jsonPath: string ) = task {
183 | let! j = getJson ()
184 | let token = j.SelectToken jsonPath
185 | return token.ToObject<'T> ()
186 | }
187 |
188 | member private _.GetFields<'T> ( jsonPath: string ) = task {
189 | let! j = getJson ()
190 |
191 | return
192 | jsonPath
193 | |> j.SelectTokens
194 | |> Seq.map ( fun i -> i.ToObject<'T> () )
195 | |> List.ofSeq
196 | }
197 |
198 | member this.Exact<'T> () : Task<'T> = this.ReadJson<'T> ()
199 |
200 | member this.String ( path: string ) : Task = task {
201 | return! this.Optional path
202 | }
203 |
204 | member this.Get<'T> ( path: string ) ( d: 'T ) : Task<'T> = task {
205 | let! v = this.Optional<'T> path
206 | return v |> Option.defaultValue d
207 | }
208 |
209 | member this.Required<'T> ( path: string ) : Task<'T> = task {
210 | try
211 | return! this.GetField<'T> path
212 | with
213 | | :? NullReferenceException -> return ( raise ( MissingRequiredJsonFieldException path ) )
214 | | :? MissingRequiredJsonException as ex -> return ( raise ex )
215 | | :? MissingRequiredJsonFieldException as ex -> return ( raise ex )
216 | }
217 |
218 | member this.Optional<'T> ( path: string ) : Task<'T option> = task {
219 | try
220 | let! r = this.GetField<'T> path
221 | return Some r
222 | with
223 | | :? NullReferenceException -> return None
224 | | :? MissingRequiredJsonException -> return None
225 | | :? MissingRequiredJsonFieldException -> return None
226 | }
227 |
228 | member this.All<'T> ( path: string ) : Task<'T list> = task {
229 | let! data = path |> this.AllOptional
230 |
231 | return data |> Option.defaultValue []
232 | }
233 |
234 | member this.AllString ( path: string ) = this.All path
235 |
236 | member this.AllRequired<'T> ( path: string ) = task {
237 | try
238 | return! this.GetFields<'T> path
239 | with
240 | | :? NullReferenceException -> return ( raise ( MissingRequiredJsonFieldException path ) )
241 | | :? MissingRequiredJsonException as ex -> return ( raise ex )
242 | | :? MissingRequiredJsonFieldException as ex -> return ( raise ex )
243 | }
244 |
245 | member this.AllOptional<'T> ( path: string ) : Task<'T list option> = task {
246 | try
247 | let! r = this.GetFields<'T> path
248 | return Some r
249 | with
250 | | :? NullReferenceException -> return None
251 | | :? MissingRequiredJsonException -> return None
252 | | :? MissingRequiredJsonFieldException -> return None
253 | }
254 |
255 | member this.Count ( path: string ) : Task = task {
256 | let! v = this.AllString path
257 | return v.Length
258 | }
259 |
260 | member this.Raw with get () : Task = task {
261 | let! isPresent = this.IsPresent ()
262 |
263 | if isPresent then
264 | return json.Value
265 | else
266 | return raise ( MissingRequiredJsonException () )
267 | }
268 |
269 | member this.IsPresent () = task {
270 | if this.IsJsonContentType then
271 | try
272 | let! _ = getJson ()
273 | return true
274 | with
275 | | :? MissingRequiredJsonException -> return false
276 | else
277 | return false
278 | }
279 | member val IsJsonContentType = not notJsonContentType
280 |
281 | type Body ( req: HttpRequest, jsonSettingsProvider: Lazy ) =
282 | member val Form = FormEncodedBody req
283 | member val Json = JsonEncodedBody ( req, lazy jsonSettingsProvider.Value.Settings )
284 | member val Raw = req.Body
285 | member val RawPipe = req.BodyReader
286 |
--------------------------------------------------------------------------------
/src/WebFrame/ConfigParts.fs:
--------------------------------------------------------------------------------
1 | module WebFrame.ConfigParts
2 |
3 | open Microsoft.AspNetCore.Hosting
4 |
5 | open Microsoft.Extensions.Configuration
6 | open Microsoft.Extensions.Hosting
7 |
8 | open WebFrame.Exceptions
9 | open WebFrame.Converters
10 |
11 | type RuntimeConfigs ( conf: Lazy, env: Lazy ) =
12 | member _.String ( name: string ) =
13 | let r = conf.Value.[ name ]
14 |
15 | match box r with
16 | | null -> None
17 | | _ -> Some r
18 |
19 | member this.Get<'T> ( name: string ) ( d: 'T ) =
20 | name
21 | |> this.Optional
22 | |> Option.defaultValue d
23 |
24 | member this.Required<'T> ( name: string ) =
25 | name
26 | |> this.Optional<'T>
27 | |> Option.defaultWith ( fun _ -> MissingRequiredConfigException name |> raise )
28 |
29 | member this.Optional<'T> ( name: string ) =
30 | name
31 | |> this.String
32 | |> Option.bind convertTo<'T>
33 |
34 | member _.Raw = conf.Value
35 | member _.ApplicationName = env.Value.ApplicationName
36 | member _.EnvironmentName = env.Value.EnvironmentName
37 | member _.IsDevelopment = env.Value.IsDevelopment ()
38 | member _.IsStaging = env.Value.IsStaging ()
39 | member _.IsProduction = env.Value.IsProduction ()
40 | member _.IsEnvironment name = env.Value.IsEnvironment name
41 |
--------------------------------------------------------------------------------
/src/WebFrame/Configuration.fs:
--------------------------------------------------------------------------------
1 | module WebFrame.Configuration
2 |
3 | open System.Collections.Generic
4 |
5 | open System.Globalization
6 | open Microsoft.Extensions.Logging
7 |
8 | open Newtonsoft.Json
9 | open Newtonsoft.Json.Serialization
10 |
11 | type SystemDefaults = {
12 | SettingsPrefix: string
13 | LoggerPrefix: string
14 | LoggerGlobalName: string
15 | LoggerHostFactory: ILoggerFactory
16 | Args: string []
17 | }
18 |
19 | module SystemDefaults =
20 | let defaultLoggerFactory =
21 | LoggerFactory.Create ( fun l -> l.AddSimpleConsole () |> ignore )
22 | let standard = {
23 | SettingsPrefix = "WebFrame"
24 | LoggerPrefix = "WebFrame"
25 | LoggerGlobalName = "Global"
26 | LoggerHostFactory = defaultLoggerFactory
27 | Args = [||]
28 | }
29 | let defaultWithArgs args = { standard with Args = args }
30 | let getLoggerNameForCategory ( name: string ) ( c: SystemDefaults ) =
31 | let prefix = if c.LoggerPrefix <> "" then $"{c.LoggerPrefix}." else ""
32 | $"{prefix}{name}"
33 | let getGlobalLoggerName ( c: SystemDefaults ) =
34 | c |> getLoggerNameForCategory c.LoggerGlobalName
35 | let getHostLogger ( c: SystemDefaults ) =
36 | let factory = c.LoggerHostFactory
37 | let loggerName = c |> getGlobalLoggerName
38 | factory.CreateLogger loggerName
39 | let toMap ( c: SystemDefaults ) =
40 | let prefix = if c.SettingsPrefix="" then "WebFrame" else c.SettingsPrefix
41 | Map [
42 | $"{prefix}:GlobalLogger:FullName", getGlobalLoggerName c
43 | $"{prefix}:GlobalLogger:Prefix", c.LoggerPrefix
44 | ]
45 |
46 | type IGlobalizationConfig =
47 | abstract DefaultCulture: CultureInfo with get
48 | abstract AllowedCultures: CultureInfo list with get
49 |
50 | type IUserExceptionFilter =
51 | abstract ShowUserException: bool
52 |
53 | type ConfigOverrides ( _defaultConfig: SystemDefaults ) =
54 | let config = Dictionary ()
55 |
56 | let get i =
57 | match config.TryGetValue i with
58 | | true, v -> v
59 | | _ -> ""
60 | let set i v = config.[ i ] <- v
61 |
62 | member _.Item with get i = get i and set i v = config.[ i ] <- v
63 | member _.ConnectionStrings with get i = get $"ConnectionStrings:{i}" and set i v = set $"ConnectionStrings:{i}" v
64 |
65 | member val Raw = config
66 |
67 | // Some Json Serialization Configs
68 | type IJsonSerializationService =
69 | abstract Settings: JsonSerializerSettings
70 |
71 | type IJsonDeserializationService =
72 | abstract Settings: JsonSerializerSettings
73 |
74 | type RequireAllPropertiesContractResolver () =
75 | inherit DefaultContractResolver ()
76 |
77 | // Code samples are taken from:
78 | // https://stackoverflow.com/questions/29655502/json-net-require-all-properties-on-deserialization/29660550
79 |
80 | override this.CreateProperty ( memberInfo, serialization ) =
81 | let prop = base.CreateProperty ( memberInfo, serialization )
82 | let isRequired =
83 | not prop.PropertyType.IsGenericType || prop.PropertyType.GetGenericTypeDefinition () <> typedefof