diff --git a/.gitignore b/.gitignore
index cd9f35a..74e917a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,369 @@
-\.vs/
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+# User-specific files
+*.rsuser
+*.suo
*.user
-.couscous/
-docs/Template-Dark/
-.idea/
\ No newline at end of file
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# MacOS file systems
+**/.DS_STORE
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JetBrains Rider
+.idea/
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*[.json, .xml, .info]
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# Sqlite example databases
+*.db
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6ae2058
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,18 @@
+language: csharp
+mono: none
+dotnet: 3.1.100
+solution: JsonApiDotNetCore.MongoDb.sln
+script:
+ - dotnet restore
+ - dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=./coverage/lcov-coverage /p:Include="[JsonApiDotNetCore.MongoDb]*"
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
+before_deploy:
+ - dotnet build -c Release
+ - dotnet pack -c Release
+deploy:
+ skip_cleanup: true
+ provider: script
+ script: dotnet nuget push ./src/JsonApiDotNetCore.MongoDb/bin/Release/JsonApiDotNetCore.MongoDb.*.nupkg -k $NUGET_API -s https://api.nuget.org/v3/index.json
+ on:
+ branch: master
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..c835917
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,28 @@
+{
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": ".NET Core Launch (webapi)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/src/Example/bin/Debug/netcoreapp3.1/Example.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/src/Example",
+ // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+ "console": "internalConsole",
+ "stopAtEntry": false,
+ "requireExactSource": false
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach",
+ "processId": "${command:pickProcess}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..0463b1e
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,42 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/test/JsonApiDotNetCore.MongoDb.UnitTests/JsonApiDotNetCore.MongoDb.UnitTests.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/test/JsonApiDotNetCore.MongoDb.UnitTests/JsonApiDotNetCore.MongoDb.UnitTests.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "${workspaceFolder}/test/JsonApiDotNetCore.MongoDb.UnitTests/JsonApiDotNetCore.MongoDb.UnitTests.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..7f73136
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,22 @@
+
+
+ netcoreapp3.1
+ 3.1.*
+ 4.0.*
+ 2.11.*
+
+
+
+ $(NoWarn);1591
+ true
+ true
+
+
+
+
+ 2.4.1
+ 5.10.3
+ 31.0.3
+ 4.14.6
+
+
diff --git a/JsonApiDotNetCore.MongoDb.sln b/JsonApiDotNetCore.MongoDb.sln
index 5dbe7c9..5994f4a 100644
--- a/JsonApiDotNetCore.MongoDb.sln
+++ b/JsonApiDotNetCore.MongoDb.sln
@@ -1,44 +1,86 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.MongoDb", "src\JsonApiDotNetCore.MongoDb\JsonApiDotNetCore.MongoDb.csproj", "{7F964050-3BB1-4E5C-8458-A974B956908F}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{262566DF-A206-4026-8018-69C66936EFCF}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EAC8B3B-BD8B-4FFF-8807-37FCA45BD8BD}"
- ProjectSection(SolutionItems) = preProject
- .gitignore = .gitignore
- EndProjectSection
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "test\UnitTests\UnitTests.csproj", "{0AB9C87F-0A30-4875-B9BF-38F606E79734}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AEA5DF2E-26B5-4D81-AA6A-ADA690E3E0D8}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {7F964050-3BB1-4E5C-8458-A974B956908F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7F964050-3BB1-4E5C-8458-A974B956908F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7F964050-3BB1-4E5C-8458-A974B956908F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7F964050-3BB1-4E5C-8458-A974B956908F}.Release|Any CPU.Build.0 = Release|Any CPU
- {0AB9C87F-0A30-4875-B9BF-38F606E79734}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0AB9C87F-0A30-4875-B9BF-38F606E79734}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0AB9C87F-0A30-4875-B9BF-38F606E79734}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0AB9C87F-0A30-4875-B9BF-38F606E79734}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {7F964050-3BB1-4E5C-8458-A974B956908F} = {262566DF-A206-4026-8018-69C66936EFCF}
- {0AB9C87F-0A30-4875-B9BF-38F606E79734} = {AEA5DF2E-26B5-4D81-AA6A-ADA690E3E0D8}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {F7741F43-2CB1-4C5E-9886-FDCB0B973E30}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7E29AA10-F938-4CF8-9CAB-7ACD2D6DC784}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore.MongoDb.GettingStarted", "src\JsonApiDotNetCore.MongoDb.GettingStarted\JsonApiDotNetCore.MongoDb.GettingStarted.csproj", "{600A3E66-E63F-427D-A991-4CD2067041F9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore.MongoDb", "src\JsonApiDotNetCore.MongoDb\JsonApiDotNetCore.MongoDb.csproj", "{FD312677-2A62-4B8F-A965-879B059F1755}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{19A533AA-E006-496D-A476-364DF2B637A1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore.MongoDb.Example.Tests", "test\JsonApiDotNetCore.MongoDb.Example.Tests\JsonApiDotNetCore.MongoDb.Example.Tests.csproj", "{24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore.MongoDb.Example", "src\JsonApiDotNetCore.MongoDb.Example\JsonApiDotNetCore.MongoDb.Example.csproj", "{743C32A5-2584-4FA0-987B-B4E97CDAADE8}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Debug|x64.Build.0 = Debug|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Debug|x86.Build.0 = Debug|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Release|x64.ActiveCfg = Release|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Release|x64.Build.0 = Release|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Release|x86.ActiveCfg = Release|Any CPU
+ {600A3E66-E63F-427D-A991-4CD2067041F9}.Release|x86.Build.0 = Release|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Debug|x64.Build.0 = Debug|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Debug|x86.Build.0 = Debug|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Release|x64.ActiveCfg = Release|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Release|x64.Build.0 = Release|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Release|x86.ActiveCfg = Release|Any CPU
+ {FD312677-2A62-4B8F-A965-879B059F1755}.Release|x86.Build.0 = Release|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|x64.Build.0 = Debug|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|x86.Build.0 = Debug|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|x64.ActiveCfg = Release|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|x64.Build.0 = Release|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|x86.ActiveCfg = Release|Any CPU
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|x86.Build.0 = Release|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Debug|x64.Build.0 = Debug|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Debug|x86.Build.0 = Debug|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Release|x64.ActiveCfg = Release|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Release|x64.Build.0 = Release|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Release|x86.ActiveCfg = Release|Any CPU
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {600A3E66-E63F-427D-A991-4CD2067041F9} = {7E29AA10-F938-4CF8-9CAB-7ACD2D6DC784}
+ {FD312677-2A62-4B8F-A965-879B059F1755} = {7E29AA10-F938-4CF8-9CAB-7ACD2D6DC784}
+ {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1} = {19A533AA-E006-496D-A476-364DF2B637A1}
+ {743C32A5-2584-4FA0-987B-B4E97CDAADE8} = {7E29AA10-F938-4CF8-9CAB-7ACD2D6DC784}
+ EndGlobalSection
+EndGlobal
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..509975f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+Copyright (c) 2020 Alvaro Nicoli
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c7b5ac1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,104 @@
+# JsonApiDotNetCore MongoDB Repository
+
+[![NuGet version][nuget-image]][nuget-url]
+[![Downloads][downloads-image]][nuget-url]
+[](https://travis-ci.com/mrnkr/JsonApiDotNetCore.MongoDb)
+[](https://codecov.io/gh/mrnkr/JsonApiDotNetCore.MongoDb)
+[![license][license]](https://github.com/mrnkr/JsonApiDotNetCore.MongoDb/blob/master/LICENSE)
+
+[nuget-image]:https://img.shields.io/nuget/v/JsonApiDotNetCore.MongoDb
+[nuget-url]:https://www.nuget.org/packages/JsonApiDotNetCore.MongoDb
+[downloads-image]:https://img.shields.io/nuget/dt/JsonApiDotNetCore.MongoDb
+[license]:https://img.shields.io/github/license/mrnkr/JsonApiDotNetCore.MongoDb
+
+Plug-n-play implementation of `IResourceRepository` allowing you to use MongoDb with your `JsonApiDotNetCore` APIs.
+
+## Installation and Usage
+
+### Models
+
+```cs
+public sealed class Book : IIdentifiable
+{
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string Name { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+}
+```
+
+### Controllers
+
+```cs
+public sealed class BooksController : JsonApiController
+{
+ public BooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ {
+ }
+}
+```
+
+### Middleware
+
+```cs
+public class Startup
+{
+ public IServiceProvider ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton(sp =>
+ {
+ var client = new MongoClient(Configuration.GetSection("DatabaseSettings:ConnectionString").Value);
+ return client.GetDatabase(Configuration.GetSection("DatabaseSettings:Database").Value);
+ });
+
+ services.AddScoped, MongoEntityRepository>();
+ services.AddJsonApi(options =>
+ {
+ options.Namespace = "api";
+ options.UseRelativeLinks = true;
+ options.IncludeTotalResourceCount = true;
+ options.SerializerSettings.Formatting = Formatting.Indented;
+ }, resources: builder =>
+ {
+ builder.Add();
+ });
+ // ...
+ }
+
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseRouting();
+ app.UseJsonApi();
+ app.UseEndpoints(endpoints => endpoints.MapControllers());
+ // ...
+ }
+}
+```
+
+## Running tests and examples
+
+Integration tests use the [`Mongo2Go`](https://github.com/Mongo2Go/Mongo2Go) package so they don't require a running instance of MongoDb on your machine.
+
+Just run the following command to run all tests:
+
+```bash
+dotnet test
+```
+
+To run the examples you are indeed going to want to have a running instance of MongoDb on your device. Fastest way to get one running is using docker:
+
+```bash
+docker run -p 27017:27017 -d mongo:latest
+dotnet run
+```
+
+## Limitations
+
+- Relations are not supported (yet)
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/ArticlesController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/ArticlesController.cs
new file mode 100644
index 0000000..0aa0aa1
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/ArticlesController.cs
@@ -0,0 +1,18 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class ArticlesController : JsonApiController
+ {
+ public ArticlesController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/AuthorsController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/AuthorsController.cs
new file mode 100644
index 0000000..73b9962
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/AuthorsController.cs
@@ -0,0 +1,18 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class AuthorsController : JsonApiController
+ {
+ public AuthorsController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/BlogsController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/BlogsController.cs
new file mode 100644
index 0000000..6805d16
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/BlogsController.cs
@@ -0,0 +1,18 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class BlogsController : JsonApiController
+ {
+ public BlogsController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/CountriesController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/CountriesController.cs
new file mode 100644
index 0000000..78ef4af
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/CountriesController.cs
@@ -0,0 +1,21 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.Controllers.Annotations;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.QueryStrings;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ [DisableQueryString(StandardQueryStringParameters.Sort | StandardQueryStringParameters.Page)]
+ public sealed class CountriesController : JsonApiController
+ {
+ public CountriesController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/PassportsController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/PassportsController.cs
new file mode 100644
index 0000000..b61ee4b
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/PassportsController.cs
@@ -0,0 +1,16 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class PassportsController : JsonApiController
+ {
+ public PassportsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ {
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/PeopleController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/PeopleController.cs
new file mode 100644
index 0000000..8710d52
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/PeopleController.cs
@@ -0,0 +1,18 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class PeopleController : JsonApiController
+ {
+ public PeopleController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/PersonRolesController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/PersonRolesController.cs
new file mode 100644
index 0000000..9bf10f1
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/PersonRolesController.cs
@@ -0,0 +1,18 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class PersonRolesController : JsonApiController
+ {
+ public PersonRolesController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/Restricted/ReadOnlyController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/Restricted/ReadOnlyController.cs
new file mode 100644
index 0000000..31c120f
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/Restricted/ReadOnlyController.cs
@@ -0,0 +1,106 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.Controllers.Annotations;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers.Restricted
+{
+ [DisableRoutingConvention, Route("[controller]")]
+ [HttpReadOnly]
+ public class ReadOnlyController : BaseJsonApiController
+ {
+ public ReadOnlyController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+
+ [HttpGet]
+ public IActionResult Get() => Ok();
+
+ [HttpPost]
+ public IActionResult Post() => Ok();
+
+ [HttpPatch]
+ public IActionResult Patch() => Ok();
+
+ [HttpDelete]
+ public IActionResult Delete() => Ok();
+ }
+
+ [DisableRoutingConvention, Route("[controller]")]
+ [NoHttpPost]
+ public class NoHttpPostController : BaseJsonApiController
+ {
+ public NoHttpPostController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+
+ [HttpGet]
+ public IActionResult Get() => Ok();
+
+ [HttpPost]
+ public IActionResult Post() => Ok();
+
+ [HttpPatch]
+ public IActionResult Patch() => Ok();
+
+ [HttpDelete]
+ public IActionResult Delete() => Ok();
+ }
+
+ [DisableRoutingConvention, Route("[controller]")]
+ [NoHttpPatch]
+ public class NoHttpPatchController : BaseJsonApiController
+ {
+ public NoHttpPatchController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+
+ [HttpGet]
+ public IActionResult Get() => Ok();
+
+ [HttpPost]
+ public IActionResult Post() => Ok();
+
+ [HttpPatch]
+ public IActionResult Patch() => Ok();
+
+ [HttpDelete]
+ public IActionResult Delete() => Ok();
+ }
+
+ [DisableRoutingConvention, Route("[controller]")]
+ [NoHttpDelete]
+ public class NoHttpDeleteController : BaseJsonApiController
+ {
+ public NoHttpDeleteController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+
+ [HttpGet]
+ public IActionResult Get() => Ok();
+
+ [HttpPost]
+ public IActionResult Post() => Ok();
+
+ [HttpPatch]
+ public IActionResult Patch() => Ok();
+
+ [HttpDelete]
+ public IActionResult Delete() => Ok();
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TagsController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TagsController.cs
new file mode 100644
index 0000000..16c958d
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TagsController.cs
@@ -0,0 +1,20 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.Controllers.Annotations;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ [DisableQueryString("skipCache")]
+ public sealed class TagsController : JsonApiController
+ {
+ public TagsController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TestValuesController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TestValuesController.cs
new file mode 100644
index 0000000..9bd4cec
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TestValuesController.cs
@@ -0,0 +1,35 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ [Route("[controller]")]
+ public class TestValuesController : ControllerBase
+ {
+ [HttpGet]
+ public IActionResult Get()
+ {
+ var result = new[] { "value" };
+ return Ok(result);
+ }
+
+ [HttpPost]
+ public IActionResult Post(string name)
+ {
+ var result = "Hello, " + name;
+ return Ok(result);
+ }
+
+ [HttpPatch]
+ public IActionResult Patch(string name)
+ {
+ var result = "Hello, " + name;
+ return Ok(result);
+ }
+
+ [HttpDelete]
+ public IActionResult Delete()
+ {
+ return Ok("Deleted");
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/ThrowingResourcesController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/ThrowingResourcesController.cs
new file mode 100644
index 0000000..2fcda52
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/ThrowingResourcesController.cs
@@ -0,0 +1,18 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class ThrowingResourcesController : JsonApiController
+ {
+ public ThrowingResourcesController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoCollectionsController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoCollectionsController.cs
new file mode 100644
index 0000000..13804a9
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoCollectionsController.cs
@@ -0,0 +1,37 @@
+using System.Threading;
+using System.Threading.Tasks;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class TodoCollectionsController : JsonApiController
+ {
+
+ public TodoCollectionsController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ {
+ }
+
+ [HttpPatch("{id}")]
+ public override async Task PatchAsync(string id, [FromBody] TodoItemCollection resource, CancellationToken cancellationToken)
+ {
+ // if (resource.Name == "PRE-ATTACH-TEST")
+ // {
+ // var targetTodoId = resource.TodoItems.First().Id;
+ // var todoItemContext = _dbResolver.GetContext().Set();
+ // await todoItemContext.Where(ti => ti.Id == targetTodoId).FirstOrDefaultAsync(cancellationToken);
+ // }
+
+ return await base.PatchAsync(id, resource, cancellationToken);
+ }
+
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoItemsController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoItemsController.cs
new file mode 100644
index 0000000..a147a20
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoItemsController.cs
@@ -0,0 +1,18 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class TodoItemsController : JsonApiController
+ {
+ public TodoItemsController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoItemsCustomController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoItemsCustomController.cs
new file mode 100644
index 0000000..36ad9c5
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoItemsCustomController.cs
@@ -0,0 +1,149 @@
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers.Annotations;
+using JsonApiDotNetCore.Errors;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Services;
+using Microsoft.AspNetCore.Mvc;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ [ApiController]
+ [DisableRoutingConvention, Route("custom/route/todoItems")]
+ public class TodoItemsCustomController : CustomJsonApiController
+ {
+ public TodoItemsCustomController(
+ IJsonApiOptions options,
+ IResourceService resourceService)
+ : base(options, resourceService)
+ { }
+ }
+
+ public class CustomJsonApiController
+ : CustomJsonApiController where T : class, IIdentifiable
+ {
+ public CustomJsonApiController(
+ IJsonApiOptions options,
+ IResourceService resourceService)
+ : base(options, resourceService)
+ {
+ }
+ }
+
+ public class CustomJsonApiController
+ : ControllerBase where T : class, IIdentifiable
+ {
+ private readonly IJsonApiOptions _options;
+ private readonly IResourceService _resourceService;
+
+ private IActionResult Forbidden()
+ {
+ return new StatusCodeResult((int)HttpStatusCode.Forbidden);
+ }
+
+ public CustomJsonApiController(
+ IJsonApiOptions options,
+ IResourceService resourceService)
+ {
+ _options = options;
+ _resourceService = resourceService;
+ }
+
+ public CustomJsonApiController(
+ IResourceService resourceService)
+ {
+ _resourceService = resourceService;
+ }
+
+ [HttpGet]
+ public async Task GetAsync(CancellationToken cancellationToken)
+ {
+ var resources = await _resourceService.GetAsync(cancellationToken);
+ return Ok(resources);
+ }
+
+ [HttpGet("{id}")]
+ public async Task GetAsync(TId id, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var resource = await _resourceService.GetAsync(id, cancellationToken);
+ return Ok(resource);
+ }
+ catch (ResourceNotFoundException)
+ {
+ return NotFound();
+ }
+ }
+
+ [HttpGet("{id}/relationships/{relationshipName}")]
+ public async Task GetRelationshipsAsync(TId id, string relationshipName, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var relationship = await _resourceService.GetRelationshipAsync(id, relationshipName, cancellationToken);
+ return Ok(relationship);
+ }
+ catch (ResourceNotFoundException)
+ {
+ return NotFound();
+ }
+ }
+
+ [HttpGet("{id}/{relationshipName}")]
+ public async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken)
+ {
+ var relationship = await _resourceService.GetSecondaryAsync(id, relationshipName, cancellationToken);
+ return Ok(relationship);
+ }
+
+ [HttpPost]
+ public async Task PostAsync([FromBody] T resource, CancellationToken cancellationToken)
+ {
+ if (resource == null)
+ return UnprocessableEntity();
+
+ if (_options.AllowClientGeneratedIds && !string.IsNullOrEmpty(resource.StringId))
+ return Forbidden();
+
+ resource = await _resourceService.CreateAsync(resource, cancellationToken);
+
+ return Created($"{HttpContext.Request.Path}/{resource.Id}", resource);
+ }
+
+ [HttpPatch("{id}")]
+ public async Task PatchAsync(TId id, [FromBody] T resource, CancellationToken cancellationToken)
+ {
+ if (resource == null)
+ return UnprocessableEntity();
+
+ try
+ {
+ var updated = await _resourceService.UpdateAsync(id, resource, cancellationToken);
+ return Ok(updated);
+ }
+ catch (ResourceNotFoundException)
+ {
+ return NotFound();
+ }
+ }
+
+ [HttpPatch("{id}/relationships/{relationshipName}")]
+ public async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken)
+ {
+ await _resourceService.SetRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken);
+
+ return Ok();
+ }
+
+ [HttpDelete("{id}")]
+ public async Task DeleteAsync(TId id, CancellationToken cancellationToken)
+ {
+ await _resourceService.DeleteAsync(id, cancellationToken);
+ return NoContent();
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoItemsTestController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoItemsTestController.cs
new file mode 100644
index 0000000..518b34f
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/TodoItemsTestController.cs
@@ -0,0 +1,110 @@
+using System.Collections.Generic;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.Controllers.Annotations;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Serialization.Objects;
+using JsonApiDotNetCore.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public abstract class AbstractTodoItemsController
+ : BaseJsonApiController where T : class, IIdentifiable
+ {
+ protected AbstractTodoItemsController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService service)
+ : base(options, loggerFactory, service)
+ { }
+ }
+
+ [DisableRoutingConvention]
+ [Route("/abstract")]
+ public class TodoItemsTestController : AbstractTodoItemsController
+ {
+ public TodoItemsTestController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService service)
+ : base(options, loggerFactory, service)
+ { }
+
+ [HttpGet]
+ public override async Task GetAsync(CancellationToken cancellationToken)
+ {
+ return await base.GetAsync(cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public override async Task GetAsync(string id, CancellationToken cancellationToken)
+ {
+ return await base.GetAsync(id, cancellationToken);
+ }
+
+ [HttpGet("{id}/{relationshipName}")]
+ public override async Task GetSecondaryAsync(string id, string relationshipName, CancellationToken cancellationToken)
+ {
+ return await base.GetSecondaryAsync(id, relationshipName, cancellationToken);
+ }
+
+ [HttpGet("{id}/relationships/{relationshipName}")]
+ public override async Task GetRelationshipAsync(string id, string relationshipName, CancellationToken cancellationToken)
+ {
+ return await base.GetRelationshipAsync(id, relationshipName, cancellationToken);
+ }
+
+ [HttpPost]
+ public override async Task PostAsync([FromBody] TodoItem resource, CancellationToken cancellationToken)
+ {
+ await Task.Yield();
+
+ return NotFound(new Error(HttpStatusCode.NotFound)
+ {
+ Title = "NotFound ActionResult with explicit error object."
+ });
+ }
+
+ [HttpPost("{id}/relationships/{relationshipName}")]
+ public override async Task PostRelationshipAsync(
+ string id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken)
+ {
+ return await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken);
+ }
+
+ [HttpPatch("{id}")]
+ public override async Task PatchAsync(string id, [FromBody] TodoItem resource, CancellationToken cancellationToken)
+ {
+ await Task.Yield();
+
+ return Conflict("Something went wrong");
+ }
+
+ [HttpPatch("{id}/relationships/{relationshipName}")]
+ public override async Task PatchRelationshipAsync(
+ string id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken)
+ {
+ return await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public override async Task DeleteAsync(string id, CancellationToken cancellationToken)
+ {
+ await Task.Yield();
+
+ return NotFound();
+ }
+
+ [HttpDelete("{id}/relationships/{relationshipName}")]
+ public override async Task DeleteRelationshipAsync(string id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken)
+ {
+ return await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken);
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Controllers/UsersController.cs b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/UsersController.cs
new file mode 100644
index 0000000..78b962b
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Controllers/UsersController.cs
@@ -0,0 +1,28 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Controllers
+{
+ public sealed class UsersController : JsonApiController
+ {
+ public UsersController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+
+ public sealed class SuperUsersController : JsonApiController
+ {
+ public SuperUsersController(
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ { }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Definitions/ArticleHooksDefinition.cs b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/ArticleHooksDefinition.cs
new file mode 100644
index 0000000..76b2a18
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/ArticleHooksDefinition.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Errors;
+using JsonApiDotNetCore.Hooks.Internal.Execution;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Serialization.Objects;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Definitions
+{
+ public class ArticleHooksDefinition : ResourceHooksDefinition
+ {
+ public ArticleHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
+
+ public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline)
+ {
+ if (pipeline == ResourcePipeline.GetSingle && resources.Any(r => r.Caption == "Classified"))
+ {
+ throw new JsonApiException(new Error(HttpStatusCode.Forbidden)
+ {
+ Title = "You are not allowed to see this article."
+ });
+ }
+
+ return resources.Where(t => t.Caption != "This should not be included");
+ }
+ }
+}
+
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Definitions/LockableHooksDefinition.cs b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/LockableHooksDefinition.cs
new file mode 100644
index 0000000..662cf82
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/LockableHooksDefinition.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Errors;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Serialization.Objects;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Definitions
+{
+ public abstract class LockableHooksDefinition : ResourceHooksDefinition where T : class, IIsLockable, IIdentifiable
+ {
+ protected LockableHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
+
+ protected void DisallowLocked(IEnumerable resources)
+ {
+ foreach (var e in resources ?? Enumerable.Empty())
+ {
+ if (e.IsLocked)
+ {
+ throw new JsonApiException(new Error(HttpStatusCode.Forbidden)
+ {
+ Title = "You are not allowed to update fields or relationships of locked todo items."
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Definitions/PassportHooksDefinition.cs b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/PassportHooksDefinition.cs
new file mode 100644
index 0000000..6318fbc
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/PassportHooksDefinition.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Errors;
+using JsonApiDotNetCore.Hooks.Internal.Execution;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Serialization.Objects;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Definitions
+{
+ public class PassportHooksDefinition : ResourceHooksDefinition
+ {
+ public PassportHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
+ {
+ }
+
+ public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null)
+ {
+ if (pipeline == ResourcePipeline.GetSingle && isIncluded)
+ {
+ throw new JsonApiException(new Error(HttpStatusCode.Forbidden)
+ {
+ Title = "You are not allowed to include passports on individual persons."
+ });
+ }
+ }
+
+ public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline)
+ {
+ resourcesByRelationship.GetByRelationship().ToList().ForEach(kvp => DoesNotTouchLockedPassports(kvp.Value));
+ }
+
+ private void DoesNotTouchLockedPassports(IEnumerable resources)
+ {
+ foreach (var passport in resources ?? Enumerable.Empty())
+ {
+ if (passport.IsLocked)
+ {
+ throw new JsonApiException(new Error(HttpStatusCode.Forbidden)
+ {
+ Title = "You are not allowed to update fields or relationships of locked persons."
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Definitions/PersonHooksDefinition.cs b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/PersonHooksDefinition.cs
new file mode 100644
index 0000000..c2b618b
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/PersonHooksDefinition.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Linq;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Hooks.Internal.Execution;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Definitions
+{
+ public class PersonHooksDefinition : LockableHooksDefinition
+ {
+ public PersonHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
+
+ public override IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline)
+ {
+ BeforeImplicitUpdateRelationship(resourcesByRelationship, pipeline);
+ return ids;
+ }
+
+ public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline)
+ {
+ resourcesByRelationship.GetByRelationship().ToList().ForEach(kvp => DisallowLocked(kvp.Value));
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Definitions/TagHooksDefinition.cs b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/TagHooksDefinition.cs
new file mode 100644
index 0000000..3742c70
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/TagHooksDefinition.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Linq;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Hooks.Internal.Execution;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Resources;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Definitions
+{
+ public class TagHooksDefinition : ResourceHooksDefinition
+ {
+ public TagHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
+
+ public override IEnumerable BeforeCreate(IResourceHashSet affected, ResourcePipeline pipeline)
+ {
+ return base.BeforeCreate(affected, pipeline);
+ }
+
+ public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline)
+ {
+ return resources.Where(t => t.Name != "This should not be included");
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Definitions/TodoHooksDefinition.cs b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/TodoHooksDefinition.cs
new file mode 100644
index 0000000..b1ccad4
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/TodoHooksDefinition.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Errors;
+using JsonApiDotNetCore.Hooks.Internal.Execution;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Serialization.Objects;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Definitions
+{
+ public class TodoHooksDefinition : LockableHooksDefinition
+ {
+ public TodoHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
+
+ public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null)
+ {
+ if (stringId == "1337")
+ {
+ throw new JsonApiException(new Error(HttpStatusCode.Forbidden)
+ {
+ Title = "You are not allowed to update the author of todo items."
+ });
+ }
+ }
+
+ public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline)
+ {
+ List todos = resourcesByRelationship.GetByRelationship().SelectMany(kvp => kvp.Value).ToList();
+ DisallowLocked(todos);
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Definitions/TodoItemDefinition.cs b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/TodoItemDefinition.cs
new file mode 100644
index 0000000..c5b9706
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Definitions/TodoItemDefinition.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Resources;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Definitions
+{
+ public sealed class TodoItemDefinition : JsonApiResourceDefinition
+ {
+ public TodoItemDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
+ {
+ }
+
+ public override IDictionary GetMeta(TodoItem resource)
+ {
+ if (resource.Description != null && resource.Description.StartsWith("Important:"))
+ {
+ return new Dictionary
+ {
+ ["hasHighPriority"] = true
+ };
+ }
+
+ return base.GetMeta(resource);
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Dockerfile b/src/JsonApiDotNetCore.MongoDb.Example/Dockerfile
new file mode 100644
index 0000000..c5a5d90
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Dockerfile
@@ -0,0 +1,13 @@
+FROM microsoft/dotnet:latest
+
+COPY . /app
+
+WORKDIR /app
+
+RUN ["dotnet", "restore"]
+
+RUN ["dotnet", "build"]
+
+EXPOSE 14140/tcp
+
+CMD ["dotnet", "run", "--server.urls", "http://*:14140"]
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/JsonApiDotNetCore.MongoDb.Example.csproj b/src/JsonApiDotNetCore.MongoDb.Example/JsonApiDotNetCore.MongoDb.Example.csproj
new file mode 100644
index 0000000..68f4699
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/JsonApiDotNetCore.MongoDb.Example.csproj
@@ -0,0 +1,14 @@
+
+
+ $(NetCoreAppVersion)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Address.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Address.cs
new file mode 100644
index 0000000..686e488
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Address.cs
@@ -0,0 +1,27 @@
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public sealed class Address : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string Street { get; set; }
+
+ [Attr]
+ public string ZipCode { get; set; }
+
+ [HasOne]
+ public Country Country { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Article.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Article.cs
new file mode 100644
index 0000000..525ccad
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Article.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public sealed class Article : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string Caption { get; set; }
+
+ [Attr]
+ public string Url { get; set; }
+
+ // [HasOne]
+ // public Author Author { get; set; }
+ //
+ // [BsonIgnore]
+ // [HasManyThrough(nameof(ArticleTags))]
+ // public ISet Tags { get; set; }
+ // public ISet ArticleTags { get; set; }
+ //
+ // [BsonIgnore]
+ // [HasManyThrough(nameof(IdentifiableArticleTags))]
+ // public ICollection IdentifiableTags { get; set; }
+ // public ICollection IdentifiableArticleTags { get; set; }
+ //
+ // [HasMany]
+ // public ICollection Revisions { get; set; }
+ //
+ // [HasOne]
+ // public Blog Blog { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/ArticleTag.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/ArticleTag.cs
new file mode 100644
index 0000000..6593c3e
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/ArticleTag.cs
@@ -0,0 +1,11 @@
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public sealed class ArticleTag
+ {
+ public int ArticleId { get; set; }
+ public Article Article { get; set; }
+
+ public int TagId { get; set; }
+ public Tag Tag { get; set; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Author.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Author.cs
new file mode 100644
index 0000000..651dd2e
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Author.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public sealed class Author : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string FirstName { get; set; }
+
+ [Attr]
+ public string LastName { get; set; }
+
+ [Attr]
+ public DateTime? DateOfBirth { get; set; }
+
+ [Attr]
+ public string BusinessEmail { get; set; }
+
+ // [HasOne]
+ // public Address LivingAddress { get; set; }
+ //
+ // [HasMany]
+ // public IList Articles { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Blog.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Blog.cs
new file mode 100644
index 0000000..9f80e75
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Blog.cs
@@ -0,0 +1,30 @@
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public sealed class Blog : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string Title { get; set; }
+
+ [Attr]
+ public string CompanyName { get; set; }
+
+ // [HasMany]
+ // public IList Articles { get; set; }
+
+ // [HasOne]
+ // public Author Owner { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Country.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Country.cs
new file mode 100644
index 0000000..28e83d7
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Country.cs
@@ -0,0 +1,21 @@
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public class Country : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string Name { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Gender.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Gender.cs
new file mode 100644
index 0000000..c8545b9
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Gender.cs
@@ -0,0 +1,9 @@
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public enum Gender
+ {
+ Unknown,
+ Male,
+ Female
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/IIsLockable.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/IIsLockable.cs
new file mode 100644
index 0000000..35f8c4e
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/IIsLockable.cs
@@ -0,0 +1,7 @@
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public interface IIsLockable
+ {
+ bool IsLocked { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/IdentifiableArticleTag.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/IdentifiableArticleTag.cs
new file mode 100644
index 0000000..d4304d0
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/IdentifiableArticleTag.cs
@@ -0,0 +1,28 @@
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public class IdentifiableArticleTag : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ public int ArticleId { get; set; }
+ [HasOne]
+ public Article Article { get; set; }
+
+ public int TagId { get; set; }
+ [HasOne]
+ public Tag Tag { get; set; }
+
+ public string SomeMetaData { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/KebabCasedModel.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/KebabCasedModel.cs
new file mode 100644
index 0000000..a72c7b1
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/KebabCasedModel.cs
@@ -0,0 +1,21 @@
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public class KebabCasedModel : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string CompoundAttr { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Passport.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Passport.cs
new file mode 100644
index 0000000..e16fe40
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Passport.cs
@@ -0,0 +1,65 @@
+using System;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public class Passport : IIdentifiable
+ {
+ // private readonly ISystemClock _systemClock;
+ private int? _socialSecurityNumber;
+
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public int? SocialSecurityNumber
+ {
+ get => _socialSecurityNumber;
+ set
+ {
+ if (value != _socialSecurityNumber)
+ {
+ LastSocialSecurityNumberChange = DateTime.UtcNow.ToLocalTime();
+ _socialSecurityNumber = value;
+ }
+ }
+ }
+
+ [Attr]
+ public DateTime LastSocialSecurityNumberChange { get; set; }
+
+ [Attr]
+ public bool IsLocked { get; set; }
+
+ // [HasOne]
+ // public Person Person { get; set; }
+
+ // [Attr]
+ // [NotMapped]
+ // public string BirthCountryName
+ // {
+ // get => BirthCountry?.Name;
+ // set
+ // {
+ // BirthCountry ??= new Country();
+ // BirthCountry.Name = value;
+ // }
+ // }
+
+ // [EagerLoad]
+ // public Country BirthCountry { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+
+ // public Passport(AppDbContext appDbContext)
+ // {
+ // _systemClock = appDbContext.SystemClock;
+ // }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Person.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Person.cs
new file mode 100644
index 0000000..ae66ab6
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Person.cs
@@ -0,0 +1,90 @@
+using System.Linq;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public sealed class PersonRole : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [HasOne]
+ public Person Person { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+
+ public sealed class Person : IIdentifiable, IIsLockable
+ {
+ private string _firstName;
+
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ public bool IsLocked { get; set; }
+
+ [Attr]
+ public string FirstName
+ {
+ get => _firstName;
+ set
+ {
+ if (value != _firstName)
+ {
+ _firstName = value;
+ Initials = string.Concat(value.Split(' ').Select(x => char.ToUpperInvariant(x[0])));
+ }
+ }
+ }
+
+ [Attr]
+ public string Initials { get; set; }
+
+ [Attr]
+ public string LastName { get; set; }
+
+ [Attr(PublicName = "the-Age")]
+ public int Age { get; set; }
+
+ [Attr]
+ public Gender Gender { get; set; }
+
+ [Attr]
+ public string Category { get; set; }
+
+ // [HasMany]
+ // public ISet TodoItems { get; set; }
+ //
+ // [HasMany]
+ // public ISet AssignedTodoItems { get; set; }
+ //
+ // [HasMany]
+ // public HashSet TodoCollections { get; set; }
+ //
+ // [HasOne]
+ // public PersonRole Role { get; set; }
+ //
+ // [HasOne]
+ // public TodoItem OneToOneTodoItem { get; set; }
+ //
+ // [HasOne]
+ // public TodoItem StakeHolderTodoItem { get; set; }
+ //
+ // [HasOne(Links = LinkTypes.All, CanInclude = false)]
+ // public TodoItem UnIncludeableItem { get; set; }
+ //
+ // [HasOne]
+ // public Passport Passport { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Revision.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Revision.cs
new file mode 100644
index 0000000..ad5d763
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Revision.cs
@@ -0,0 +1,28 @@
+using System;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public sealed class Revision : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public DateTime PublishTime { get; set; }
+
+ [HasOne]
+ public Author Author { get; set; }
+
+ [HasOne]
+ public Article Article { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/Tag.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Tag.cs
new file mode 100644
index 0000000..be3c99b
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Tag.cs
@@ -0,0 +1,29 @@
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public class Tag : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string Name { get; set; }
+
+ [Attr]
+ public TagColor Color { get; set; }
+
+ // [NotMapped]
+ // [HasManyThrough(nameof(ArticleTags))]
+ // public ISet Articles { get; set; }
+ // public ISet ArticleTags { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/TagColor.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/TagColor.cs
new file mode 100644
index 0000000..bab8ade
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/TagColor.cs
@@ -0,0 +1,9 @@
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public enum TagColor
+ {
+ Red,
+ Green,
+ Blue
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/ThrowingResource.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/ThrowingResource.cs
new file mode 100644
index 0000000..1af8d68
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/ThrowingResource.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using JsonApiDotNetCore.Serialization;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public sealed class ThrowingResource : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string FailsOnSerialize
+ {
+ get
+ {
+ var isSerializingResponse = new StackTrace().GetFrames()
+ .Any(frame => frame.GetMethod().DeclaringType == typeof(JsonApiWriter));
+
+ if (isSerializingResponse)
+ {
+ throw new InvalidOperationException($"The value for the '{nameof(FailsOnSerialize)}' property is currently unavailable.");
+ }
+
+ return string.Empty;
+ }
+ set { }
+ }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/TodoItem.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/TodoItem.cs
new file mode 100644
index 0000000..5ed4ba6
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/TodoItem.cs
@@ -0,0 +1,72 @@
+using System;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public class TodoItem : IIdentifiable, IIsLockable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ public bool IsLocked { get; set; }
+
+ [Attr]
+ public string Description { get; set; }
+
+ [Attr]
+ public long Ordinal { get; set; }
+
+ [Attr(Capabilities = AttrCapabilities.All & ~AttrCapabilities.AllowCreate)]
+ public string AlwaysChangingValue
+ {
+ get => Guid.NewGuid().ToString();
+ set { }
+ }
+
+ [Attr]
+ public DateTime CreatedDate { get; set; }
+
+ [Attr(Capabilities = AttrCapabilities.All & ~(AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort))]
+ public DateTime? AchievedDate { get; set; }
+
+ [Attr(Capabilities = AttrCapabilities.All & ~(AttrCapabilities.AllowCreate | AttrCapabilities.AllowChange))]
+ public string CalculatedValue => "calculated";
+
+ [Attr(Capabilities = AttrCapabilities.All & ~AttrCapabilities.AllowChange)]
+ public DateTimeOffset? OffsetDate { get; set; }
+
+ // [HasOne]
+ // public Person Owner { get; set; }
+ //
+ // [HasOne]
+ // public Person Assignee { get; set; }
+ //
+ // [HasOne]
+ // public Person OneToOnePerson { get; set; }
+ //
+ // [HasMany]
+ // public ISet StakeHolders { get; set; }
+ //
+ // [HasOne]
+ // public TodoItemCollection Collection { get; set; }
+ //
+ // // cyclical to-one structure
+ // [HasOne]
+ // public TodoItem DependentOnTodo { get; set; }
+ //
+ // // cyclical to-many structure
+ // [HasOne]
+ // public TodoItem ParentTodo { get; set; }
+ //
+ // [HasMany]
+ // public IList ChildrenTodos { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/TodoItemCollection.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/TodoItemCollection.cs
new file mode 100644
index 0000000..9300816
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/TodoItemCollection.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ [Resource("todoCollections")]
+ public sealed class TodoItemCollection : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string Name { get; set; }
+
+ // [HasMany]
+ // public ISet TodoItems { get; set; }
+ //
+ // [HasOne]
+ // public Person Owner { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Models/User.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/User.cs
new file mode 100644
index 0000000..412d415
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/User.cs
@@ -0,0 +1,54 @@
+using System;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Models
+{
+ public class User : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ // private readonly ISystemClock _systemClock;
+ private string _password;
+
+ [Attr] public string UserName { get; set; }
+
+ [Attr(Capabilities = AttrCapabilities.AllowCreate | AttrCapabilities.AllowChange)]
+ public string Password
+ {
+ get => _password;
+ set
+ {
+ if (value != _password)
+ {
+ _password = value;
+ LastPasswordChange = DateTime.UtcNow.ToLocalTime();
+ }
+ }
+ }
+
+ [Attr] public DateTime LastPasswordChange { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+
+ // public User(AppDbContext appDbContext)
+ // {
+ // _systemClock = appDbContext.SystemClock;
+ // }
+ }
+
+ public sealed class SuperUser : User
+ {
+ [Attr] public int SecurityLevel { get; set; }
+
+ // public SuperUser(AppDbContext appDbContext) : base(appDbContext)
+ // {
+ // }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Program.cs b/src/JsonApiDotNetCore.MongoDb.Example/Program.cs
new file mode 100644
index 0000000..d718b50
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Program.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+
+namespace JsonApiDotNetCore.MongoDb.Example
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ });
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Properties/launchSettings.json b/src/JsonApiDotNetCore.MongoDb.Example/Properties/launchSettings.json
new file mode 100644
index 0000000..1e3998e
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Properties/launchSettings.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:14140",
+ "sslPort": 44340
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": false,
+ "launchUrl": "api/v1/todoItems",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Kestrel": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "launchUrl": "api/v1/todoItems",
+ "applicationUrl": "https://localhost:44340;http://localhost:14140",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Services/CustomArticleService.cs b/src/JsonApiDotNetCore.MongoDb.Example/Services/CustomArticleService.cs
new file mode 100644
index 0000000..4f1eed2
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Services/CustomArticleService.cs
@@ -0,0 +1,37 @@
+using System.Threading;
+using System.Threading.Tasks;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Hooks;
+using JsonApiDotNetCore.Middleware;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.Queries;
+using JsonApiDotNetCore.Repositories;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Services
+{
+ public class CustomArticleService : JsonApiResourceService
+ {
+ public CustomArticleService(
+ IResourceRepositoryAccessor repositoryAccessor,
+ IQueryLayerComposer queryLayerComposer,
+ IPaginationContext paginationContext,
+ IJsonApiOptions options,
+ ILoggerFactory loggerFactory,
+ IJsonApiRequest request,
+ IResourceChangeTracker resourceChangeTracker,
+ IResourceHookExecutorFacade hookExecutor)
+ : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request,
+ resourceChangeTracker, hookExecutor)
+ { }
+
+ public override async Task GetAsync(string id, CancellationToken cancellationToken)
+ {
+ var resource = await base.GetAsync(id, cancellationToken);
+ resource.Caption = "None for you Glen Coco";
+ return resource;
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Services/SkipCacheQueryStringParameterReader.cs b/src/JsonApiDotNetCore.MongoDb.Example/Services/SkipCacheQueryStringParameterReader.cs
new file mode 100644
index 0000000..8af3c32
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Services/SkipCacheQueryStringParameterReader.cs
@@ -0,0 +1,36 @@
+using System.Linq;
+using JsonApiDotNetCore.Controllers.Annotations;
+using JsonApiDotNetCore.Errors;
+using JsonApiDotNetCore.QueryStrings;
+using Microsoft.Extensions.Primitives;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Services
+{
+ public class SkipCacheQueryStringParameterReader : IQueryStringParameterReader
+ {
+ private const string _skipCacheParameterName = "skipCache";
+
+ public bool SkipCache { get; private set; }
+
+ public bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttribute)
+ {
+ return !disableQueryStringAttribute.ParameterNames.Contains(_skipCacheParameterName);
+ }
+
+ public bool CanRead(string parameterName)
+ {
+ return parameterName == _skipCacheParameterName;
+ }
+
+ public void Read(string parameterName, StringValues parameterValue)
+ {
+ if (!bool.TryParse(parameterValue, out bool skipCache))
+ {
+ throw new InvalidQueryStringParameterException(parameterName, "Boolean value required.",
+ $"The value {parameterValue} is not a valid boolean.");
+ }
+
+ SkipCache = skipCache;
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Startups/EmptyStartup.cs b/src/JsonApiDotNetCore.MongoDb.Example/Startups/EmptyStartup.cs
new file mode 100644
index 0000000..2ff1251
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Startups/EmptyStartup.cs
@@ -0,0 +1,26 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace JsonApiDotNetCore.MongoDb.Example
+{
+ ///
+ /// Empty startup class, required for integration tests.
+ /// Changes in ASP.NET Core 3 no longer allow Startup class to be defined in test projects. See https://github.com/aspnet/AspNetCore/issues/15373.
+ ///
+ public abstract class EmptyStartup
+ {
+ protected EmptyStartup(IConfiguration configuration)
+ {
+ }
+
+ public virtual void ConfigureServices(IServiceCollection services)
+ {
+ }
+
+ public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
+ {
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Startups/Startup.cs b/src/JsonApiDotNetCore.MongoDb.Example/Startups/Startup.cs
new file mode 100644
index 0000000..a2d7854
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Startups/Startup.cs
@@ -0,0 +1,107 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.MongoDb;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+using JsonApiDotNetCore.MongoDb.Example.Services;
+using JsonApiDotNetCore.QueryStrings;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using MongoDB.Driver;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace JsonApiDotNetCore.MongoDb.Example
+{
+ public class Startup : EmptyStartup
+ {
+ private IConfiguration Configuration { get; }
+
+ public Startup(IConfiguration configuration) : base(configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ ConfigureClock(services);
+
+ services.AddScoped();
+ services.AddScoped(sp => sp.GetRequiredService());
+
+ // TryAddSingleton will only register the IMongoDatabase if there is no
+ // previously registered instance - will make tests use individual dbs
+ services.TryAddSingleton(sp =>
+ {
+ var client = new MongoClient(Configuration.GetSection("DatabaseSettings:ConnectionString").Value);
+ return client.GetDatabase(Configuration.GetSection("DatabaseSettings:Database").Value);
+ });
+
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+ services.AddResourceRepository>();
+
+
+ services.AddJsonApi(
+ ConfigureJsonApiOptions,
+ resources: builder =>
+ {
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ builder.Add();
+ });
+
+ // once all tests have been moved to WebApplicationFactory format we can get rid of this line below
+ services.AddClientSerialization();
+ }
+
+ private void ConfigureClock(IServiceCollection services)
+ {
+ services.AddSingleton();
+ }
+
+ protected virtual void ConfigureJsonApiOptions(JsonApiOptions options)
+ {
+ options.IncludeExceptionStackTraceInErrors = true;
+ options.Namespace = "api/v1";
+ options.DefaultPageSize = new PageSize(5);
+ options.IncludeTotalResourceCount = true;
+ options.ValidateModelState = true;
+ options.SerializerSettings.Formatting = Formatting.Indented;
+ options.SerializerSettings.Converters.Add(new StringEnumConverter());
+ }
+
+ public override void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
+ {
+ app.UseRouting();
+ app.UseJsonApi();
+ app.UseEndpoints(endpoints => endpoints.MapControllers());
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/Startups/TestStartup.cs b/src/JsonApiDotNetCore.MongoDb.Example/Startups/TestStartup.cs
new file mode 100644
index 0000000..cf99c40
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Startups/TestStartup.cs
@@ -0,0 +1,26 @@
+using JsonApiDotNetCore.Configuration;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace JsonApiDotNetCore.MongoDb.Example
+{
+ public class TestStartup : EmptyStartup
+ {
+ public TestStartup(IConfiguration configuration) : base(configuration)
+ {
+ }
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ }
+
+ public override void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
+ {
+ app.UseRouting();
+ app.UseJsonApi();
+ app.UseEndpoints(endpoints => endpoints.MapControllers());
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.Example/appsettings.json b/src/JsonApiDotNetCore.MongoDb.Example/appsettings.json
new file mode 100644
index 0000000..ff9ab4c
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.Example/appsettings.json
@@ -0,0 +1,14 @@
+{
+ "DatabaseSettings": {
+ "ConnectionString": "mongodb://localhost:27017",
+ "Database": "JsonApiDotNetCoreExample"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Warning",
+ "Microsoft": "Warning",
+ "Microsoft.EntityFrameworkCore.Database.Command": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.GettingStarted/Controllers/BooksController.cs b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Controllers/BooksController.cs
new file mode 100644
index 0000000..e80e46a
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Controllers/BooksController.cs
@@ -0,0 +1,16 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.GettingStarted.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCore.MongoDb.GettingStarted.Controllers
+{
+ public sealed class BooksController : JsonApiController
+ {
+ public BooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, loggerFactory, resourceService)
+ {
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.GettingStarted/JsonApiDotNetCore.MongoDb.GettingStarted.csproj b/src/JsonApiDotNetCore.MongoDb.GettingStarted/JsonApiDotNetCore.MongoDb.GettingStarted.csproj
new file mode 100644
index 0000000..68f4699
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/JsonApiDotNetCore.MongoDb.GettingStarted.csproj
@@ -0,0 +1,14 @@
+
+
+ $(NetCoreAppVersion)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/JsonApiDotNetCore.MongoDb.GettingStarted/Models/Book.cs b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Models/Book.cs
new file mode 100644
index 0000000..89d7a74
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Models/Book.cs
@@ -0,0 +1,31 @@
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.GettingStarted.Models
+{
+ public sealed class Book : IIdentifiable
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [Attr]
+ public string Id { get; set; }
+
+ [Attr]
+ public string Name { get; set; }
+
+ [Attr]
+ [BsonRepresentation(BsonType.Decimal128)]
+ public decimal Price { get; set; }
+
+ [Attr]
+ public string Category { get; set; }
+
+ [Attr]
+ public string Author { get; set; }
+
+ [BsonIgnore]
+ public string StringId { get => Id; set => Id = value; }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.GettingStarted/Program.cs b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Program.cs
new file mode 100644
index 0000000..87d567e
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Program.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+
+namespace JsonApiDotNetCore.MongoDb.GettingStarted
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ private static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ });
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.GettingStarted/Properties/launchSettings.json b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Properties/launchSettings.json
new file mode 100644
index 0000000..e360e7a
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:52498",
+ "sslPort": 44343
+ }
+ },
+ "profiles": {
+ "Kestrel": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "launchUrl": "api/books",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+ }
+
diff --git a/src/JsonApiDotNetCore.MongoDb.GettingStarted/README.md b/src/JsonApiDotNetCore.MongoDb.GettingStarted/README.md
new file mode 100644
index 0000000..833ce87
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/README.md
@@ -0,0 +1,14 @@
+## Sample project
+
+## Usage
+
+`dotnet run` to run the project
+
+You can verify the project is running by checking this endpoint:
+`localhost:14141/api/people`
+
+For further documentation and implementation of a JsonApiDotnetCore Application see the documentation or GitHub page:
+
+Repository: https://github.com/json-api-dotnet/JsonApiDotNetCore
+
+Documentation: http://www.jsonapi.net
diff --git a/src/JsonApiDotNetCore.MongoDb.GettingStarted/Startup.cs b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Startup.cs
new file mode 100644
index 0000000..908bd90
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Startup.cs
@@ -0,0 +1,52 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.MongoDb;
+using JsonApiDotNetCore.MongoDb.GettingStarted.Models;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using MongoDB.Driver;
+using Newtonsoft.Json;
+
+namespace JsonApiDotNetCore.MongoDb.GettingStarted
+{
+ public sealed class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ private IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton(sp =>
+ {
+ var client = new MongoClient(Configuration.GetSection("DatabaseSettings:ConnectionString").Value);
+ return client.GetDatabase(Configuration.GetSection("DatabaseSettings:Database").Value);
+ });
+
+ services.AddResourceRepository>();
+ services.AddJsonApi(options =>
+ {
+ options.Namespace = "api";
+ options.UseRelativeLinks = true;
+ options.IncludeTotalResourceCount = true;
+ options.SerializerSettings.Formatting = Formatting.Indented;
+ }, resources: builder =>
+ {
+ builder.Add();
+ });
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseHttpsRedirection();
+ app.UseRouting();
+ app.UseJsonApi();
+ app.UseEndpoints(endpoints => endpoints.MapControllers());
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb.GettingStarted/appsettings.json b/src/JsonApiDotNetCore.MongoDb.GettingStarted/appsettings.json
new file mode 100644
index 0000000..7eecbe7
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/appsettings.json
@@ -0,0 +1,14 @@
+{
+ "DatabaseSettings": {
+ "ConnectionString": "mongodb://localhost:27017",
+ "Database": "JsonApiDotNetCoreExample"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/src/JsonApiDotNetCore.MongoDb/Data/MongoEntityRepository.cs b/src/JsonApiDotNetCore.MongoDb/Data/MongoEntityRepository.cs
deleted file mode 100644
index c926da0..0000000
--- a/src/JsonApiDotNetCore.MongoDb/Data/MongoEntityRepository.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-namespace JsonApiDotNetCore.MongoDb.Data
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using JsonApiDotNetCore.Data;
- using JsonApiDotNetCore.Extensions;
- using JsonApiDotNetCore.Internal.Query;
- using JsonApiDotNetCore.Models;
- using JsonApiDotNetCore.MongoDb.Extensions;
- using JsonApiDotNetCore.Services;
- using MongoDB.Bson;
- using MongoDB.Driver;
-
- public class MongoEntityRepository
- : IEntityRepository
- where TEntity : class, IIdentifiable
- {
- private readonly IMongoDatabase db;
-
- private readonly string collectionName;
-
- private readonly IJsonApiContext jsonApiContext;
-
- public MongoEntityRepository(IMongoDatabase db, string collectionName, IJsonApiContext jsonApiContext)
- {
- this.db = db;
- this.collectionName = collectionName;
- this.jsonApiContext = jsonApiContext;
- }
-
- private IMongoCollection Collection => this.db.GetCollection(this.collectionName);
-
- private IQueryable Entities => this.Collection.AsQueryable();
-
- public async Task CountAsync(IQueryable entities)
- {
- return (int)await this.Collection.CountAsync(Builders.Filter.Empty);
- }
-
- public async Task CreateAsync(TEntity entity)
- {
- await this.Collection.InsertOneAsync(entity);
-
- return entity;
- }
-
- public async Task DeleteAsync(TId id)
- {
- var result = await this.Collection.DeleteOneAsync(Builders.Filter.Eq(e => e.Id, id));
-
- return result.IsAcknowledged && result.DeletedCount > 0;
- }
-
- public IQueryable Filter(IQueryable entities, FilterQuery filterQuery)
- {
- return entities.Filter(this.jsonApiContext, filterQuery);
- }
-
- public Task FirstOrDefaultAsync(IQueryable entities)
- {
- return entities.FirstOrDefaultAsync();
- }
-
- public IQueryable Get()
- {
- List fields = this.jsonApiContext.QuerySet?.Fields;
- if (fields?.Any() ?? false)
- {
- return this.Entities.Select(fields);
- }
-
- return this.Entities;
- }
-
- public Task GetAndIncludeAsync(TId id, string relationshipName)
- {
- // this is a document DB, no relations!
- return this.GetAsync(id);
- }
-
- public Task GetAsync(TId id)
- {
- return this.Collection.Find(Builders.Filter.Eq(e => e.Id, id)).SingleOrDefaultAsync();
- }
-
- public IQueryable Include(IQueryable entities, string relationshipName)
- {
- // this is a document DB, no relations!
- return entities;
- }
-
- public async Task> PageAsync(IQueryable entities, int pageSize, int pageNumber)
- {
- return await entities.PageForward(pageSize, pageNumber).ToListAsync();
- }
-
- public Task SingleOrDefaultAsync(IQueryable queryable)
- {
- return queryable.SingleOrDefaultAsync();
- }
-
- public IQueryable Sort(IQueryable entities, List sortQueries)
- {
- return entities.Sort(sortQueries);
- }
-
- public Task> ToListAsync(IQueryable entities)
- {
- return entities.ToListAsync();
- }
-
- public async Task UpdateAsync(TId id, TEntity entity)
- {
- var existingEntity = await this.GetAsync(id);
-
- if (existingEntity == null)
- {
- return null;
- }
-
- foreach (var attr in this.jsonApiContext.AttributesToUpdate)
- {
- attr.Key.SetValue(existingEntity, attr.Value);
- }
-
- foreach (var relationship in this.jsonApiContext.RelationshipsToUpdate)
- {
- relationship.Key.SetValue(existingEntity, relationship.Value);
- }
-
- await this.Collection.ReplaceOneAsync(Builders.Filter.Eq(e => e.Id, id), existingEntity);
-
- return existingEntity;
- }
-
- public Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable relationshipIds)
- {
- throw new NotImplementedException();
- }
-
- internal IEnumerable GetAllDocuments() => this.Collection.Find(new BsonDocument()).ToEnumerable();
- }
-}
diff --git a/src/JsonApiDotNetCore.MongoDb/Extensions/MongoIQueryableExtensions.cs b/src/JsonApiDotNetCore.MongoDb/Extensions/MongoIQueryableExtensions.cs
index f245c35..f163bcc 100644
--- a/src/JsonApiDotNetCore.MongoDb/Extensions/MongoIQueryableExtensions.cs
+++ b/src/JsonApiDotNetCore.MongoDb/Extensions/MongoIQueryableExtensions.cs
@@ -1,37 +1,20 @@
-namespace JsonApiDotNetCore.MongoDb.Extensions
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using MongoDB.Driver;
- using MongoDB.Driver.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+namespace JsonApiDotNetCore.MongoDb.Extensions
+{
public static class MongoIQueryableExtensions
{
- public static async Task> ToListAsync(this IQueryable queryable)
- {
- return await IAsyncCursorSourceExtensions.ToListAsync(ToMongoQueryable(queryable));
- }
-
- public static async Task SingleOrDefaultAsync(this IQueryable queryable)
- {
- return await IAsyncCursorSourceExtensions.SingleOrDefaultAsync(ToMongoQueryable(queryable));
- }
+ public static async Task> ToListAsync(this IQueryable queryable) =>
+ await IAsyncCursorSourceExtensions.ToListAsync(ToMongoQueryable(queryable));
- public static async Task FirstOrDefaultAsync(this IQueryable queryable)
- {
- return await IAsyncCursorSourceExtensions.FirstOrDefaultAsync(ToMongoQueryable(queryable));
- }
-
- private static IMongoQueryable ToMongoQueryable(IQueryable queryable)
- {
- if (!(queryable is IMongoQueryable mongoQueryable))
- {
+ private static IMongoQueryable ToMongoQueryable(IQueryable queryable) =>
+ (queryable is IMongoQueryable mongoQueryable) ?
+ mongoQueryable :
throw new ArgumentException($"This MongoDB-specific extension method expects a {nameof(IMongoQueryable)} and cannot work with {nameof(IQueryable)} of type {queryable.GetType().Name}.");
- }
-
- return mongoQueryable;
- }
}
}
diff --git a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
index dac47a6..21a35bc 100644
--- a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
+++ b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
@@ -1,12 +1,29 @@
- netcoreapp2.0
+ 4.0.0-rc
+ $(NetCoreAppVersion)
+ true
+
+ jsonapi;json:api;dotnet;core;MongoDB
+ Persistence layer implementation for use of mongodb in applications using JsonApiDotNetCore
+ https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb
+ MIT
+ false
+ true
+ true
+ embedded
+
+
+
+
+
+
-
-
+
+
diff --git a/src/JsonApiDotNetCore.MongoDb/MongoEntityRepository.cs b/src/JsonApiDotNetCore.MongoDb/MongoEntityRepository.cs
new file mode 100644
index 0000000..f49f2ad
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb/MongoEntityRepository.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.MongoDb.Extensions;
+using JsonApiDotNetCore.Queries.Internal.QueryableBuilding;
+using JsonApiDotNetCore.Repositories;
+using JsonApiDotNetCore.Resources;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+using JsonApiDotNetCore.Queries;
+using JsonApiDotNetCore.Queries.Expressions;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+
+namespace JsonApiDotNetCore.MongoDb
+{
+ public class MongoEntityRepository
+ : IResourceRepository
+ where TResource : class, IIdentifiable
+ {
+ private readonly IMongoDatabase _db;
+ private readonly ITargetedFields _targetedFields;
+ private readonly IResourceContextProvider _resourceContextProvider;
+ private readonly IResourceFactory _resourceFactory;
+
+ public MongoEntityRepository(
+ IMongoDatabase db,
+ ITargetedFields targetedFields,
+ IResourceContextProvider resourceContextProvider,
+ IResourceFactory resourceFactory)
+ {
+ _db = db;
+ _targetedFields = targetedFields;
+ _resourceContextProvider = resourceContextProvider;
+ _resourceFactory = resourceFactory;
+ }
+
+ private IMongoCollection Collection => _db.GetCollection(typeof(TResource).Name);
+ private IMongoQueryable Entities => Collection.AsQueryable();
+
+ public virtual async Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) =>
+ await ApplyQueryLayer(layer).ToListAsync();
+
+ public virtual Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken)
+ {
+ var resourceContext = _resourceContextProvider.GetResourceContext();
+ var layer = new QueryLayer(resourceContext)
+ {
+ Filter = topFilter
+ };
+
+ var query = ApplyQueryLayer(layer);
+ return query.CountAsync(cancellationToken);
+ }
+
+ public virtual Task GetForCreateAsync(TId id, CancellationToken cancellationToken)
+ {
+ var resource = _resourceFactory.CreateInstance