diff --git a/.gitignore b/.gitignore
index 1e7e35a..74e917a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,90 +1,369 @@
-# Created by https://www.toptal.com/developers/gitignore/api/dotnetcore,vscode,macos,linux,windows
-# Edit at https://www.toptal.com/developers/gitignore?templates=dotnetcore,vscode,macos,linux,windows
+## 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
-### DotnetCore ###
-# .NET Core build folders
-bin/
-obj/
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
-# Common node modules locations
-/node_modules
-/wwwroot/node_modules
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
-### Linux ###
+# 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
-# temporary files which can be created if a process still has a handle open of a deleted file
-.fuse_hidden*
-
-# KDE directory preferences
-.directory
-
-# Linux trash folder which might appear on any partition or disk
-.Trash-*
-
-# .nfs files are created when an open file is removed but is still being accessed
-.nfs*
-
-### macOS ###
-# General
-.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-
-### vscode ###
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-*.code-workspace
-
-### Windows ###
-# Windows thumbnail cache files
-Thumbs.db
-Thumbs.db:encryptable
-ehthumbs.db
-ehthumbs_vista.db
-
-# Dump file
-*.stackdump
-
-# Folder config file
-[Dd]esktop.ini
-
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
-
-# Windows Installer files
-*.cab
-*.msi
-*.msix
-*.msm
-*.msp
-
-# Windows shortcuts
-*.lnk
-
-# End of https://www.toptal.com/developers/gitignore/api/dotnetcore,vscode,macos,linux,windows
+# Sqlite example databases
+*.db
diff --git a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/contentModel.xml b/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/contentModel.xml
deleted file mode 100644
index 701e3d0..0000000
--- a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/contentModel.xml
+++ /dev/null
@@ -1,109 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/encodings.xml b/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/encodings.xml
deleted file mode 100644
index df87cf9..0000000
--- a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/indexLayout.xml b/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/indexLayout.xml
deleted file mode 100644
index 27ba142..0000000
--- a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/modules.xml b/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/modules.xml
deleted file mode 100644
index f01f435..0000000
--- a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/projectSettingsUpdater.xml b/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/projectSettingsUpdater.xml
deleted file mode 100644
index 4bb9f4d..0000000
--- a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/projectSettingsUpdater.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/vcs.xml b/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/workspace.xml b/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/workspace.xml
deleted file mode 100644
index 2afb3ca..0000000
--- a/.idea/.idea.JsonApiDotNetCore.MongoDb/.idea/workspace.xml
+++ /dev/null
@@ -1,314 +0,0 @@
-
-
-
- src/Example/Example.csproj
- test/JsonApiDotNetCore.MongoDb.IntegrationTests/JsonApiDotNetCore.MongoDb.IntegrationTests.csproj
- test/JsonApiDotNetCore.MongoDb.Example.Tests/JsonApiDotNetCore.MongoDb.Example.Tests.csproj
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1604674714591
-
-
- 1604674714591
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.JsonApiDotNetCore.MongoDb/riderModule.iml b/.idea/.idea.JsonApiDotNetCore.MongoDb/riderModule.iml
deleted file mode 100644
index d233658..0000000
--- a/.idea/.idea.JsonApiDotNetCore.MongoDb/riderModule.iml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 1c08e62..6ae2058 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,11 +2,9 @@ language: csharp
mono: none
dotnet: 3.1.100
solution: JsonApiDotNetCore.MongoDb.sln
-services:
- - mongodb
script:
- dotnet restore
- - dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=./coverage/lcov-coverage
+ - 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:
diff --git a/Directory.Build.props b/Directory.Build.props
index b427d93..7f73136 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,8 +2,8 @@
netcoreapp3.1
3.1.*
- 4.0.0-beta1
- 2.11.2
+ 4.0.*
+ 2.11.*
diff --git a/JsonApiDotNetCore.MongoDb.sln b/JsonApiDotNetCore.MongoDb.sln
index e63cbca..5994f4a 100644
--- a/JsonApiDotNetCore.MongoDb.sln
+++ b/JsonApiDotNetCore.MongoDb.sln
@@ -5,16 +5,16 @@ 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}") = "Example", "src\Example\Example.csproj", "{600A3E66-E63F-427D-A991-4CD2067041F9}"
+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.IntegrationTests", "test\JsonApiDotNetCore.MongoDb.IntegrationTests\JsonApiDotNetCore.MongoDb.IntegrationTests.csproj", "{5C59FDFE-F079-4015-9975-FEFA766F0787}"
-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
@@ -52,18 +52,6 @@ Global
{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
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Debug|x64.ActiveCfg = Debug|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Debug|x64.Build.0 = Debug|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Debug|x86.ActiveCfg = Debug|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Debug|x86.Build.0 = Debug|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Release|Any CPU.Build.0 = Release|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Release|x64.ActiveCfg = Release|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Release|x64.Build.0 = Release|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.Release|x86.ActiveCfg = Release|Any CPU
- {5C59FDFE-F079-4015-9975-FEFA766F0787}.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
@@ -76,11 +64,23 @@ Global
{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}
- {5C59FDFE-F079-4015-9975-FEFA766F0787} = {19A533AA-E006-496D-A476-364DF2B637A1}
{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/JsonApiDotNetCore.MongoDb.sln.DotSettings.user b/JsonApiDotNetCore.MongoDb.sln.DotSettings.user
deleted file mode 100644
index 0829a84..0000000
--- a/JsonApiDotNetCore.MongoDb.sln.DotSettings.user
+++ /dev/null
@@ -1,4 +0,0 @@
-
- <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
- <Solution />
-</SessionState>
\ No newline at end of file
diff --git a/README.md b/README.md
index bfdc70a..c7b5ac1 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,8 @@ public sealed class BooksController : JsonApiController
```cs
public class Startup
{
- public IServiceProvider ConfigureServices(IServiceCollection services) {
+ public IServiceProvider ConfigureServices(IServiceCollection services)
+ {
services.AddSingleton(sp =>
{
var client = new MongoClient(Configuration.GetSection("DatabaseSettings:ConnectionString").Value);
@@ -71,14 +72,33 @@ public class Startup
// ...
}
- public void Configure(IApplicationBuilder app) {
+ 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)
-- Projections 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/Example/Example.csproj b/src/JsonApiDotNetCore.MongoDb.Example/JsonApiDotNetCore.MongoDb.Example.csproj
similarity index 100%
rename from src/Example/Example.csproj
rename to src/JsonApiDotNetCore.MongoDb.Example/JsonApiDotNetCore.MongoDb.Example.csproj
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/test/JsonApiDotNetCore.MongoDb.IntegrationTests/Models/Book.cs b/src/JsonApiDotNetCore.MongoDb.Example/Models/Country.cs
similarity index 59%
rename from test/JsonApiDotNetCore.MongoDb.IntegrationTests/Models/Book.cs
rename to src/JsonApiDotNetCore.MongoDb.Example/Models/Country.cs
index 15a90d9..28e83d7 100644
--- a/test/JsonApiDotNetCore.MongoDb.IntegrationTests/Models/Book.cs
+++ b/src/JsonApiDotNetCore.MongoDb.Example/Models/Country.cs
@@ -3,26 +3,17 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
-namespace JsonApiDotNetCore.MongoDb.IntegrationTests.Models
+namespace JsonApiDotNetCore.MongoDb.Example.Models
{
- public sealed class Book : IIdentifiable
+ public class Country : IIdentifiable
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[Attr]
public string Id { get; set; }
-
+
[Attr]
public string Name { get; set; }
-
- [Attr]
- 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.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/Example/Controllers/BooksController.cs b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Controllers/BooksController.cs
similarity index 79%
rename from src/Example/Controllers/BooksController.cs
rename to src/JsonApiDotNetCore.MongoDb.GettingStarted/Controllers/BooksController.cs
index 5a78d91..e80e46a 100644
--- a/src/Example/Controllers/BooksController.cs
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Controllers/BooksController.cs
@@ -1,10 +1,10 @@
-using Example.Models;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.MongoDb.GettingStarted.Models;
using JsonApiDotNetCore.Services;
using Microsoft.Extensions.Logging;
-namespace Example.Controllers
+namespace JsonApiDotNetCore.MongoDb.GettingStarted.Controllers
{
public sealed class BooksController : JsonApiController
{
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/Example/Models/Book.cs b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Models/Book.cs
similarity index 92%
rename from src/Example/Models/Book.cs
rename to src/JsonApiDotNetCore.MongoDb.GettingStarted/Models/Book.cs
index 022cf17..89d7a74 100644
--- a/src/Example/Models/Book.cs
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Models/Book.cs
@@ -3,7 +3,7 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
-namespace Example.Models
+namespace JsonApiDotNetCore.MongoDb.GettingStarted.Models
{
public sealed class Book : IIdentifiable
{
diff --git a/src/Example/Program.cs b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Program.cs
similarity index 90%
rename from src/Example/Program.cs
rename to src/JsonApiDotNetCore.MongoDb.GettingStarted/Program.cs
index 1a94e50..87d567e 100644
--- a/src/Example/Program.cs
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Program.cs
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
-namespace Example
+namespace JsonApiDotNetCore.MongoDb.GettingStarted
{
public class Program
{
diff --git a/src/Example/Properties/launchSettings.json b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Properties/launchSettings.json
similarity index 96%
rename from src/Example/Properties/launchSettings.json
rename to src/JsonApiDotNetCore.MongoDb.GettingStarted/Properties/launchSettings.json
index 4a6248f..e360e7a 100644
--- a/src/Example/Properties/launchSettings.json
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Properties/launchSettings.json
@@ -9,7 +9,7 @@
}
},
"profiles": {
- "Test": {
+ "Kestrel": {
"commandName": "Project",
"launchBrowser": false,
"launchUrl": "api/books",
@@ -20,4 +20,4 @@
}
}
}
-
\ No newline at end of file
+
diff --git a/src/Example/README.md b/src/JsonApiDotNetCore.MongoDb.GettingStarted/README.md
similarity index 87%
rename from src/Example/README.md
rename to src/JsonApiDotNetCore.MongoDb.GettingStarted/README.md
index d79afbd..833ce87 100644
--- a/src/Example/README.md
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/README.md
@@ -11,4 +11,4 @@ For further documentation and implementation of a JsonApiDotnetCore Application
Repository: https://github.com/json-api-dotnet/JsonApiDotNetCore
-Documentation: https://json-api-dotnet.github.io/
+Documentation: http://www.jsonapi.net
diff --git a/src/Example/Startup.cs b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Startup.cs
similarity index 82%
rename from src/Example/Startup.cs
rename to src/JsonApiDotNetCore.MongoDb.GettingStarted/Startup.cs
index d3a2b96..908bd90 100644
--- a/src/Example/Startup.cs
+++ b/src/JsonApiDotNetCore.MongoDb.GettingStarted/Startup.cs
@@ -1,15 +1,13 @@
-using Example.Models;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.MongoDb;
-using JsonApiDotNetCore.Repositories;
+using JsonApiDotNetCore.MongoDb.GettingStarted.Models;
using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using Newtonsoft.Json;
-namespace Example
+namespace JsonApiDotNetCore.MongoDb.GettingStarted
{
public sealed class Startup
{
@@ -29,7 +27,7 @@ public void ConfigureServices(IServiceCollection services)
return client.GetDatabase(Configuration.GetSection("DatabaseSettings:Database").Value);
});
- services.AddScoped, MongoEntityRepository>();
+ services.AddResourceRepository>();
services.AddJsonApi(options =>
{
options.Namespace = "api";
@@ -43,7 +41,7 @@ public void ConfigureServices(IServiceCollection services)
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ public void Configure(IApplicationBuilder app)
{
app.UseHttpsRedirection();
app.UseRouting();
diff --git a/src/Example/appsettings.json b/src/JsonApiDotNetCore.MongoDb.GettingStarted/appsettings.json
similarity index 100%
rename from src/Example/appsettings.json
rename to src/JsonApiDotNetCore.MongoDb.GettingStarted/appsettings.json
diff --git a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
index 13281f3..21a35bc 100644
--- a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
+++ b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
@@ -1,15 +1,15 @@
- 4.0.0-beta1
+ 4.0.0-rc
$(NetCoreAppVersion)
true
- jsonapi;json:api;dotnet;core;mongodb
+ jsonapi;json:api;dotnet;core;MongoDB
Persistence layer implementation for use of mongodb in applications using JsonApiDotNetCore
- https://github.com/mrnkr/JsonApiDotNetCore.MongoDb
+ https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb
MIT
false
true
diff --git a/src/JsonApiDotNetCore.MongoDb/MongoEntityRepository.cs b/src/JsonApiDotNetCore.MongoDb/MongoEntityRepository.cs
index 2c23f5f..f49f2ad 100644
--- a/src/JsonApiDotNetCore.MongoDb/MongoEntityRepository.cs
+++ b/src/JsonApiDotNetCore.MongoDb/MongoEntityRepository.cs
@@ -1,17 +1,19 @@
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.MongoDb.Queries.Internal.QueryableBuilding;
+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 JsonApiDotNetCore.Resources.Annotations;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Infrastructure;
namespace JsonApiDotNetCore.MongoDb
{
@@ -21,74 +23,160 @@ public class MongoEntityRepository
{
private readonly IMongoDatabase _db;
private readonly ITargetedFields _targetedFields;
- private readonly IResourceGraph _resourceGraph;
+ private readonly IResourceContextProvider _resourceContextProvider;
private readonly IResourceFactory _resourceFactory;
- private readonly IEnumerable _constraintProviders;
-
+
public MongoEntityRepository(
IMongoDatabase db,
ITargetedFields targetedFields,
- IResourceGraph resourceGraph,
- IResourceFactory resourceFactory,
- IEnumerable constraintProviders)
+ IResourceContextProvider resourceContextProvider,
+ IResourceFactory resourceFactory)
{
_db = db;
_targetedFields = targetedFields;
- _resourceGraph = resourceGraph;
+ _resourceContextProvider = resourceContextProvider;
_resourceFactory = resourceFactory;
- _constraintProviders = constraintProviders;
}
private IMongoCollection Collection => _db.GetCollection(typeof(TResource).Name);
private IMongoQueryable Entities => Collection.AsQueryable();
- public virtual Task CountAsync(FilterExpression topFilter)
+ public virtual async Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) =>
+ await ApplyQueryLayer(layer).ToListAsync();
+
+ public virtual Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken)
{
- var resourceContext = _resourceGraph.GetResourceContext();
+ var resourceContext = _resourceContextProvider.GetResourceContext();
var layer = new QueryLayer(resourceContext)
{
Filter = topFilter
};
var query = ApplyQueryLayer(layer);
- return query.CountAsync();
+ return query.CountAsync(cancellationToken);
}
- public virtual Task CreateAsync(TResource resource) =>
- Collection.InsertOneAsync(resource);
-
- public virtual async Task DeleteAsync(TId id)
+ public virtual Task GetForCreateAsync(TId id, CancellationToken cancellationToken)
{
- var result = await Collection.DeleteOneAsync(Builders.Filter.Eq(e => e.Id, id));
- return result.IsAcknowledged && result.DeletedCount > 0;
+ var resource = _resourceFactory.CreateInstance();
+ resource.Id = id;
+
+ return Task.FromResult(resource);
}
- public virtual void FlushFromCache(TResource resource) =>
- throw new NotImplementedException();
+ public virtual Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase,
+ CancellationToken cancellationToken)
+ {
+ if (resourceFromRequest == null) throw new ArgumentNullException(nameof(resourceFromRequest));
+ if (resourceForDatabase == null) throw new ArgumentNullException(nameof(resourceForDatabase));
+
+ foreach (var attribute in _targetedFields.Attributes)
+ {
+ attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest));
+ }
- public virtual async Task> GetAsync(QueryLayer layer) =>
- await ApplyQueryLayer(layer).ToListAsync();
+ return Collection.InsertOneAsync(resourceForDatabase, new InsertOneOptions(), cancellationToken);
+ }
- public virtual async Task UpdateAsync(TResource requestResource, TResource databaseResource)
+ public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken)
+ {
+ var resources = await GetAsync(queryLayer, cancellationToken);
+ return resources.FirstOrDefault();
+ }
+
+ public virtual async Task UpdateAsync(TResource requestResource, TResource databaseResource, CancellationToken cancellationToken)
{
foreach (var attr in _targetedFields.Attributes)
attr.SetValue(databaseResource, attr.GetValue(requestResource));
- await Collection.ReplaceOneAsync(Builders.Filter.Eq(e => e.Id, databaseResource.Id), databaseResource);
+ await Collection.ReplaceOneAsync(
+ Builders.Filter.Eq(e => e.Id, databaseResource.Id),
+ databaseResource,
+ new ReplaceOptions(),
+ cancellationToken);
+ }
+
+ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken)
+ {
+ var result = await Collection.DeleteOneAsync(
+ Builders.Filter.Eq(e => e.Id, id),
+ new DeleteOptions(),
+ cancellationToken);
+
+ if (!result.IsAcknowledged || result.DeletedCount == 0)
+ {
+ throw new DataStoreUpdateException(new Exception());
+ }
+ }
+
+ public virtual Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
}
- public virtual Task UpdateRelationshipAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection relationshipIds) =>
+ public virtual Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds,
+ CancellationToken cancellationToken)
+ {
throw new NotImplementedException();
+ }
protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer layer)
{
var source = Entities;
- var nameFactory = new JsonApiDotNetCore.Queries.Internal.QueryableBuilding.LambdaParameterNameFactory();
- var builder = new QueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, _resourceGraph);
+ var nameFactory = new LambdaParameterNameFactory();
+ var builder = new QueryableBuilder(
+ source.Expression,
+ source.ElementType,
+ typeof(Queryable),
+ nameFactory,
+ _resourceFactory,
+ _resourceContextProvider,
+ DummyModel.Instance);
var expression = builder.ApplyQuery(layer);
return (IMongoQueryable)source.Provider.CreateQuery(expression);
}
}
+
+ internal sealed class DummyModel : IModel
+ {
+ public static IModel Instance { get; } = new DummyModel();
+
+ private DummyModel()
+ {
+ }
+
+ public IAnnotation FindAnnotation(string name)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEnumerable GetAnnotations()
+ {
+ throw new NotImplementedException();
+ }
+
+ public object this[string name] => throw new NotImplementedException();
+
+ public IEnumerable GetEntityTypes()
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEntityType FindEntityType(string name)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEntityType FindEntityType(string name, string definingNavigationName, IEntityType definingEntityType)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/QueryableBuilder.cs b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/QueryableBuilder.cs
deleted file mode 100644
index 0b70588..0000000
--- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/QueryableBuilder.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using System;
-using System.Linq.Expressions;
-using JsonApiDotNetCore.Configuration;
-using JsonApiDotNetCore.Queries;
-using JsonApiDotNetCore.Queries.Expressions;
-using JsonApiDotNetCore.Queries.Internal.QueryableBuilding;
-using JsonApiDotNetCore.Resources;
-
-namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding
-{
- ///
- /// Drives conversion from into system trees.
- ///
- public sealed class QueryableBuilder
- {
- private readonly Expression _source;
- private readonly Type _elementType;
- private readonly Type _extensionType;
- private readonly LambdaParameterNameFactory _nameFactory;
- private readonly IResourceFactory _resourceFactory;
- private readonly IResourceContextProvider _resourceContextProvider;
- private readonly LambdaScopeFactory _lambdaScopeFactory;
-
- public QueryableBuilder(Expression source, Type elementType, Type extensionType, LambdaParameterNameFactory nameFactory,
- IResourceFactory resourceFactory, IResourceContextProvider resourceContextProvider,
- LambdaScopeFactory lambdaScopeFactory = null)
- {
- _source = source ?? throw new ArgumentNullException(nameof(source));
- _elementType = elementType ?? throw new ArgumentNullException(nameof(elementType));
- _extensionType = extensionType ?? throw new ArgumentNullException(nameof(extensionType));
- _nameFactory = nameFactory ?? throw new ArgumentNullException(nameof(nameFactory));
- _resourceFactory = resourceFactory ?? throw new ArgumentNullException(nameof(resourceFactory));
- _resourceContextProvider = resourceContextProvider ?? throw new ArgumentNullException(nameof(resourceContextProvider));
- _lambdaScopeFactory = lambdaScopeFactory ?? new LambdaScopeFactory(_nameFactory);
- }
-
- public Expression ApplyQuery(QueryLayer layer)
- {
- var expression = _source;
-
- if (layer.Filter != null)
- {
- expression = ApplyFilter(expression, layer.Filter);
- }
-
- if (layer.Sort != null)
- {
- expression = ApplySort(expression, layer.Sort);
- }
-
- if (layer.Pagination != null)
- {
- expression = ApplyPagination(expression, layer.Pagination);
- }
-
- return expression;
- }
-
- private Expression ApplyFilter(Expression source, FilterExpression filter)
- {
- using var lambdaScope = _lambdaScopeFactory.CreateScope(_elementType);
-
- var builder = new WhereClauseBuilder(source, lambdaScope, _extensionType);
- return builder.ApplyWhere(filter);
- }
-
- private Expression ApplySort(Expression source, SortExpression sort)
- {
- using var lambdaScope = _lambdaScopeFactory.CreateScope(_elementType);
-
- var builder = new OrderClauseBuilder(source, lambdaScope, _extensionType);
- return builder.ApplyOrderBy(sort);
- }
-
- private Expression ApplyPagination(Expression source, PaginationExpression pagination)
- {
- using var lambdaScope = _lambdaScopeFactory.CreateScope(_elementType);
-
- var builder = new SkipTakeClauseBuilder(source, lambdaScope, _extensionType);
- return builder.ApplySkipTake(pagination);
- }
- }
-}
diff --git a/test/JsonApiDotNetCore.MongoDb.Example.Tests/Factories/IntegrationTestWebApplicationFactory.cs b/test/JsonApiDotNetCore.MongoDb.Example.Tests/Factories/IntegrationTestWebApplicationFactory.cs
deleted file mode 100644
index b0f4109..0000000
--- a/test/JsonApiDotNetCore.MongoDb.Example.Tests/Factories/IntegrationTestWebApplicationFactory.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.AspNetCore.TestHost;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-
-namespace JsonApiDotNetCore.MongoDb.Example.Tests.Factories
-{
- internal sealed class IntegrationTestWebApplicationFactory : WebApplicationFactory
- where TStartup : class
- {
- private Action _beforeServicesConfiguration;
- private Action _afterServicesConfiguration;
-
- public void ConfigureServicesBeforeStartup(Action servicesConfiguration)
- {
- _beforeServicesConfiguration = servicesConfiguration;
- }
-
- public void ConfigureServicesAfterStartup(Action servicesConfiguration)
- {
- _afterServicesConfiguration = servicesConfiguration;
- }
-
- protected override IHostBuilder CreateHostBuilder()
- {
- return Host.CreateDefaultBuilder(null)
- .ConfigureWebHostDefaults(webBuilder =>
- {
- webBuilder.ConfigureTestServices(services =>
- {
- _beforeServicesConfiguration?.Invoke(services);
- });
-
- webBuilder.UseStartup();
-
- webBuilder.ConfigureTestServices(services =>
- {
- _afterServicesConfiguration?.Invoke(services);
- });
- });
- }
- }
-}
diff --git a/test/JsonApiDotNetCore.MongoDb.Example.Tests/Helpers/Extensions/StringExtensions.cs b/test/JsonApiDotNetCore.MongoDb.Example.Tests/Helpers/Extensions/StringExtensions.cs
new file mode 100644
index 0000000..6ff22de
--- /dev/null
+++ b/test/JsonApiDotNetCore.MongoDb.Example.Tests/Helpers/Extensions/StringExtensions.cs
@@ -0,0 +1,10 @@
+namespace JsonApiDotNetCore.MongoDb.Example.Tests.Helpers.Extensions
+{
+ public static class StringExtensions
+ {
+ public static string NormalizeLineEndings(this string text)
+ {
+ return text.Replace("\r\n", "\n").Replace("\r", "\n");
+ }
+ }
+}
diff --git a/test/JsonApiDotNetCore.MongoDb.Example.Tests/Helpers/Models/TodoItemClient.cs b/test/JsonApiDotNetCore.MongoDb.Example.Tests/Helpers/Models/TodoItemClient.cs
new file mode 100644
index 0000000..b7e5123
--- /dev/null
+++ b/test/JsonApiDotNetCore.MongoDb.Example.Tests/Helpers/Models/TodoItemClient.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using JsonApiDotNetCore.MongoDb.Example.Models;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Tests.Helpers.Models
+{
+ ///
+ /// this "client" version of the is required because the
+ /// base property that is overridden here does not have a setter. For a model
+ /// defined on a JSON:API client, it would not make sense to have an exposed attribute
+ /// without a setter.
+ ///
+ public class TodoItemClient : TodoItem
+ {
+ [Attr]
+ public new string CalculatedValue { get; set; }
+ }
+
+ [Resource("todoCollections")]
+ public sealed class TodoItemCollectionClient : Identifiable
+ {
+ [Attr]
+ public string Name { get; set; }
+ public int OwnerId { get; set; }
+
+ [HasMany]
+ public ISet TodoItems { get; set; }
+
+ [HasOne]
+ public Person Owner { get; set; }
+ }
+}
diff --git a/test/JsonApiDotNetCore.MongoDb.Example.Tests/HttpResponseMessageExtensions.cs b/test/JsonApiDotNetCore.MongoDb.Example.Tests/HttpResponseMessageExtensions.cs
new file mode 100644
index 0000000..14aa16d
--- /dev/null
+++ b/test/JsonApiDotNetCore.MongoDb.Example.Tests/HttpResponseMessageExtensions.cs
@@ -0,0 +1,59 @@
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using FluentAssertions;
+using FluentAssertions.Primitives;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace JsonApiDotNetCore.MongoDb.Example.Tests
+{
+ public static class HttpResponseMessageExtensions
+ {
+ public static HttpResponseMessageAssertions Should(this HttpResponseMessage instance)
+ {
+ return new HttpResponseMessageAssertions(instance);
+ }
+
+ public sealed class HttpResponseMessageAssertions
+ : ReferenceTypeAssertions
+ {
+ protected override string Identifier => "response";
+
+ public HttpResponseMessageAssertions(HttpResponseMessage instance)
+ {
+ Subject = instance;
+ }
+
+ public AndConstraint HaveStatusCode(HttpStatusCode statusCode)
+ {
+ if (Subject.StatusCode != statusCode)
+ {
+ string responseText = GetFormattedContentAsync(Subject).Result;
+ Subject.StatusCode.Should().Be(statusCode, "response body returned was:\n" + responseText);
+ }
+
+ return new AndConstraint(this);
+ }
+
+ private static async Task GetFormattedContentAsync(HttpResponseMessage responseMessage)
+ {
+ string text = await responseMessage.Content.ReadAsStringAsync();
+
+ try
+ {
+ if (text.Length > 0)
+ {
+ return JsonConvert.DeserializeObject(text).ToString();
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+
+ return text;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/JsonApiDotNetCore.MongoDb.Example.Tests/IntegrationTestContext.cs b/test/JsonApiDotNetCore.MongoDb.Example.Tests/IntegrationTestContext.cs
index 2e3aafd..3841a4c 100644
--- a/test/JsonApiDotNetCore.MongoDb.Example.Tests/IntegrationTestContext.cs
+++ b/test/JsonApiDotNetCore.MongoDb.Example.Tests/IntegrationTestContext.cs
@@ -2,46 +2,71 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
-using Example;
+using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
-using JsonApiDotNetCore.MongoDb.Example.Tests.Factories;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Mongo2Go;
using MongoDB.Driver;
using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
namespace JsonApiDotNetCore.MongoDb.Example.Tests
{
public sealed class IntegrationTestContext : IDisposable
where TStartup : class
{
- private readonly Lazy> _lazyFactory;
+ private readonly Lazy> _lazyFactory;
private Action _beforeServicesConfiguration;
private Action _afterServicesConfiguration;
+ private Action _registerResources;
+ private readonly MongoDbRunner _runner;
- private WebApplicationFactory Factory => _lazyFactory.Value;
+ public WebApplicationFactory Factory => _lazyFactory.Value;
public IntegrationTestContext()
{
- _lazyFactory = new Lazy>(CreateFactory);
+ _lazyFactory = new Lazy>(CreateFactory);
+ _runner = MongoDbRunner.Start();
}
- private WebApplicationFactory CreateFactory()
+ private WebApplicationFactory CreateFactory()
{
- var factory = new IntegrationTestWebApplicationFactory();
+ var factory = new IntegrationTestWebApplicationFactory();
- factory.ConfigureServicesBeforeStartup(_beforeServicesConfiguration);
-
- factory.ConfigureServicesAfterStartup(services =>
+ factory.ConfigureServicesBeforeStartup(services =>
{
- _afterServicesConfiguration?.Invoke(services);
+ _beforeServicesConfiguration?.Invoke(services);
+
+ services.AddSingleton(sp =>
+ {
+ var client = new MongoClient(_runner.ConnectionString);
+ return client.GetDatabase($"JsonApiDotNetCore_MongoDb_{new Random().Next()}_Test");
+ });
+
+ services.AddJsonApi(
+ options =>
+ {
+ options.IncludeExceptionStackTraceInErrors = true;
+ options.SerializerSettings.Formatting = Formatting.Indented;
+ options.SerializerSettings.Converters.Add(new StringEnumConverter());
+ }, resources: _registerResources);
});
+ factory.ConfigureServicesAfterStartup(_afterServicesConfiguration);
+
return factory;
}
- public void Dispose() => Factory.Dispose();
+ public void Dispose()
+ {
+ _runner.Dispose();
+ Factory.Dispose();
+ }
public void ConfigureServicesBeforeStartup(Action servicesConfiguration) =>
_beforeServicesConfiguration = servicesConfiguration;
@@ -49,6 +74,9 @@ public void ConfigureServicesBeforeStartup(Action servicesCo
public void ConfigureServicesAfterStartup(Action servicesConfiguration) =>
_afterServicesConfiguration = servicesConfiguration;
+ public void RegisterResources(Action resources) =>
+ _registerResources = resources;
+
public async Task RunOnDatabaseAsync(Func asyncAction)
{
using var scope = Factory.Services.CreateScope();
@@ -110,5 +138,40 @@ private TResponseDocument DeserializeResponse(string response
throw new FormatException($"Failed to deserialize response body to JSON:\n{responseText}", exception);
}
}
+
+ private sealed class IntegrationTestWebApplicationFactory : WebApplicationFactory
+ {
+ private Action _beforeServicesConfiguration;
+ private Action _afterServicesConfiguration;
+
+ public void ConfigureServicesBeforeStartup(Action servicesConfiguration)
+ {
+ _beforeServicesConfiguration = servicesConfiguration;
+ }
+
+ public void ConfigureServicesAfterStartup(Action servicesConfiguration)
+ {
+ _afterServicesConfiguration = servicesConfiguration;
+ }
+
+ protected override IHostBuilder CreateHostBuilder()
+ {
+ return Host.CreateDefaultBuilder(null)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.ConfigureTestServices(services =>
+ {
+ _beforeServicesConfiguration?.Invoke(services);
+ });
+
+ webBuilder.UseStartup();
+
+ webBuilder.ConfigureTestServices(services =>
+ {
+ _afterServicesConfiguration?.Invoke(services);
+ });
+ });
+ }
+ }
}
}
diff --git a/test/JsonApiDotNetCore.MongoDb.Example.Tests/IntegrationTests/CreatingResourcesTests.cs b/test/JsonApiDotNetCore.MongoDb.Example.Tests/IntegrationTests/CreatingResourcesTests.cs
deleted file mode 100644
index 6326cf1..0000000
--- a/test/JsonApiDotNetCore.MongoDb.Example.Tests/IntegrationTests/CreatingResourcesTests.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System.Net;
-using System.Threading.Tasks;
-using Bogus;
-using Example;
-using Example.Models;
-using JsonApiDotNetCore.Serialization.Objects;
-using Microsoft.Extensions.DependencyInjection;
-using MongoDB.Driver;
-using Xunit;
-
-namespace JsonApiDotNetCore.MongoDb.Example.Tests.IntegrationTests
-{
- public sealed class CreatingResourcesTests : IClassFixture>, IAsyncLifetime
- {
- private readonly IntegrationTestContext _testContext;
- private readonly Faker _bookFaker;
-
- private string _createdBookId;
-
- public CreatingResourcesTests(IntegrationTestContext testContext)
- {
- _testContext = testContext;
- _bookFaker = new Faker