diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000..134066baff
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+charset = utf-8
+
+[*.{csproj,props}]
+indent_size = 2
diff --git a/.gitignore b/.gitignore
index cd9f35a530..6630942ad5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,6 @@
*.user
.couscous/
docs/Template-Dark/
-.idea/
\ No newline at end of file
+.idea/
+**/_site/
+log.txt
diff --git a/.travis.yml b/.travis.yml
index 234e836e7e..9c5e937589 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,12 @@
language: csharp
dist: trusty
sudo: required
-services:
+services:
- postgresql
before_script:
- psql -c 'create database JsonApiDotNetCoreExample;' -U postgres
mono: none
-dotnet: 1.0.4 # https://www.microsoft.com/net/download/linux
+dotnet: 2.1.300 # https://www.microsoft.com/net/download/linux
branches:
only:
- master
diff --git a/.vscode/launch.json b/.vscode/launch.json
index b26b008078..1da452f8e1 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,6 +1,16 @@
{
"version": "0.2.0",
"configurations": [
+ {
+ "name": "OperationsExample",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ "program": "${workspaceRoot}/src/Examples/OperationsExample/bin/Debug/netcoreapp1.0/OperationsExample.dll",
+ "args": [],
+ "cwd": "${workspaceRoot}/src/Examples/OperationsExample",
+ "stopAtEntry": false
+ },
{
"name": ".NET Core Attach",
"type": "coreclr",
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 09ab6390d4..073a93abae 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -17,4 +17,4 @@
"problemMatcher": "$msCompile"
}
]
-}
\ No newline at end of file
+}
diff --git a/Build.ps1 b/Build.ps1
index 6a37b7f8a5..8ff62d6578 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -9,28 +9,55 @@ function Get-Version-Suffix-From-Tag
return $final
}
+function CheckLastExitCode {
+ param ([int[]]$SuccessCodes = @(0), [scriptblock]$CleanupScript=$null)
+
+ if ($SuccessCodes -notcontains $LastExitCode) {
+ $msg = "EXE RETURNED EXIT CODE $LastExitCode"
+ throw $msg
+ }
+}
+
$revision = @{ $true = $env:APPVEYOR_BUILD_NUMBER; $false = 1 }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
$revision = "{0:D4}" -f [convert]::ToInt32($revision, 10)
dotnet restore
dotnet test ./test/UnitTests/UnitTests.csproj
+CheckLastExitCode
+
dotnet test ./test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj
+CheckLastExitCode
+
dotnet test ./test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj
+CheckLastExitCode
-dotnet build .\src\JsonApiDotNetCore -c Release
+dotnet test ./test/OperationsExampleTests/OperationsExampleTests.csproj
+CheckLastExitCode
-echo "APPVEYOR_REPO_TAG: $env:APPVEYOR_REPO_TAG"
+dotnet build .\src\JsonApiDotNetCore -c Release
+CheckLastExitCode
+Write-Output "APPVEYOR_REPO_TAG: $env:APPVEYOR_REPO_TAG"
If($env:APPVEYOR_REPO_TAG -eq $true) {
$revision = Get-Version-Suffix-From-Tag
- echo "VERSION-SUFFIX: $revision"
- echo "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$revision"
- dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$revision
+ Write-Output "VERSION-SUFFIX: $revision"
+
+ IF ([string]::IsNullOrWhitespace($revision)){
+ Write-Output "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts"
+ dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --include-symbols
+ CheckLastExitCode
+ }
+ Else {
+ Write-Output "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$revision"
+ dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$revision --include-symbols
+ CheckLastExitCode
+ }
}
Else {
- echo "VERSION-SUFFIX: alpha1-$revision"
- echo "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision"
- dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision
-}
\ No newline at end of file
+ Write-Output "VERSION-SUFFIX: alpha1-$revision"
+ Write-Output "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision"
+ dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision --include-symbols
+ CheckLastExitCode
+}
diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD
index 0e14787fec..8ce50e6c68 100644
--- a/CONTRIBUTING.MD
+++ b/CONTRIBUTING.MD
@@ -2,10 +2,14 @@
## Workflow
-1. Search through the issues to see if your particular issue has already been discovered and possibly addressed
-2. Open an issue if you can't find anything helpful
-3. Open a PR for proposed changes
+1. Search through the issues to see if your particular issue has already been discovered and possibly addressed
+2. Open an issue if you can't find anything helpful
+3. Open a PR for proposed changes
## Commit Guidelines
I have chosen to loosely follow the [Angular Commit Guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit)
+
+# Documentation
+
+If you'd like to help us improve our documentation, please checkout our [GitHub pages repository](https://github.com/json-api-dotnet/json-api-dotnet.github.io) where we host our documentation.
diff --git a/Directory.Build.props b/Directory.Build.props
index d3e19546db..0d034e0c5d 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,26 +1,30 @@
-
+
+ netcoreapp2.0
netstandard2.0
- 2.0.1
- 2.0.1
- 2.0.0
- 2.0.0
- 4.4.0
+
+ 2.1.0
+
+ 2.1.0
+ 2.1.0
+ 2.1.0
+
+ 2.1.0
+ 2.1.0
+
+ 4.0.0
+ 2.1.0
+
+ 4.5.0
-
-
+
+
- netcoreapp2.0
- 2.0.0
- 2.0.0
- 3.2.6
- 2.0.1
- 15.3.0-preview-20170427-09
- 1.1.2
- 2.3.0-beta3-build3705
- 15.0.3
- 4.7.99
+ 15.7.2
+ 2.3.1
+ 22.1.2
+ 4.8.3
-
-
\ No newline at end of file
+
+
diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln
index a144223671..4b7cf5ea47 100644
--- a/JsonApiDotnetCore.sln
+++ b/JsonApiDotnetCore.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26730.10
+VisualStudioVersion = 15.0.27130.2010
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore", "src\JsonApiDotNetCore\JsonApiDotNetCore.csproj", "{C0EC9E70-EB2E-436F-9D94-FA16FA774123}"
EndProject
@@ -15,6 +15,11 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C5B4D998-CECB-454D-9F32-085A897577BE}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
+ .travis.yml = .travis.yml
+ appveyor.yml = appveyor.yml
+ Build.ps1 = Build.ps1
+ build.sh = build.sh
+ Directory.Build.props = Directory.Build.props
README.md = README.md
EndProjectSection
EndProject
@@ -28,6 +33,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{02
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReportsExample", "src\Examples\ReportsExample\ReportsExample.csproj", "{FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{076E1AE4-FD25-4684-B826-CAAE37FEA0AA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{1F604666-BB0F-413E-922D-9D37C6073285}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExample", "src\Examples\OperationsExample\OperationsExample.csproj", "{CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExampleTests", "test\OperationsExampleTests\OperationsExampleTests.csproj", "{9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -110,19 +123,58 @@ Global
{FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x64.Build.0 = Release|Any CPU
{FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.ActiveCfg = Release|Any CPU
{FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.Build.0 = Release|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x64.Build.0 = Debug|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x86.Build.0 = Debug|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x64.ActiveCfg = Release|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x64.Build.0 = Release|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x86.ActiveCfg = Release|Any CPU
+ {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x86.Build.0 = Release|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x64.Build.0 = Debug|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x86.Build.0 = Debug|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x64.ActiveCfg = Release|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x64.Build.0 = Release|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x86.ActiveCfg = Release|Any CPU
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x86.Build.0 = Release|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x64.Build.0 = Debug|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x86.Build.0 = Debug|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x64.ActiveCfg = Release|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x64.Build.0 = Release|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x86.ActiveCfg = Release|Any CPU
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C0EC9E70-EB2E-436F-9D94-FA16FA774123} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
- {97EE048B-16C0-43F6-BDA9-4E762B2F579F} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
+ {97EE048B-16C0-43F6-BDA9-4E762B2F579F} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
{0B959765-40D2-43B5-87EE-FE2FEF9DBED5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
- {570165EC-62B5-4684-A139-8D2A30DD4475} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
+ {570165EC-62B5-4684-A139-8D2A30DD4475} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
{73DA578D-A63F-4956-83ED-6D7102E09140} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
{6D4BD85A-A262-44C6-8572-FE3A30410BF3} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
{026FBC6C-AF76-4568-9B87-EC73457899FD} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
{FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
+ {1F604666-BB0F-413E-922D-9D37C6073285} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA}
+ {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
+ {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4}
diff --git a/README.md b/README.md
index c0a02f3d35..81ebba79bf 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,77 @@
+
+
+
+
# JSON API .Net Core
-[](https://ci.appveyor.com/project/jaredcnance/json-api-dotnet-core)
-[](https://travis-ci.org/Research-Institute/json-api-dotnet-core)
+[](https://ci.appveyor.com/project/jaredcnance/jsonapidotnetcore)
+[](https://travis-ci.org/json-api-dotnet/JsonApiDotNetCore)
[](https://www.nuget.org/packages/JsonApiDotNetCore/)
-[](https://www.myget.org/feed/research-institute/package/nuget/JsonApiDotNetCore)
[](https://gitter.im/json-api-dotnet-core/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](http://www.firsttimersonly.com/)
A framework for building [json:api](http://jsonapi.org/) compliant web APIs. The ultimate goal of this library is to eliminate as much boilerplate as possible by offering out-of-the-box features such as sorting, filtering and pagination. You just need to focus on defining the resources and implementing your custom business logic. This library has been designed around dependency injection making extensibility incredibly easy.
+## Getting Started
+
+These are some steps you can take to help you understand what this project is and how you can use it:
+
+- [What is json:api and why should I use it?](https://nordicapis.com/the-benefits-of-using-json-api/)
+- [The json:api specification](http://jsonapi.org/format/)
+- [Demo [Video]](https://youtu.be/KAMuo6K7VcE)
+- [Our documentation](https://json-api-dotnet.github.io)
+- [Check out the example projects](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/src/Examples)
+
+## Related Projects
+
+- [Performance Reports](https://github.com/json-api-dotnet/PerformanceReports)
+- [JsonApiDotNetCore.MongoDb](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb)
+- [JsonApiDotNetCore.Marten](https://github.com/wayne-o/JsonApiDotNetCore.Marten)
+- [Todo List App](https://github.com/json-api-dotnet/TodoListExample)
+
+## Examples
+
+See the [examples](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/src/Examples) directory for up-to-date sample applications. There is also a [Todo List App](https://github.com/json-api-dotnet/TodoListExample) that includes a JADNC API and an EmberJs client.
+
## Installation And Usage
-See the documentation [here](https://research-institute.github.io/json-api-dotnet-core)
+See [the documentation](https://json-api-dotnet.github.io/#/) for detailed usage.
+
+### Models
+
+```csharp
+public class Article : Identifiable
+{
+ [Attr("name")]
+ public string Name { get; set; }
+}
+```
+
+### Controllers
+```csharp
+public class ArticlesController : JsonApiController
+{
+ public ArticlesController(
+ IJsonApiContext jsonApiContext,
+ IResourceService resourceService)
+ : base(jsonApiContext, resourceService) { }
+}
+```
-## .Net Core v2 Notes
+### Middleware
-Branch `feat/core-2` is where I am working on .Net Core 2 compatibility tests and package upgrades.
-There are several blockers to be aware of:
+```csharp
+public class Startup
+{
+ public IServiceProvider ConfigureServices(IServiceCollection services) {
+ services.AddJsonApi();
+ // ...
+ }
-- Microsoft.AspNetCore.* packages target the runtime (netcoreapp) instead of netstandard. [This will be changed in future versions.](https://blogs.msdn.microsoft.com/webdev/2017/05/10/aspnet-2-preview-1/).
-- Can't run acceptance testing against postgres on preview runtime [pgsql.EntityFrameworkCore.PostgreSQL#171](https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/171#issuecomment-301287257)
+ public void Configure(IApplicationBuilder app) {
+ app.UseJsonApi()
+ // ...
+ }
+}
+```
diff --git a/appveyor.yml b/appveyor.yml
index ec135d19bb..78f4d3187e 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -44,6 +44,7 @@ deploy:
api_key:
secure: 6CeYcZ4Ze+57gxfeuHzqP6ldbUkPtF6pfpVM1Gw/K2jExFrAz763gNAQ++tiacq3
skip_symbols: false
+ symbol_server: https://www.myget.org/F/research-institute/symbols/api/v2/package
on:
branch: develop
- provider: NuGet
diff --git a/benchmarks/.gitignore b/benchmarks/.gitignore
new file mode 100644
index 0000000000..5a3c72cbbb
--- /dev/null
+++ b/benchmarks/.gitignore
@@ -0,0 +1,239 @@
+_data/
+*-report-default.md
+*-report.csv
+*-report.html
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# 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
+# TODO: 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
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Microsoft Azure ApplicationInsights config file
+ApplicationInsights.config
+
+# Windows Store app package directory
+AppPackages/
+BundleArtifacts/
+
+# 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
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# 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
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# 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
+
+# FAKE - F# Make
+.fake/
diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.JsonApiContext.PathIsRelationship_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.JsonApiContext.PathIsRelationship_Benchmarks-report-github.md
new file mode 100644
index 0000000000..6be58e241a
--- /dev/null
+++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.JsonApiContext.PathIsRelationship_Benchmarks-report-github.md
@@ -0,0 +1,12 @@
+```ini
+BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
+Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
+.NET Core SDK=2.1.4
+ [Host] : .NET Core 2.0.5 (Framework 4.6.0.0), 64bit RyuJIT
+ DefaultJob : .NET Core 2.0.5 (Framework 4.6.0.0), 64bit RyuJIT
+```
+
+| Method | Mean | Error | StdDev | Gen 0 | Allocated |
+| ---------- | --------: | ---------: | ---------: | -----: | --------: |
+| UsingSplit | 421.08 ns | 19.3905 ns | 54.0529 ns | 0.4725 | 744 B |
+| Current | 52.23 ns | 0.8052 ns | 0.7532 ns | - | 0 B |
diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.LinkBuilder.LinkBuilder_GetNamespaceFromPath_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.LinkBuilder.LinkBuilder_GetNamespaceFromPath_Benchmarks-report-github.md
new file mode 100644
index 0000000000..72951396e8
--- /dev/null
+++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.LinkBuilder.LinkBuilder_GetNamespaceFromPath_Benchmarks-report-github.md
@@ -0,0 +1,16 @@
+``` ini
+
+BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
+Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
+.NET Core SDK=2.1.4
+ [Host] : .NET Core 2.0.5 (Framework 4.6.0.0), 64bit RyuJIT
+ Job-XFMVNE : .NET Core 2.0.5 (Framework 4.6.0.0), 64bit RyuJIT
+
+LaunchCount=3 TargetCount=20 WarmupCount=10
+
+```
+| Method | Mean | Error | StdDev | Gen 0 | Allocated |
+|--------------------------- |-----------:|----------:|----------:|-------:|----------:|
+| UsingSplit | 1,197.6 ns | 11.929 ns | 25.933 ns | 0.9251 | 1456 B |
+| UsingSpanWithStringBuilder | 1,542.0 ns | 15.249 ns | 33.792 ns | 0.9460 | 1488 B |
+| UsingSpanWithNoAlloc | 272.6 ns | 2.265 ns | 5.018 ns | 0.0863 | 136 B |
diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md
new file mode 100755
index 0000000000..b3ebd8a29c
--- /dev/null
+++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Query.QueryParser_Benchmarks-report-github.md
@@ -0,0 +1,16 @@
+``` ini
+
+BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
+Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
+.NET Core SDK=2.0.0
+ [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+ Job-WKDOLS : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+
+LaunchCount=3 TargetCount=20 WarmupCount=10
+
+```
+| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Allocated |
+|--------------- |-------------:|-----------:|-----------:|---------:|--------:|----------:|
+| AscendingSort | 4.316 us | 1.3773 us | 3.0232 us | 0.5066 | 0.1303 | 1.08 KB |
+| DescendingSort | 3.300 us | 0.0314 us | 0.0682 us | 0.5123 | 0.1318 | 1.13 KB |
+| ComplexQuery | 2,041.642 us | 41.5631 us | 92.1010 us | 312.5000 | 80.2734 | 648.99 KB |
diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.RequestMiddleware.ContainsMediaTypeParameters_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.RequestMiddleware.ContainsMediaTypeParameters_Benchmarks-report-github.md
new file mode 100644
index 0000000000..066e7b2036
--- /dev/null
+++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.RequestMiddleware.ContainsMediaTypeParameters_Benchmarks-report-github.md
@@ -0,0 +1,14 @@
+``` ini
+
+BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
+Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
+.NET Core SDK=2.1.4
+ [Host] : .NET Core 2.0.5 (Framework 4.6.0.0), 64bit RyuJIT
+ DefaultJob : .NET Core 2.0.5 (Framework 4.6.0.0), 64bit RyuJIT
+
+
+```
+| Method | Mean | Error | StdDev | Gen 0 | Allocated |
+|----------- |----------:|----------:|----------:|-------:|----------:|
+| UsingSplit | 157.28 ns | 2.9689 ns | 5.8602 ns | 0.2134 | 336 B |
+| Current | 39.96 ns | 0.6489 ns | 0.6070 ns | - | 0 B |
diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md
new file mode 100755
index 0000000000..6c8c3f2905
--- /dev/null
+++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiDeserializer_Benchmarks-report-github.md
@@ -0,0 +1,13 @@
+``` ini
+
+BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
+Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
+.NET Core SDK=2.0.0
+ [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+ DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+
+
+```
+| Method | Mean | Error | StdDev |
+|------------------------ |---------:|----------:|----------:|
+| DeserializeSimpleObject | 27.05 us | 0.5353 us | 0.5950 us |
diff --git a/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md
new file mode 100755
index 0000000000..f86bf0faa9
--- /dev/null
+++ b/benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.Serialization.JsonApiSerializer_Benchmarks-report-github.md
@@ -0,0 +1,13 @@
+``` ini
+
+BenchmarkDotNet=v0.10.10, OS=Mac OS X 10.12
+Processor=Intel Core i5-5257U CPU 2.70GHz (Broadwell), ProcessorCount=4
+.NET Core SDK=2.0.0
+ [Host] : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+ DefaultJob : .NET Core 1.1.4 (Framework 4.6.25714.03), 64bit RyuJIT
+
+
+```
+| Method | Mean | Error | StdDev |
+|---------------------- |---------:|----------:|----------:|
+| SerializeSimpleObject | 7.195 us | 0.1436 us | 0.1816 us |
diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj
new file mode 100644
index 0000000000..60a9a8bf42
--- /dev/null
+++ b/benchmarks/Benchmarks.csproj
@@ -0,0 +1,15 @@
+
+
+ Exe
+ $(NetCoreAppVersion)
+ Benchmarks
+
+
+
+
+
+
+
+
+
+
diff --git a/benchmarks/JsonApiContext/PathIsRelationship_Benchmarks.cs b/benchmarks/JsonApiContext/PathIsRelationship_Benchmarks.cs
new file mode 100644
index 0000000000..83fe6fc53c
--- /dev/null
+++ b/benchmarks/JsonApiContext/PathIsRelationship_Benchmarks.cs
@@ -0,0 +1,24 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes.Exporters;
+
+namespace Benchmarks.JsonApiContext
+{
+ [MarkdownExporter, MemoryDiagnoser]
+ public class PathIsRelationship_Benchmarks
+ {
+ private const string PATH = "https://example.com/api/v1/namespace/articles/relationships/author/";
+
+ [Benchmark]
+ public void Current()
+ => JsonApiDotNetCore.Services.JsonApiContext.PathIsRelationship(PATH);
+
+ [Benchmark]
+ public void UsingSplit() => UsingSplitImpl(PATH);
+
+ private bool UsingSplitImpl(string path)
+ {
+ var split = path.Split('/');
+ return split[split.Length - 2] == "relationships";
+ }
+ }
+}
diff --git a/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs b/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs
new file mode 100644
index 0000000000..05728321c3
--- /dev/null
+++ b/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs
@@ -0,0 +1,38 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes.Exporters;
+using BenchmarkDotNet.Attributes.Jobs;
+
+namespace Benchmarks.LinkBuilder
+{
+ [MarkdownExporter, SimpleJob(launchCount : 3, warmupCount : 10, targetCount : 20), MemoryDiagnoser]
+ public class LinkBuilder_GetNamespaceFromPath_Benchmarks
+ {
+ private const string PATH = "/api/some-really-long-namespace-path/resources/current/articles";
+ private const string ENTITY_NAME = "articles";
+
+ [Benchmark]
+ public void UsingSplit() => GetNamespaceFromPath_BySplitting(PATH, ENTITY_NAME);
+
+ [Benchmark]
+ public void Current() => GetNameSpaceFromPath_Current(PATH, ENTITY_NAME);
+
+ public static string GetNamespaceFromPath_BySplitting(string path, string entityName)
+ {
+ var nSpace = string.Empty;
+ var segments = path.Split('/');
+
+ for (var i = 1; i < segments.Length; i++)
+ {
+ if (segments[i].ToLower() == entityName)
+ break;
+
+ nSpace += $"/{segments[i]}";
+ }
+
+ return nSpace;
+ }
+
+ public static string GetNameSpaceFromPath_Current(string path, string entityName)
+ => JsonApiDotNetCore.Builders.LinkBuilder.GetNamespaceFromPath(path, entityName);
+ }
+}
diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs
new file mode 100644
index 0000000000..9a2c45dffb
--- /dev/null
+++ b/benchmarks/Program.cs
@@ -0,0 +1,22 @@
+using BenchmarkDotNet.Running;
+using Benchmarks.JsonApiContext;
+using Benchmarks.LinkBuilder;
+using Benchmarks.Query;
+using Benchmarks.RequestMiddleware;
+using Benchmarks.Serialization;
+
+namespace Benchmarks {
+ class Program {
+ static void Main(string[] args) {
+ var switcher = new BenchmarkSwitcher(new[] {
+ typeof(JsonApiDeserializer_Benchmarks),
+ typeof(JsonApiSerializer_Benchmarks),
+ typeof(QueryParser_Benchmarks),
+ typeof(LinkBuilder_GetNamespaceFromPath_Benchmarks),
+ typeof(ContainsMediaTypeParameters_Benchmarks),
+ typeof(PathIsRelationship_Benchmarks)
+ });
+ switcher.Run(args);
+ }
+ }
+}
diff --git a/benchmarks/Query/QueryParser_Benchmarks.cs b/benchmarks/Query/QueryParser_Benchmarks.cs
new file mode 100644
index 0000000000..de82baa60f
--- /dev/null
+++ b/benchmarks/Query/QueryParser_Benchmarks.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes.Exporters;
+using BenchmarkDotNet.Attributes.Jobs;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Internal;
+using JsonApiDotNetCore.Models;
+using JsonApiDotNetCore.Services;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.Primitives;
+using Moq;
+
+namespace Benchmarks.Query {
+ [MarkdownExporter, SimpleJob(launchCount : 3, warmupCount : 10, targetCount : 20), MemoryDiagnoser]
+ public class QueryParser_Benchmarks {
+ private readonly BenchmarkFacade _queryParser;
+
+ private const string ATTRIBUTE = "Attribute";
+ private const string ASCENDING_SORT = ATTRIBUTE;
+ private const string DESCENDING_SORT = "-" + ATTRIBUTE;
+
+ public QueryParser_Benchmarks() {
+ var controllerContextMock = new Mock();
+ controllerContextMock.Setup(m => m.RequestEntity).Returns(new ContextEntity {
+ Attributes = new List {
+ new AttrAttribute(ATTRIBUTE, ATTRIBUTE)
+ }
+ });
+ var options = new JsonApiOptions();
+ _queryParser = new BenchmarkFacade(controllerContextMock.Object, options);
+ }
+
+ [Benchmark]
+ public void AscendingSort() => _queryParser._ParseSortParameters(ASCENDING_SORT);
+
+ [Benchmark]
+ public void DescendingSort() => _queryParser._ParseSortParameters(DESCENDING_SORT);
+
+ [Benchmark]
+ public void ComplexQuery() => Run(100, () => _queryParser.Parse(
+ new QueryCollection(
+ new Dictionary {
+ { $"filter[{ATTRIBUTE}]", new StringValues(new [] { "abc", "eq:abc" }) },
+ { $"sort", $"-{ATTRIBUTE}" },
+ { $"include", "relationship" },
+ { $"page[size]", "1" },
+ { $"fields[resource]", ATTRIBUTE },
+ }
+ )
+ ));
+
+ private void Run(int iterations, Action action) {
+ for (int i = 0; i < iterations; i++)
+ action();
+ }
+
+ // this facade allows us to expose and micro-benchmark protected methods
+ private class BenchmarkFacade : QueryParser {
+ public BenchmarkFacade(
+ IControllerContext controllerContext,
+ JsonApiOptions options) : base(controllerContext, options) { }
+
+ public void _ParseSortParameters(string value) => base.ParseSortParameters(value);
+ }
+ }
+}
diff --git a/benchmarks/RequestMiddleware/ContainsMediaTypeParameters_Benchmarks.cs b/benchmarks/RequestMiddleware/ContainsMediaTypeParameters_Benchmarks.cs
new file mode 100644
index 0000000000..ed64c98335
--- /dev/null
+++ b/benchmarks/RequestMiddleware/ContainsMediaTypeParameters_Benchmarks.cs
@@ -0,0 +1,25 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes.Exporters;
+using JsonApiDotNetCore.Internal;
+
+namespace Benchmarks.RequestMiddleware
+{
+ [MarkdownExporter, MemoryDiagnoser]
+ public class ContainsMediaTypeParameters_Benchmarks
+ {
+ private const string MEDIA_TYPE = "application/vnd.api+json; version=1";
+
+ [Benchmark]
+ public void UsingSplit() => UsingSplitImpl(MEDIA_TYPE);
+
+ [Benchmark]
+ public void Current()
+ => JsonApiDotNetCore.Middleware.RequestMiddleware.ContainsMediaTypeParameters(MEDIA_TYPE);
+
+ private bool UsingSplitImpl(string mediaType)
+ {
+ var mediaTypeArr = mediaType.Split(';');
+ return (mediaTypeArr[0] == Constants.ContentType && mediaTypeArr.Length == 2);
+ }
+ }
+}
diff --git a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs
new file mode 100644
index 0000000000..c490bee362
--- /dev/null
+++ b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes.Exporters;
+using JsonApiDotNetCore.Builders;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Internal.Generics;
+using JsonApiDotNetCore.Models;
+using JsonApiDotNetCore.Serialization;
+using JsonApiDotNetCore.Services;
+using Moq;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Benchmarks.Serialization {
+ [MarkdownExporter]
+ public class JsonApiDeserializer_Benchmarks {
+ private const string TYPE_NAME = "simple-types";
+ private static readonly string Content = JsonConvert.SerializeObject(new Document {
+ Data = new DocumentData {
+ Type = TYPE_NAME,
+ Id = "1",
+ Attributes = new Dictionary {
+ {
+ "name",
+ Guid.NewGuid().ToString()
+ }
+ }
+ }
+ });
+
+ private readonly JsonApiDeSerializer _jsonApiDeSerializer;
+
+ public JsonApiDeserializer_Benchmarks() {
+ var contextGraphBuilder = new ContextGraphBuilder();
+ contextGraphBuilder.AddResource(TYPE_NAME);
+ var contextGraph = contextGraphBuilder.Build();
+
+ var jsonApiContextMock = new Mock();
+ jsonApiContextMock.SetupAllProperties();
+ jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph);
+ jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary());
+
+ var jsonApiOptions = new JsonApiOptions();
+ jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
+ jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions);
+
+ var genericProcessorFactoryMock = new Mock();
+
+ _jsonApiDeSerializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object);
+ }
+
+ [Benchmark]
+ public object DeserializeSimpleObject() => _jsonApiDeSerializer.Deserialize(Content);
+
+ private class SimpleType : Identifiable {
+ [Attr("name")]
+ public string Name { get; set; }
+ }
+ }
+}
diff --git a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs
new file mode 100644
index 0000000000..3d5ef7c001
--- /dev/null
+++ b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes.Exporters;
+using JsonApiDotNetCore.Builders;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Internal.Generics;
+using JsonApiDotNetCore.Models;
+using JsonApiDotNetCore.Serialization;
+using JsonApiDotNetCore.Services;
+using Moq;
+using Newtonsoft.Json.Serialization;
+
+namespace Benchmarks.Serialization {
+ [MarkdownExporter]
+ public class JsonApiSerializer_Benchmarks {
+ private const string TYPE_NAME = "simple-types";
+ private static readonly SimpleType Content = new SimpleType();
+
+ private readonly JsonApiSerializer _jsonApiSerializer;
+
+ public JsonApiSerializer_Benchmarks() {
+ var contextGraphBuilder = new ContextGraphBuilder();
+ contextGraphBuilder.AddResource(TYPE_NAME);
+ var contextGraph = contextGraphBuilder.Build();
+
+ var jsonApiContextMock = new Mock();
+ jsonApiContextMock.SetupAllProperties();
+ jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph);
+ jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary());
+
+ var jsonApiOptions = new JsonApiOptions();
+ jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
+ jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions);
+
+ var genericProcessorFactoryMock = new Mock();
+
+ var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object);
+ _jsonApiSerializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder);
+ }
+
+ [Benchmark]
+ public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content);
+
+ private class SimpleType : Identifiable {
+ [Attr("name")]
+ public string Name { get; set; }
+ }
+ }
+}
diff --git a/build.sh b/build.sh
index 441470e65d..50f2ab9c99 100755
--- a/build.sh
+++ b/build.sh
@@ -7,4 +7,5 @@ dotnet restore
dotnet test ./test/UnitTests/UnitTests.csproj
dotnet test ./test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj
-dotnet test ./test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj
\ No newline at end of file
+dotnet test ./test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj
+dotnet test ./test/OperationsExampleTests/OperationsExampleTests.csproj
diff --git a/couscous.yml b/couscous.yml
deleted file mode 100644
index 90f2b9b095..0000000000
--- a/couscous.yml
+++ /dev/null
@@ -1,110 +0,0 @@
-template:
- # Name of the directory containing the website template (default is "website")
- # directory: docs/Template-Dark
- # Or if you are using a remote template, you can set the Git URL
- url: https://github.com/jaredcnance/Template-Dark.git
- # Name of the index file (default is "README.md")
- index: index.md
-
-# List of directories to include in the processing (by default it's empty, so all markdown files are parsed)
-# Paths are relative to the optional source path given when generating the website, repository root by default
-include:
- - docs
-
-# List of directories to exclude from the processing (default contains "vendor" and "website")
-# Paths are relative to the optional include paths given when generating the website, repository root by default
-exclude:
- # This special entry will ask Couscous to read the exluded directories from your ".gitignore" file
- - %gitignore%
-
-# scripts:
- # Scripts to execute before generating the website
- # before:
- # - cp bin/couscous.phar website/
- # Scripts to execute after generating the website
- # after:
- # - rm website/couscous.phar
-
-# Set this variable to use a Custom Domain
-# The content of this variable will be directly inserted into the CNAME file
-# cname: docs.yourdomain.com
-
-# Set the target branch in which to deploy the generated website
-branch: gh-pages
-
-# Base URL of the published website (no "/" at the end!)
-# You are advised to set and use this variable to write your links in the HTML layouts
-baseUrl: https://research-institute.github.io/json-api-dotnet-core
-github:
- user: research-institute
- repo: json-api-dotnet-core
-
-title: JSONAPI .Net Core
-subTitle: killing boilerplate in dotnet web APIs
-
-# The left menu bar
-menu:
- sections:
- intro:
- name: Getting Started
- items:
- about:
- text: About
- relativeUrl: index.html
- installation:
- text: Installation
- relativeUrl: installation.html
- usage:
- text: Step by Step
- relativeUrl: usage.html
- usage:
- name: Usage
- items:
- models:
- text: Models
- relativeUrl: models.html
- contextgraph:
- text: Context Graph
- relativeUrl: contextgraph.html
- meta:
- text: Meta
- relativeUrl: meta.html
- options:
- text: Global Options
- relativeUrl: options.html
- errors:
- text: Custom Errors
- relativeUrl: errors.html
- filtering:
- text: Filtering
- relativeUrl: filtering.html
- pagination:
- text: Pagination
- relativeUrl: pagination.html
- routing:
- text: Routing
- relativeUrl: routing.html
- sorting:
- text: Sorting
- relativeUrl: sorting.html
- sparsefields:
- text: Sparse Fieldsets
- relativeUrl: sparsefieldsets.html
- extensibility:
- name: Extensibility
- items:
- layers:
- text: The Layers
- relativeUrl: layers.html
- controllers:
- text: Controllers
- relativeUrl: controllers.html
- services:
- text: Resource Services
- relativeUrl: resourceservices.html
- repositories:
- text: Entity Repositories
- relativeUrl: entityrepositories.html
- middleware:
- text: Middleware
- relativeUrl: middleware.html
\ No newline at end of file
diff --git a/docs/ContextGraph.md b/docs/ContextGraph.md
deleted file mode 100644
index f5b8394b83..0000000000
--- a/docs/ContextGraph.md
+++ /dev/null
@@ -1,58 +0,0 @@
----
-currentMenu: contextgraph
----
-
-# Context Graph
-
-The [ContextGraph](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Internal/ContextGraph.cs) is a map of all the json:api resources and their relationships that your API serves.
-It is built at app startup and available as a singleton through Dependency Injection.
-
-When you call `services.AddJsonApi()`, the graph is constructed from the context.
-
-### Defining Non-EF Resources
-
-If you have models that are not members of a `DbContext`,
-you can manually create this graph like so:
-
-```csharp
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- // Add framework services.
- var mvcBuilder = services.AddMvc();
-
- services.AddJsonApi(options => {
- options.Namespace = "api/v1";
- options.BuildContextGraph((builder) => {
- builder.AddResource("my-models");
- });
- }, mvcBuilder);
- // ...
-}
-```
-
-### Changing Resource Names
-
-If a DbContext is specified when adding the services, the context will be used to define the resources and their names. By default, these names will be hyphenated.
-
-```csharp
-public class AppDbContext : DbContext {
- // this will be translated into "my-models"
- public DbSet MyModels { get; set; }
-}
-```
-
-However, you can specify a custom name like so:
-
-```csharp
-public class AppDbContext : DbContext {
- // this will be translated into "someModels"
- [Resource("someModels")]
- public DbSet MyModels { get; set; }
-}
-```
-
-
-
-
-
diff --git a/docs/Controllers.md b/docs/Controllers.md
deleted file mode 100644
index 0ec47b8774..0000000000
--- a/docs/Controllers.md
+++ /dev/null
@@ -1,93 +0,0 @@
----
-currentMenu: controllers
----
-
-# Controllers
-
-You need to create controllers that inherit from [JsonApiController<TEntity>](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Controllers/JsonApiController.cs).
-
-```csharp
-public class ThingsController : JsonApiController
-{
- public ThingsController(
- IJsonApiContext jsonApiContext,
- IResourceService resourceService,
- ILoggerFactory loggerFactory)
- : base(jsonApiContext, resourceService, loggerFactory)
- { }
-}
-```
-
-### Non-Integer Type Keys
-
-If your model is using a type other than `int` for the primary key,
-you should explicitly declare it in the controller
-and service generic type definitions:
-
-```csharp
-public class ThingsController : JsonApiController
- //---------------------- ^^^^
-{
- public ThingsController(
- IJsonApiContext jsonApiContext,
- IResourceService resourceService,
- //--------------------- ^^^^
- ILoggerFactory loggerFactory)
- : base(jsonApiContext, resourceService, loggerFactory)
- { }
-}
-```
-
-### Limiting Write Access
-
-It is possible to limit write resource access on the controller entirely using the following attributes:
-
-- `NoHttpPost`: disallow POST requests
-- `NoHttpPatch`: disallow PATCH requests
-- `NoHttpDelete`: disallow DELETE requests
-- `HttpReadOnly`: all of the above
-
-```csharp
-[HttpReadOnly]
-public class ThingsController : JsonApiController
-{
- public ThingsController(
- IJsonApiContext jsonApiContext,
- IResourceService resourceService,
- ILoggerFactory loggerFactory)
- : base(jsonApiContext, resourceService, loggerFactory)
- { }
-}
-```
-
-### Additional customizations
-
-If you need additional customization at the controller level, you can override the virtual
-methods. Please be aware that this is not the place for advanced business logic
-which should be performed at the [service](resourceservices.html) or [repository](entityrepositories.html) layers. Here is an example override at the controller layer:
-
-```csharp
-public class TodoItemsController : JsonApiController
-{
- public TodoItemsController(
- IJsonApiContext jsonApiContext,
- IResourceService resourceService,
- ILoggerFactory loggerFactory)
- : base(jsonApiContext, resourceService, loggerFactory)
- { }
-
- [HttpGet]
- public override async Task GetAsync()
- {
- // custom code
- if(RequestIsValid() == false)
- return BadRequest();
-
- // return result from base class
- return await base.GetAsync();
- }
-
- // some custom validation logic
- private bool RequestIsValid() => true;
-}
-```
diff --git a/docs/EntityRepositories.md b/docs/EntityRepositories.md
deleted file mode 100644
index 62d156e427..0000000000
--- a/docs/EntityRepositories.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-currentMenu: repositories
----
-
-# Entity Repositories
-
-If you want to use EF, but need additional data access logic (such as authorization), you can implement custom methods for accessing the data by creating an implementation of
-[IEntityRepository<Entity, TId>](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Data/IEntityRepository.cs). If you only need minor changes you can override the
-methods defined in [DefaultEntityRepository<TEntity, TId>](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs).
-
-The repository should then be
-add to the service collection in `Startup.cs` like so:
-
-```csharp
-public IServiceProvider ConfigureServices(IServiceCollection services) {
- services.AddScoped, MyAuthorizedEntityRepository>();
- // ...
-}
-```
-
-A sample implementation that performs data authorization might look like:
-
-```csharp
-public class MyAuthorizedEntityRepository : DefaultEntityRepository
-{
- private readonly ILogger _logger;
- private readonly IAuthenticationService _authenticationService;
-
- public MyAuthorizedEntityRepository(
- ILoggerFactory loggerFactory,
- IJsonApiContext jsonApiContext,
- IAuthenticationService authenticationService)
- : base(loggerFactory, jsonApiContext)
- {
- _logger = loggerFactory.CreateLogger();
- _authenticationService = authenticationService;
- }
-
- public override IQueryable Get()
- {
- return base.Get().Where(e => e.UserId == _authenticationService.UserId);
- }
-}
-```
diff --git a/docs/Errors.md b/docs/Errors.md
deleted file mode 100644
index 59f4f6da21..0000000000
--- a/docs/Errors.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-currentMenu: errors
----
-
-# Custom Errors
-
-By default, errors will only contain the properties defined by the internal [Error](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Internal/Error.cs) class. However, you can create your own by inheriting from `Error` and either throwing it in a `JsonApiException` or returning the error from your controller.
-
-```csharp
-// custom error definition
-public class CustomError : Error {
- public CustomError(string status, string title, string detail, string myProp)
- : base(status, title, detail)
- {
- MyCustomProperty = myProp;
- }
- public string MyCustomProperty { get; set; }
-}
-
-// throwing a custom error
-public void MyMethod() {
- var error = new CustomError("507", "title", "detail", "custom");
- throw new JsonApiException(error);
-}
-
-// returning from controller
-[HttpPost]
-public override async Task PostAsync([FromBody] MyEntity entity)
-{
- if(_db.IsFull)
- return Error(new CustomError("507", "Database is full.", "Theres no more room.", "Sorry."));
-
- if(model.Validations.IsValid == false)
- return Errors(model.Validations.GetErrors());
-
- // ...
-}
-```
\ No newline at end of file
diff --git a/docs/Filtering.md b/docs/Filtering.md
deleted file mode 100644
index c258fe4f73..0000000000
--- a/docs/Filtering.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-currentMenu: filtering
----
-
-# Filtering
-
-You can filter resources by attributes using the `filter` query parameter.
-By default, all attributes are filterable.
-The filtering strategy we have selected, uses the following form:
-
-```
-?filter[attribute]=value
-```
-
-For operations other than equality, the query can be prefixed with an operation
-identifier):
-
-```
-?filter[attribute]=eq:value
-?filter[attribute]=lt:value
-?filter[attribute]=gt:value
-?filter[attribute]=le:value
-?filter[attribute]=ge:value
-?filter[attribute]=like:value
-```
-
-### Custom Filters
-
-You can customize the filter implementation by overriding the method in the `DefaultEntityRepository` like so:
-
-```csharp
-public class MyEntityRepository : DefaultEntityRepository
-{
- public MyEntityRepository(
- AppDbContext context,
- ILoggerFactory loggerFactory,
- IJsonApiContext jsonApiContext)
- : base(context, loggerFactory, jsonApiContext)
- { }
-
- public override IQueryable Filter(IQueryable entities, FilterQuery filterQuery)
- {
- // use the base filtering method
- entities = base.Filter(entities, filterQuery);
-
- // implement custom method
- return ApplyMyCustomFilter(entities, filterQuery);
- }
-}
-```
diff --git a/docs/Index.md b/docs/Index.md
deleted file mode 100644
index 64b0861155..0000000000
--- a/docs/Index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-currentMenu: about
----
-
-# JSON API .Net Core
-
-A framework for building [json:api](http://jsonapi.org/) compliant web servers. It allows you to eliminate a significant amount of boilerplate while offering out-of-the-box features such as sorting, filtering and pagination. This library provides all the required middleware to build a complete server. All you need to focus on is defining the resources and implementing your custom business logic. This library has been designed around dependency injection making extensibility incredibly easy.
\ No newline at end of file
diff --git a/docs/Installation.md b/docs/Installation.md
deleted file mode 100644
index 367d5d3a2e..0000000000
--- a/docs/Installation.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-currentMenu: installation
----
-
-# Installation
-
-- CLI
-```
-$ dotnet add package jsonapidotnetcore
-```
-
-- Visual Studio
-```
-Install-Package JsonApiDotnetCore
-```
-
-- *.csproj
-```xml
-
-
-
-
-```
-
-Click [here](https://www.nuget.org/packages/JsonApiDotnetCore/) for the latest NuGet version.
-
-For pre-releases (develop branch), add the [MyGet](https://www.myget.org/feed/Details/research-institute) package feed
-(https://www.myget.org/F/research-institute/api/v3/index.json)
-to your nuget configuration.
\ No newline at end of file
diff --git a/docs/Layers.md b/docs/Layers.md
deleted file mode 100644
index 5c71030274..0000000000
--- a/docs/Layers.md
+++ /dev/null
@@ -1,57 +0,0 @@
----
-currentMenu: layers
----
-
-# The Layers
-
-By default, data retrieval is distributed across 3 layers:
-
-1. [JsonApiController](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Controllers/JsonApiController.cs) (required)
-2. [IResourceService](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Services/IResourceService.cs) (default [EntityResourceService](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Services/EntityResourceService.cs))
-3. [IEntityRepository](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Data/IEntityRepository.cs) (default [DefaultEntityRepository](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs))
-
-Customization can be done at any of these layers. However, it is recommended that you make your customizations at the service or the repository layer when possible to keep the controllers free of unnecessary logic. You can use the following as a general rule of
-thumb for where to put business logic:
-
-- **Controller**: simple validation logic that should result in the return of specific HTTP status codes such as model validation
-- **IResourceService**: advanced BL and replacement of data access mechanisms
-- **IEntityRepository**: custom logic that builds on the EF APIs, such as Authorization of data
-
-## Replacing Services / Repositories
-
-Replacing services is done on a per resource basis and can be done through simple DI
-in your Startup.cs file:
-
-```csharp
-public IServiceProvider ConfigureServices(IServiceCollection services)
-{
- // custom service
- services.AddScoped, CustomPersonService>();
-
- // custom repository
- services.AddScoped, AuthorizedTodoItemRepository>();
-
- // ...
-}
-```
-
-## Not Using Entity Framework?
-
-Out of the box, the library uses your `DbContext` to create a "ContextGraph" or map of all your models and their relationships. If, however, you have models that are not members of a `DbContext`, you can manually create this graph like so:
-
-```csharp
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- // Add framework services.
- var mvcBuilder = services.AddMvc();
-
- services.AddJsonApi(options => {
- options.Namespace = "api/v1";
- options.BuildContextGraph((builder) => {
- builder.AddResource("my-models");
- });
- }, mvcBuilder);
- // ...
-}
-```
diff --git a/docs/Meta.md b/docs/Meta.md
deleted file mode 100644
index 10b1a23e9a..0000000000
--- a/docs/Meta.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-currentMenu: meta
----
-
-# Meta
-
-Meta objects can be assigned in two ways:
- - Resource meta
- - Request Meta
-
-Resource meta can be defined by implementing `IHasMeta` on the model class:
-
-```csharp
-public class Person : Identifiable, IHasMeta
-{
- // ...
-
- public Dictionary GetMeta(IJsonApiContext context)
- {
- return new Dictionary {
- { "copyright", "Copyright 2015 Example Corp." },
- { "authors", new string[] { "Jared Nance" } }
- };
- }
-}
-```
-
-Request Meta can be added by injecting a service that implements `IRequestMeta`.
-In the event of a key collision, the Request Meta will take precendence.
diff --git a/docs/Middleware.md b/docs/Middleware.md
deleted file mode 100644
index 681136ca9b..0000000000
--- a/docs/Middleware.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-currentMenu: middleware
----
-
-# Configure Middleware and Services
-
-Add the following to your `Startup.ConfigureServices` method.
-Replace `AppDbContext` with your DbContext.
-
-```csharp
-services.AddJsonApi();
-```
-
-Add the middleware to the `Startup.Configure` method.
-Note that under the hood, this will call `app.UseMvc()`
-so there is no need to add that as well.
-
-```csharp
-app.UseJsonApi();
-```
\ No newline at end of file
diff --git a/docs/Models.md b/docs/Models.md
deleted file mode 100644
index 73d9afdc43..0000000000
--- a/docs/Models.md
+++ /dev/null
@@ -1,106 +0,0 @@
----
-currentMenu: models
----
-
-# Defining Models
-
-Models must implement [IIdentifiable<TId>](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Models/IIdentifiable.cs).
-The easiest way to do this is to inherit [Identifiable<TId>](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Models/Identifiable.cs) where `TId` is the type of the primary key, like so:
-
-```csharp
-public class Person : Identifiable
-{ }
-```
-
-You can use the non-generic `Identifiable` if your primary key is an integer:
-
-```csharp
-public class Person : Identifiable
-{ }
-```
-
-If you need to hang annotations or attributes on the `Id` property, you can override the virtual member:
-
-```csharp
-public class Person : Identifiable
-{
- [Key]
- [Column("person_id")]
- public override int Id { get; set; }
-}
-```
-
-If your model must inherit from another class, you can always implement the interface yourself.
-In the following example, ApplicationUser inherits IdentityUser which already contains an Id property of
-type string.
-
-```csharp
-public class ApplicationUser
-: IdentityUser, IIdentifiable
-{
- [NotMapped]
- public string StringId { get => this.Id; set => Id = value; }
-}
-```
-
-## Specifying Public Attributes
-
-If you want an attribute on your model to be publicly available,
-add the `AttrAttribute` and provide the outbound name.
-
-```csharp
-public class Person : Identifiable
-{
- [Attr("first-name")]
- public string FirstName { get; set; }
-}
-```
-
-### Immutability
-
-Attributes can be marked as immutable which will prevent `PATCH` requests
-from updating them:
-
-```csharp
-public class Person : Identifiable
-{
- [Attr("first-name", immutable: true)]
- public string FirstName { get; set; }
-}
-```
-
-## Relationships
-
-In order for navigation properties to be identified in the model,
-they should be labeled with the appropriate attribute (either `HasOne` or `HasMany`).
-
-```csharp
-public class Person : Identifiable
-{
- [Attr("first-name")]
- public string FirstName { get; set; }
-
- [HasMany("todo-items")]
- public virtual List TodoItems { get; set; }
-}
-```
-
-Dependent relationships should contain a property in the form `{RelationshipName}Id`.
-For example, a `TodoItem` may have an `Owner` and so the Id attribute should be `OwnerId` like so:
-
-```csharp
-public class TodoItem : Identifiable
-{
- [Attr("description")]
- public string Description { get; set; }
-
- public int OwnerId { get; set; }
-
- [HasOne("owner")]
- public virtual Person Owner { get; set; }
-}
-```
-
-## Resource Names
-
-See [ContextGraph](contextGraph.html) for details on how the resource names are determined.
\ No newline at end of file
diff --git a/docs/Options.md b/docs/Options.md
deleted file mode 100644
index ffba0bd685..0000000000
--- a/docs/Options.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-currentMenu: options
----
-
-# Global Options
-
-## Client Generated Ids
-
-By default, the server will respond with a `403 Forbidden` HTTP Status Code if a `POST` request is
-received with a client generated id. However, this can be allowed by setting the `AllowClientGeneratedIds`
-flag in the options:
-
-```csharp
-public IServiceProvider ConfigureServices(IServiceCollection services) {
- services.AddJsonApi(
- opt => opt.AllowClientGeneratedIds = true);
- // ...
-}
-```
-
-## Pagination
-
-If you would like pagination implemented by default, you can specify the page size
-when setting up the services:
-
-```csharp
-public IServiceProvider ConfigureServices(IServiceCollection services) {
- services.AddJsonApi(
- opt => opt.DefaultPageSize = 10);
- // ...
-}
-```
-
-### Total Record Count
-
-The total number of records can be added to the document meta by setting it in the options:
-
-```csharp
-public IServiceProvider ConfigureServices(IServiceCollection services) {
- services.AddJsonApi(opt =>
- {
- opt.DefaultPageSize = 5;
- opt.IncludeTotalRecordCount = true;
- });
- // ...
-}
-```
diff --git a/docs/Pagination.md b/docs/Pagination.md
deleted file mode 100644
index 6d645cac2b..0000000000
--- a/docs/Pagination.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-currentMenu: pagination
----
-
-# Pagination
-
-Resources can be paginated.
-The following query would set the page size to 10 and get page 2.
-
-```
-?page[size]=10&page[number]=2
-```
-
-If you would like pagination implemented by default, you can specify the page size
-when setting up the services:
diff --git a/docs/QueryingData.md b/docs/QueryingData.md
deleted file mode 100644
index d6de50111a..0000000000
--- a/docs/QueryingData.md
+++ /dev/null
@@ -1,185 +0,0 @@
-# Querying Data
-### Pagination
-
-Resources can be paginated.
-The following query would set the page size to 10 and get page 2.
-
-```
-?page[size]=10&page[number]=2
-```
-
-If you would like pagination implemented by default, you can specify the page size
-when setting up the services:
-
-```csharp
- services.AddJsonApi(
- opt => opt.DefaultPageSize = 10);
-```
-
-**Total Record Count**
-
-The total number of records can be added to the document meta by setting it in the options:
-
-```csharp
-services.AddJsonApi(opt =>
-{
- opt.DefaultPageSize = 5;
- opt.IncludeTotalRecordCount = true;
-});
-```
-
-### Filtering
-
-You can filter resources by attributes using the `filter` query parameter.
-By default, all attributes are filterable.
-The filtering strategy we have selected, uses the following form:
-
-```
-?filter[attribute]=value
-```
-
-For operations other than equality, the query can be prefixed with an operation
-identifier):
-
-```
-?filter[attribute]=eq:value
-?filter[attribute]=lt:value
-?filter[attribute]=gt:value
-?filter[attribute]=le:value
-?filter[attribute]=ge:value
-?filter[attribute]=like:value
-```
-
-#### Custom Filters
-
-You can customize the filter implementation by overriding the method in the `DefaultEntityRepository` like so:
-
-```csharp
-public class MyEntityRepository : DefaultEntityRepository
-{
- public MyEntityRepository(
- AppDbContext context,
- ILoggerFactory loggerFactory,
- IJsonApiContext jsonApiContext)
- : base(context, loggerFactory, jsonApiContext)
- { }
-
- public override IQueryable Filter(IQueryable entities, FilterQuery filterQuery)
- {
- // use the base filtering method
- entities = base.Filter(entities, filterQuery);
-
- // implement custom method
- return ApplyMyCustomFilter(entities, filterQuery);
- }
-}
-```
-
-### Sorting
-
-Resources can be sorted by an attribute:
-
-```
-?sort=attribute // ascending
-?sort=-attribute // descending
-```
-
-### Meta
-
-Meta objects can be assigned in two ways:
- - Resource meta
- - Request Meta
-
-Resource meta can be defined by implementing `IHasMeta` on the model class:
-
-```csharp
-public class Person : Identifiable, IHasMeta
-{
- // ...
-
- public Dictionary GetMeta(IJsonApiContext context)
- {
- return new Dictionary {
- { "copyright", "Copyright 2015 Example Corp." },
- { "authors", new string[] { "Jared Nance" } }
- };
- }
-}
-```
-
-Request Meta can be added by injecting a service that implements `IRequestMeta`.
-In the event of a key collision, the Request Meta will take precendence.
-
-### Client Generated Ids
-
-By default, the server will respond with a `403 Forbidden` HTTP Status Code if a `POST` request is
-received with a client generated id. However, this can be allowed by setting the `AllowClientGeneratedIds`
-flag in the options:
-
-```csharp
-services.AddJsonApi(opt =>
-{
- opt.AllowClientGeneratedIds = true;
- // ..
-});
-```
-
-### Custom Errors
-
-By default, errors will only contain the properties defined by the internal [Error](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Internal/Error.cs) class. However, you can create your own by inheriting from `Error` and either throwing it in a `JsonApiException` or returning the error from your controller.
-
-```csharp
-// custom error definition
-public class CustomError : Error {
- public CustomError(string status, string title, string detail, string myProp)
- : base(status, title, detail)
- {
- MyCustomProperty = myProp;
- }
- public string MyCustomProperty { get; set; }
-}
-
-// throwing a custom error
-public void MyMethod() {
- var error = new CustomError("507", "title", "detail", "custom");
- throw new JsonApiException(error);
-}
-
-// returning from controller
-[HttpPost]
-public override async Task PostAsync([FromBody] MyEntity entity)
-{
- if(_db.IsFull)
- return new ObjectResult(new CustomError("507", "Database is full.", "Theres no more room.", "Sorry."));
-
- // ...
-}
-```
-
-### Sparse Fieldsets
-
-We currently support top-level field selection.
-What this means is you can restrict which fields are returned by a query using the `fields` query parameter, but this does not yet apply to included relationships.
-
-- Currently valid:
-```http
-GET /articles?fields[articles]=title,body HTTP/1.1
-Accept: application/vnd.api+json
-```
-
-- Not yet supported:
-```http
-GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1
-Accept: application/vnd.api+json
-```
-
-## Tests
-
-I am using DotNetCoreDocs to generate sample requests and documentation.
-
-1. To run the tests, start a postgres server and verify the connection properties define in `/test/JsonApiDotNetCoreExampleTests/appsettings.json`
-2. `cd ./test/JsonApiDotNetCoreExampleTests`
-3. `dotnet test`
-4. `cd ./src/JsonApiDotNetCoreExample`
-5. `dotnet run`
-6. `open http://localhost:5000/docs`
diff --git a/docs/ResourceServices.md b/docs/ResourceServices.md
deleted file mode 100644
index 28629d9e36..0000000000
--- a/docs/ResourceServices.md
+++ /dev/null
@@ -1,126 +0,0 @@
----
-currentMenu: services
----
-
-# Resource Services
-
-The [IResourceService](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Services/IResourceService.cs) acts as a service layer between the controller and the data access
-layer. This allows you to customize it however you want and not be dependent upon Entity
-Framework. This is also a good place to implement custom business logic.
-
-### Supplementing Default Behavior
-
-A simple example would be to send notifications when an entity gets created:
-
-```csharp
-public class TodoItemService : EntityResourceService {
-
- private readonly INotificationService _notificationService;
-
- public TodoItemService(
- IJsonApiContext jsonApiContext,
- IEntityRepository repository,
- ILoggerFactory loggerFactory,
- // Get the notification service via DI
- INotificationService notificationService)
- : base(jsonApiContext, repository, loggerFactory)
- {
- _notificationService = notificationService;
- }
-
- public override async Task CreateAsync(TEntity entity)
- {
- // call the base implementation which uses Entity Framework
- var newEntity = await base.CreateAsync(entity);
-
- // custom code
- _notificationService.Notify($"Entity created: { newEntity.Id }");
-
- // don't forget to return the new entity
- return entity;
- }
-}
-```
-
-### Not Using Entity Framework?
-
-As previously discussed, this library uses Entity Framework by default.
-If you'd like to use another ORM that does not implement `IQueryable`,
-you can inject a custom service like so:
-
-```csharp
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- // add the service override for MyModel
- services.AddScoped, MyModelService>();
-
- // add your own DAO
- services.AddScoped();
- // ...
-}
-
-
-// MyModelService.cs
-public class MyModelService : IResourceService
-{
- private readonly IMyModelDAL _dal;
- public MyModelService(IMyModelDAL dal)
- {
- _dal = dal;
- }
-
- public Task> GetAsync()
- {
- return await _dal.GetModelAsync();
- }
-}
-```
-
-### Limited Requirements
-
-In some cases it may be necessary to only expose a few methods on the resource.
-For this reason, we have created a hierarchy of service interfaces that can be used to get the
-exact implementation you require. Below is a table outlining these interfaces:
-
-
-
-
- In order to take advantage of these interfaces you first need to inject the service for each implemented interface.
- Using Autofac, as an example, this is simply:
-
-```csharp
-public class MyResourceService : ICreateService, IDeleteService {
- // ...
-}
-```
-
-```csharp
-public class Startup {
- public IServiceProvider ConfigureServices(IServiceCollection services) {
- builder.RegisterType().AsImplementedInterfaces();
- }
-}
-```
-
-Then in the controller, you should inherit the base controller and pass the services into
-the named, optional base parameters:
-
-```csharp
-public class MyResourcesController : BaseJsonApiController {
-
- public MyResourcesController(
- IJsonApiContext jsonApiContext,
- ICreateService create,
- IDeleteService delete
- ) : base(jsonApiContext, create: create, delete: delete) { }
-
- [HttpPost]
- public override async Task PostAsync([FromBody] MyResource entity)
- => await base.PostAsync(entity);
-
- [HttpDelete("{id}")]
- public override async TaskDeleteAsync(int id)
- => await base.DeleteAsync(id);
-}
-```
\ No newline at end of file
diff --git a/docs/Routing.md b/docs/Routing.md
deleted file mode 100644
index 008438dd39..0000000000
--- a/docs/Routing.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-currentMenu: routing
----
-
-# Routing
-
-By default the library will configure routes for each controller.
-Based on the [recommendations](http://jsonapi.org/recommendations/)
-outlined in the JSONAPI spec, routes are hyphenated.
-
-```http
-GET /api/compound-models HTTP/1.1
-Accept: application/vnd.api+json
-```
-
-## Namespacing and Versioning URLs
-
-You can add a namespace to the URL by specifying it in `ConfigureServices`:
-
-```csharp
-public IServiceProvider ConfigureServices(IServiceCollection services) {
- services.AddJsonApi(
- opt => opt.Namespace = "api/v1");
-}
-```
-
-## Disable Convention
-
-You can disable the dasherized convention and specify your own template
-by using the `DisableRoutingConvention` Attribute.
-
-```csharp
-[Route("[controller]")]
-[DisableRoutingConvention]
-public class CamelCasedModelsController : JsonApiController {
- public CamelCasedModelsController(
- IJsonApiContext jsonApiContext,
- IResourceService resourceService,
- ILoggerFactory loggerFactory)
- : base(jsonApiContext, resourceService, loggerFactory)
- { }
-}
-```
-
-It is important to note that your routes *must* still end with the model name in the same format
-as the resource name. This is so that we can build accurrate resource links in the json:api document.
-For example, if you define a resource as `MyModels` the controller route must match:
-
-```csharp
-public IServiceProvider ConfigureServices(IServiceCollection services) {
- services.AddJsonApi(options => {
- options.BuildContextGraph((builder) => {
- // resource definition
- builder.AddResource("myModels");
- });
- });
-}
-
-// controller definition
-[Route("api/myModels")]
-[DisableRoutingConvention]
-public class TodoItemsController : JsonApiController {
- //...
-}
-```
diff --git a/docs/Sorting.md b/docs/Sorting.md
deleted file mode 100644
index 6a5367d8d9..0000000000
--- a/docs/Sorting.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-currentMenu: sorting
----
-
-# Sorting
-
-Resources can be sorted by an attribute:
-
-```
-?sort=attribute // ascending
-?sort=-attribute // descending
-```
diff --git a/docs/SparseFieldsets.md b/docs/SparseFieldsets.md
deleted file mode 100644
index 16e66a2c89..0000000000
--- a/docs/SparseFieldsets.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-currentMenu: sparsefields
----
-
-# Sparse Fieldsets
-
-We currently support top-level field selection.
-What this means is you can restrict which fields are returned by a query using the `fields` query parameter, but this does not yet apply to included relationships.
-
-- Currently valid:
-```http
-GET /articles?fields[articles]=title,body HTTP/1.1
-Accept: application/vnd.api+json
-```
-
-- Not yet supported:
-```http
-GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1
-Accept: application/vnd.api+json
-```
\ No newline at end of file
diff --git a/docs/Usage.md b/docs/Usage.md
deleted file mode 100644
index 33db2d787f..0000000000
--- a/docs/Usage.md
+++ /dev/null
@@ -1,144 +0,0 @@
----
-currentMenu: usage
----
-
-# Usage
-
-The most basic use case leverages Entity Framework.
-The shortest path to a running API looks like:
-
-- Create a new web app
-- Install
-- Define models
-- Define the DbContext
-- Define controllers
-- Add Middleware and Services
-- Seed the database
-- Run Migrations
-- Start the app
-
-This page will walk you through the **simplest** use case. More detailed examples can be found in the detailed usage subsections.
-
-
-### Create a New Web App
-
-```
-$ mkdir MyApp
-$ cd MyApp
-$ dotnet new webapi
-```
-
-### Install
-
-See [Installation](installation.html)
-
-### Models
-
-Define your domain models such that they implement `IIdentifiable`.
-The easiest way to do this is to inherit `Identifiable`:
-
-```csharp
-public class Person : Identifiable
-{
- [Attr("name")]
- public string Name { get; set; }
-}
-```
-
-### DbContext
-
-Nothing special here, just an ordinary DbContext
-
-```csharp
-public class AppDbContext : DbContext
-{
- public AppDbContext(DbContextOptions options)
- : base(options) { }
-
- public DbSet People { get; set; }
-}
-```
-
-### Controllers
-
-You need to create controllers that inherit from `JsonApiController` or `JsonApiController`
-where `TEntity` is the model that inherits from `Identifiable`.
-
-```csharp
-public class PeopleController : JsonApiController
-{
- public PeopleController(
- IJsonApiContext jsonApiContext,
- IResourceService resourceService,
- ILoggerFactory loggerFactory)
- : base(jsonApiContext, resourceService, loggerFactory)
- { }
-}
-```
-
-### Middleware and Services
-
-Finally, add the services by adding the following to your
-`Startup.ConfigureServices`:
-
-```csharp
-public IServiceProvider ConfigureServices(IServiceCollection services)
-{
- // add the db context like you normally would
- services.AddDbContext(options =>
- { // use whatever provider you want, this is just an example
- options.UseNpgsql(GetDbConnectionString());
- }, ServiceLifetime.Transient);
-
- // add jsonapi dotnet core
- services.AddJsonApi();
- // ...
-}
-```
-
-Add the middleware to the `Startup.Configure` method.
-Note that under the hood, this will call `app.UseMvc()`
-so there is no need to add that as well.
-
-```csharp
-public void Configure(IApplicationBuilder app)
-{
- app.UseJsonApi();
-}
-```
-
-### Seeding the Database
-
-One way to seed the database is in your Configure method:
-
-```csharp
-public void Configure(
- IApplicationBuilder app,
- AppDbContext context)
-{
- context.Database.EnsureCreated();
- if(context.People.Any() == false)
- {
- context.People.Add(new Person {
- Name = "John Doe"
- });
- context.SaveChanges();
- }
- // ...
- app.UseJsonApi();
-}
-```
-
-### Run Migrations
-
-```
-$ dotnet ef migrations add AddPeople
-$ dotnet ef database update
-```
-
-### Start the App
-
-```
-$ dotnet run
-$ curl http://localhost:5000/people
-```
diff --git a/docs/service_table.png b/docs/service_table.png
deleted file mode 100644
index d6ae611370..0000000000
Binary files a/docs/service_table.png and /dev/null differ
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000000..78f1acd521
Binary files /dev/null and b/logo.png differ
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/Restricted/ReadOnlyController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/Restricted/ReadOnlyController.cs
index 57fafc8fc6..2515b1ea7a 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/Restricted/ReadOnlyController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/Restricted/ReadOnlyController.cs
@@ -5,7 +5,7 @@ namespace JsonApiDotNetCoreExample.Controllers.Restricted
{
[Route("[controller]")]
[HttpReadOnly]
- public class ReadOnlyController : Controller
+ public class ReadOnlyController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok();
@@ -22,7 +22,7 @@ public class ReadOnlyController : Controller
[Route("[controller]")]
[NoHttpPost]
- public class NoHttpPostController : Controller
+ public class NoHttpPostController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok();
@@ -39,7 +39,7 @@ public class NoHttpPostController : Controller
[Route("[controller]")]
[NoHttpPatch]
- public class NoHttpPatchController : Controller
+ public class NoHttpPatchController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok();
@@ -56,7 +56,7 @@ public class NoHttpPatchController : Controller
[Route("[controller]")]
[NoHttpDelete]
- public class NoHttpDeleteController : Controller
+ public class NoHttpDeleteController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok();
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TestValuesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TestValuesController.cs
index 3443d34b74..a29295c426 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TestValuesController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TestValuesController.cs
@@ -3,7 +3,7 @@
namespace JsonApiDotNetCoreExample.Controllers
{
[Route("[controller]")]
- public class TestValuesController : Controller
+ public class TestValuesController : ControllerBase
{
[HttpGet]
public IActionResult Get()
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs
index de784c129a..f5f3e111d9 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs
@@ -33,17 +33,12 @@ public CustomJsonApiController(
}
public class CustomJsonApiController
- : Controller where T : class, IIdentifiable
+ : ControllerBase where T : class, IIdentifiable
{
private readonly ILogger _logger;
private readonly IResourceService _resourceService;
private readonly IJsonApiContext _jsonApiContext;
- protected IActionResult UnprocessableEntity()
- {
- return new StatusCodeResult(422);
- }
-
protected IActionResult Forbidden()
{
return new StatusCodeResult(403);
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs
new file mode 100644
index 0000000000..dbd144caa4
--- /dev/null
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs
@@ -0,0 +1,17 @@
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.Services;
+using JsonApiDotNetCoreExample.Models;
+using Microsoft.Extensions.Logging;
+
+namespace JsonApiDotNetCoreExample.Controllers
+{
+ public class UsersController : JsonApiController
+ {
+ public UsersController(
+ IJsonApiContext jsonApiContext,
+ IResourceService resourceService,
+ ILoggerFactory loggerFactory)
+ : base(jsonApiContext, resourceService, loggerFactory)
+ { }
+ }
+}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
index ccdf8e8d9f..ec96cadd8e 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
@@ -1,5 +1,4 @@
-using JsonApiDotNetCoreExample.Models;
-using JsonApiDotNetCore.Models;
+using JsonApiDotNetCoreExample.Models;
using Microsoft.EntityFrameworkCore;
namespace JsonApiDotNetCoreExample.Data
@@ -14,12 +13,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.Property(t => t.CreatedDate).HasDefaultValueSql("CURRENT_TIMESTAMP").IsRequired();
-
+
modelBuilder.Entity()
.HasOne(t => t.Assignee)
.WithMany(p => p.AssignedTodoItems)
.HasForeignKey(t => t.AssigneeId);
-
+
modelBuilder.Entity()
.HasOne(t => t.Owner)
.WithMany(p => p.TodoItems)
@@ -28,11 +27,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
public DbSet TodoItems { get; set; }
public DbSet People { get; set; }
-
- [Resource("todo-collections")]
public DbSet TodoItemCollections { get; set; }
-
- [Resource("camelCasedModels")]
public DbSet CamelCasedModels { get; set; }
+ public DbSet Articles { get; set; }
+ public DbSet Authors { get; set; }
+ public DbSet NonJsonApiResources { get; set; }
+ public DbSet Users { get; set; }
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj
index e9971a1113..fd657e83bf 100755
--- a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj
+++ b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj
@@ -1,7 +1,6 @@
-
- netcoreapp2.0
+ $(NetCoreAppVersion)
true
JsonApiDotNetCoreExample
Exe
@@ -13,7 +12,7 @@
-
+
@@ -27,8 +26,4 @@
-
-
-
-
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.Designer.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.Designer.cs
deleted file mode 100644
index be31f0ccae..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.Designer.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-using JsonApiDotNetCoreExample.Data;
-
-namespace JsonApiDotNetCoreExample.Migrations
-{
- [DbContext(typeof(AppDbContext))]
- [Migration("20170315140127_initial")]
- partial class initial
- {
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
- modelBuilder
- .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
- .HasAnnotation("ProductVersion", "1.1.1");
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Person", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("FirstName");
-
- b.Property("LastName");
-
- b.HasKey("Id");
-
- b.ToTable("People");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("CollectionId");
-
- b.Property("Description");
-
- b.Property("Ordinal");
-
- b.Property("OwnerId");
-
- b.HasKey("Id");
-
- b.HasIndex("CollectionId");
-
- b.HasIndex("OwnerId");
-
- b.ToTable("TodoItems");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("Name");
-
- b.Property("OwnerId");
-
- b.HasKey("Id");
-
- b.HasIndex("OwnerId");
-
- b.ToTable("TodoItemCollections");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
- {
- b.HasOne("JsonApiDotNetCoreExample.Models.TodoItemCollection", "Collection")
- .WithMany("TodoItems")
- .HasForeignKey("CollectionId");
-
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner")
- .WithMany("TodoItems")
- .HasForeignKey("OwnerId");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b =>
- {
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner")
- .WithMany("TodoItemCollections")
- .HasForeignKey("OwnerId")
- .OnDelete(DeleteBehavior.Cascade);
- });
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330020650_AddAssignedTodoItems.Designer.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330020650_AddAssignedTodoItems.Designer.cs
deleted file mode 100755
index 52b60adbcb..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330020650_AddAssignedTodoItems.Designer.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-using JsonApiDotNetCoreExample.Data;
-
-namespace JsonApiDotNetCoreExample.Migrations
-{
- [DbContext(typeof(AppDbContext))]
- [Migration("20170330020650_AddAssignedTodoItems")]
- partial class AddAssignedTodoItems
- {
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
- modelBuilder
- .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
- .HasAnnotation("ProductVersion", "1.1.1");
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Person", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("FirstName");
-
- b.Property("LastName");
-
- b.HasKey("Id");
-
- b.ToTable("People");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("AssigneeId");
-
- b.Property("CollectionId");
-
- b.Property("Description");
-
- b.Property("Ordinal");
-
- b.Property("OwnerId");
-
- b.HasKey("Id");
-
- b.HasIndex("AssigneeId");
-
- b.HasIndex("CollectionId");
-
- b.HasIndex("OwnerId");
-
- b.ToTable("TodoItems");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("Name");
-
- b.Property("OwnerId");
-
- b.HasKey("Id");
-
- b.HasIndex("OwnerId");
-
- b.ToTable("TodoItemCollections");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
- {
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Assignee")
- .WithMany("AssignedTodoItems")
- .HasForeignKey("AssigneeId");
-
- b.HasOne("JsonApiDotNetCoreExample.Models.TodoItemCollection", "Collection")
- .WithMany("TodoItems")
- .HasForeignKey("CollectionId");
-
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner")
- .WithMany("TodoItems")
- .HasForeignKey("OwnerId");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b =>
- {
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner")
- .WithMany("TodoItemCollections")
- .HasForeignKey("OwnerId")
- .OnDelete(DeleteBehavior.Cascade);
- });
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330020650_AddAssignedTodoItems.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330020650_AddAssignedTodoItems.cs
deleted file mode 100755
index 8a1810a9d8..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330020650_AddAssignedTodoItems.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using Microsoft.EntityFrameworkCore.Migrations;
-
-namespace JsonApiDotNetCoreExample.Migrations
-{
- public partial class AddAssignedTodoItems : Migration
- {
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AddColumn(
- name: "AssigneeId",
- table: "TodoItems",
- nullable: true);
-
- migrationBuilder.CreateIndex(
- name: "IX_TodoItems_AssigneeId",
- table: "TodoItems",
- column: "AssigneeId");
-
- migrationBuilder.AddForeignKey(
- name: "FK_TodoItems_People_AssigneeId",
- table: "TodoItems",
- column: "AssigneeId",
- principalTable: "People",
- principalColumn: "Id",
- onDelete: ReferentialAction.Restrict);
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropForeignKey(
- name: "FK_TodoItems_People_AssigneeId",
- table: "TodoItems");
-
- migrationBuilder.DropIndex(
- name: "IX_TodoItems_AssigneeId",
- table: "TodoItems");
-
- migrationBuilder.DropColumn(
- name: "AssigneeId",
- table: "TodoItems");
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330234539_AddGuidProperty.Designer.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330234539_AddGuidProperty.Designer.cs
deleted file mode 100755
index ded5d1b160..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330234539_AddGuidProperty.Designer.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-using JsonApiDotNetCoreExample.Data;
-
-namespace JsonApiDotNetCoreExample.Migrations
-{
- [DbContext(typeof(AppDbContext))]
- [Migration("20170330234539_AddGuidProperty")]
- partial class AddGuidProperty
- {
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
- modelBuilder
- .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
- .HasAnnotation("ProductVersion", "1.1.1");
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Person", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("FirstName");
-
- b.Property("LastName");
-
- b.HasKey("Id");
-
- b.ToTable("People");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("AssigneeId");
-
- b.Property("CollectionId");
-
- b.Property("Description");
-
- b.Property("GuidProperty");
-
- b.Property("Ordinal");
-
- b.Property("OwnerId");
-
- b.HasKey("Id");
-
- b.HasIndex("AssigneeId");
-
- b.HasIndex("CollectionId");
-
- b.HasIndex("OwnerId");
-
- b.ToTable("TodoItems");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("Name");
-
- b.Property("OwnerId");
-
- b.HasKey("Id");
-
- b.HasIndex("OwnerId");
-
- b.ToTable("TodoItemCollections");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
- {
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Assignee")
- .WithMany("AssignedTodoItems")
- .HasForeignKey("AssigneeId");
-
- b.HasOne("JsonApiDotNetCoreExample.Models.TodoItemCollection", "Collection")
- .WithMany("TodoItems")
- .HasForeignKey("CollectionId");
-
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner")
- .WithMany("TodoItems")
- .HasForeignKey("OwnerId");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b =>
- {
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner")
- .WithMany("TodoItemCollections")
- .HasForeignKey("OwnerId")
- .OnDelete(DeleteBehavior.Cascade);
- });
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330234539_AddGuidProperty.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330234539_AddGuidProperty.cs
deleted file mode 100755
index 27815ce504..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170330234539_AddGuidProperty.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-namespace JsonApiDotNetCoreExample.Migrations
-{
- public partial class AddGuidProperty : Migration
- {
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AddColumn(
- name: "GuidProperty",
- table: "TodoItems",
- nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropColumn(
- name: "GuidProperty",
- table: "TodoItems");
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170424180950_AddCreatesAndAchievedDates.Designer.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20170424180950_AddCreatesAndAchievedDates.Designer.cs
deleted file mode 100755
index 537b3b043a..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170424180950_AddCreatesAndAchievedDates.Designer.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-using JsonApiDotNetCoreExample.Data;
-
-namespace JsonApiDotNetCoreExample.Migrations
-{
- [DbContext(typeof(AppDbContext))]
- [Migration("20170424180950_AddCreatesAndAchievedDates")]
- partial class AddCreatesAndAchievedDates
- {
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
- modelBuilder
- .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
- .HasAnnotation("ProductVersion", "1.1.1");
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Person", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("FirstName");
-
- b.Property("LastName");
-
- b.HasKey("Id");
-
- b.ToTable("People");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("AchievedDate");
-
- b.Property("AssigneeId");
-
- b.Property("CollectionId");
-
- b.Property("CreatedDate")
- .ValueGeneratedOnAdd()
- .HasDefaultValueSql("CURRENT_TIMESTAMP");
-
- b.Property("Description");
-
- b.Property("GuidProperty");
-
- b.Property("Ordinal");
-
- b.Property("OwnerId");
-
- b.HasKey("Id");
-
- b.HasIndex("AssigneeId");
-
- b.HasIndex("CollectionId");
-
- b.HasIndex("OwnerId");
-
- b.ToTable("TodoItems");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("Name");
-
- b.Property("OwnerId");
-
- b.HasKey("Id");
-
- b.HasIndex("OwnerId");
-
- b.ToTable("TodoItemCollections");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
- {
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Assignee")
- .WithMany("AssignedTodoItems")
- .HasForeignKey("AssigneeId");
-
- b.HasOne("JsonApiDotNetCoreExample.Models.TodoItemCollection", "Collection")
- .WithMany("TodoItems")
- .HasForeignKey("CollectionId");
-
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner")
- .WithMany("TodoItems")
- .HasForeignKey("OwnerId");
- });
-
- modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b =>
- {
- b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner")
- .WithMany("TodoItemCollections")
- .HasForeignKey("OwnerId")
- .OnDelete(DeleteBehavior.Cascade);
- });
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170424180950_AddCreatesAndAchievedDates.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20170424180950_AddCreatesAndAchievedDates.cs
deleted file mode 100755
index 1c36034fe3..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170424180950_AddCreatesAndAchievedDates.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-namespace JsonApiDotNetCoreExample.Migrations
-{
- public partial class AddCreatesAndAchievedDates : Migration
- {
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AddColumn(
- name: "AchievedDate",
- table: "TodoItems",
- nullable: true);
-
- migrationBuilder.AddColumn(
- name: "CreatedDate",
- table: "TodoItems",
- nullable: false,
- defaultValueSql: "CURRENT_TIMESTAMP");
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropColumn(
- name: "AchievedDate",
- table: "TodoItems");
-
- migrationBuilder.DropColumn(
- name: "CreatedDate",
- table: "TodoItems");
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170426232509_AddCamelCasedModel.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20170426232509_AddCamelCasedModel.cs
deleted file mode 100755
index fbec5e0a79..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170426232509_AddCamelCasedModel.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Metadata;
-
-namespace JsonApiDotNetCoreExample.Migrations
-{
- public partial class AddCamelCasedModel : Migration
- {
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "CamelCasedModels",
- columns: table => new
- {
- Id = table.Column(nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
- CompoundAttr = table.Column(nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_CamelCasedModels", x => x.Id);
- });
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "CamelCasedModels");
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170426232509_AddCamelCasedModel.Designer.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.Designer.cs
old mode 100755
new mode 100644
similarity index 71%
rename from src/Examples/JsonApiDotNetCoreExample/Migrations/20170426232509_AddCamelCasedModel.Designer.cs
rename to src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.Designer.cs
index e43d2afc1f..c9788bf82f
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170426232509_AddCamelCasedModel.Designer.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.Designer.cs
@@ -1,21 +1,54 @@
-using System;
+//
+using JsonApiDotNetCoreExample.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
-using JsonApiDotNetCoreExample.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Internal;
+using System;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace JsonApiDotNetCoreExample.Migrations
{
[DbContext(typeof(AppDbContext))]
- [Migration("20170426232509_AddCamelCasedModel")]
- partial class AddCamelCasedModel
+ [Migration("20180327120810_initial")]
+ partial class initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
+#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
- .HasAnnotation("ProductVersion", "1.1.1");
+ .HasAnnotation("ProductVersion", "2.0.1-rtm-125");
+
+ modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Article", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AuthorId");
+
+ b.Property("Name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.ToTable("Articles");
+ });
+
+ modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Author", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Name");
+
+ b.HasKey("Id");
+
+ b.ToTable("Authors");
+ });
modelBuilder.Entity("JsonApiDotNetCoreExample.Models.CamelCasedModel", b =>
{
@@ -93,6 +126,14 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.ToTable("TodoItemCollections");
});
+ modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Article", b =>
+ {
+ b.HasOne("JsonApiDotNetCoreExample.Models.Author", "Author")
+ .WithMany("Articles")
+ .HasForeignKey("AuthorId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
{
b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Assignee")
@@ -115,6 +156,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade);
});
+#pragma warning restore 612, 618
}
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs
similarity index 54%
rename from src/Examples/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.cs
rename to src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs
index e0b4e3b40b..ffbf105255 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs
@@ -1,6 +1,6 @@
-using System;
using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Metadata;
+using System;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace JsonApiDotNetCoreExample.Migrations
{
@@ -8,6 +8,32 @@ public partial class initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
+ migrationBuilder.CreateTable(
+ name: "Authors",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
+ Name = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Authors", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "CamelCasedModels",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
+ CompoundAttr = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CamelCasedModels", x => x.Id);
+ });
+
migrationBuilder.CreateTable(
name: "People",
columns: table => new
@@ -22,6 +48,26 @@ protected override void Up(MigrationBuilder migrationBuilder)
table.PrimaryKey("PK_People", x => x.Id);
});
+ migrationBuilder.CreateTable(
+ name: "Articles",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
+ AuthorId = table.Column(nullable: false),
+ Name = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Articles", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Articles_Authors_AuthorId",
+ column: x => x.AuthorId,
+ principalTable: "Authors",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
migrationBuilder.CreateTable(
name: "TodoItemCollections",
columns: table => new
@@ -47,14 +93,24 @@ protected override void Up(MigrationBuilder migrationBuilder)
{
Id = table.Column(nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
+ AchievedDate = table.Column(nullable: true),
+ AssigneeId = table.Column(nullable: true),
CollectionId = table.Column(nullable: true),
+ CreatedDate = table.Column(nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
Description = table.Column(nullable: true),
+ GuidProperty = table.Column(nullable: false),
Ordinal = table.Column(nullable: false),
OwnerId = table.Column(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TodoItems", x => x.Id);
+ table.ForeignKey(
+ name: "FK_TodoItems_People_AssigneeId",
+ column: x => x.AssigneeId,
+ principalTable: "People",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_TodoItems_TodoItemCollections_CollectionId",
column: x => x.CollectionId,
@@ -69,6 +125,21 @@ protected override void Up(MigrationBuilder migrationBuilder)
onDelete: ReferentialAction.Restrict);
});
+ migrationBuilder.CreateIndex(
+ name: "IX_Articles_AuthorId",
+ table: "Articles",
+ column: "AuthorId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TodoItemCollections_OwnerId",
+ table: "TodoItemCollections",
+ column: "OwnerId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TodoItems_AssigneeId",
+ table: "TodoItems",
+ column: "AssigneeId");
+
migrationBuilder.CreateIndex(
name: "IX_TodoItems_CollectionId",
table: "TodoItems",
@@ -78,18 +149,22 @@ protected override void Up(MigrationBuilder migrationBuilder)
name: "IX_TodoItems_OwnerId",
table: "TodoItems",
column: "OwnerId");
-
- migrationBuilder.CreateIndex(
- name: "IX_TodoItemCollections_OwnerId",
- table: "TodoItemCollections",
- column: "OwnerId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
+ migrationBuilder.DropTable(
+ name: "Articles");
+
+ migrationBuilder.DropTable(
+ name: "CamelCasedModels");
+
migrationBuilder.DropTable(
name: "TodoItems");
+ migrationBuilder.DropTable(
+ name: "Authors");
+
migrationBuilder.DropTable(
name: "TodoItemCollections");
diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs
index 5de99f5078..08c284393e 100755
--- a/src/Examples/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs
@@ -1,8 +1,13 @@
-using System;
+//
+using JsonApiDotNetCoreExample.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
-using JsonApiDotNetCoreExample.Data;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Internal;
+using System;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace JsonApiDotNetCoreExample.Migrations
{
@@ -11,9 +16,38 @@ partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
+#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
- .HasAnnotation("ProductVersion", "1.1.1");
+ .HasAnnotation("ProductVersion", "2.0.1-rtm-125");
+
+ modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Article", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AuthorId");
+
+ b.Property("Name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.ToTable("Articles");
+ });
+
+ modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Author", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Name");
+
+ b.HasKey("Id");
+
+ b.ToTable("Authors");
+ });
modelBuilder.Entity("JsonApiDotNetCoreExample.Models.CamelCasedModel", b =>
{
@@ -91,6 +125,14 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("TodoItemCollections");
});
+ modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Article", b =>
+ {
+ b.HasOne("JsonApiDotNetCoreExample.Models.Author", "Author")
+ .WithMany("Articles")
+ .HasForeignKey("AuthorId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b =>
{
b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Assignee")
@@ -113,6 +155,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade);
});
+#pragma warning restore 612, 618
}
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs
new file mode 100644
index 0000000000..c633d58bdd
--- /dev/null
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs
@@ -0,0 +1,14 @@
+using JsonApiDotNetCore.Models;
+
+namespace JsonApiDotNetCoreExample.Models
+{
+ public class Article : Identifiable
+ {
+ [Attr("name")]
+ public string Name { get; set; }
+
+ [HasOne("author")]
+ public Author Author { get; set; }
+ public int AuthorId { get; set; }
+ }
+}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Author.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Author.cs
new file mode 100644
index 0000000000..c77ad007c8
--- /dev/null
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/Author.cs
@@ -0,0 +1,14 @@
+using JsonApiDotNetCore.Models;
+using System.Collections.Generic;
+
+namespace JsonApiDotNetCoreExample.Models
+{
+ public class Author : Identifiable
+ {
+ [Attr("name")]
+ public string Name { get; set; }
+
+ [HasMany("articles")]
+ public List Articles { get; set; }
+ }
+}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/CamelCasedModel.cs b/src/Examples/JsonApiDotNetCoreExample/Models/CamelCasedModel.cs
index 7adf628f38..43d5a43272 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/CamelCasedModel.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/CamelCasedModel.cs
@@ -2,6 +2,7 @@
namespace JsonApiDotNetCoreExample.Models
{
+ [Resource("camelCasedModels")]
public class CamelCasedModel : Identifiable
{
[Attr("compoundAttr")]
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/NonJsonApiResource.cs b/src/Examples/JsonApiDotNetCoreExample/Models/NonJsonApiResource.cs
new file mode 100644
index 0000000000..7f979f4cfb
--- /dev/null
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/NonJsonApiResource.cs
@@ -0,0 +1,7 @@
+namespace JsonApiDotNetCoreExample.Models
+{
+ public class NonJsonApiResource
+ {
+ public int Id { get; set; }
+ }
+}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
index 8acf87c405..d140b23b6b 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
@@ -20,6 +20,9 @@ public class Person : Identifiable, IHasMeta
[HasMany("todo-collections")]
public virtual List TodoItemCollections { get; set; }
+
+ [HasOne("unincludeable-item", Link.All, canInclude: false)]
+ public virtual TodoItem UnIncludeableItem { get; set; }
public Dictionary GetMeta(IJsonApiContext context)
{
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
index 7257835791..fecd16319d 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
@@ -22,7 +22,7 @@ public TodoItem()
[Attr("created-date")]
public DateTime CreatedDate { get; set; }
- [Attr("achieved-date")]
+ [Attr("achieved-date", isFilterable: false, isSortable: false)]
public DateTime? AchievedDate { get; set; }
public int? OwnerId { get; set; }
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs
index 95a523dff3..85877b3848 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs
@@ -4,6 +4,7 @@
namespace JsonApiDotNetCoreExample.Models
{
+ [Resource("todo-collections")]
public class TodoItemCollection : Identifiable
{
[Attr("name")]
@@ -16,4 +17,4 @@ public class TodoItemCollection : Identifiable
[HasOne("owner")]
public virtual Person Owner { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/User.cs b/src/Examples/JsonApiDotNetCoreExample/Models/User.cs
new file mode 100644
index 0000000000..3b66f0dbb2
--- /dev/null
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/User.cs
@@ -0,0 +1,10 @@
+using JsonApiDotNetCore.Models;
+
+namespace JsonApiDotNetCoreExample.Models
+{
+ public class User : Identifiable
+ {
+ [Attr("username")] public string Username { get; set; }
+ [Attr("password")] public string Password { get; set; }
+ }
+}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Program.cs b/src/Examples/JsonApiDotNetCoreExample/Program.cs
index 16f0ad10a1..b9bbe37b6a 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Program.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Program.cs
@@ -1,26 +1,15 @@
-using System.IO;
+using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
namespace JsonApiDotNetCoreExample
{
public class Program
{
- public static void Main(string[] args)
- {
- var config = new ConfigurationBuilder()
- .AddCommandLine(args)
- .AddEnvironmentVariables(prefix: "ASPNETCORE_")
- .Build();
+ public static void Main(string[] args) => BuildWebHost(args).Run();
- var host = new WebHostBuilder()
- .UseConfiguration(config)
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
+ public static IWebHost BuildWebHost(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
.UseStartup()
.Build();
-
- host.Run();
- }
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs
new file mode 100644
index 0000000000..030bc4eaa4
--- /dev/null
+++ b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using JsonApiDotNetCore.Models;
+using JsonApiDotNetCoreExample.Models;
+
+namespace JsonApiDotNetCoreExample.Resources
+{
+ public class UserResource : ResourceDefinition
+ {
+ protected override List OutputAttrs()
+ => Remove(user => user.Password);
+ }
+}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs
index 9683eb033d..29dfc9e4b5 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs
@@ -18,7 +18,7 @@ public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
- .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
@@ -28,26 +28,22 @@ public Startup(IHostingEnvironment env)
public virtual IServiceProvider ConfigureServices(IServiceCollection services)
{
var loggerFactory = new LoggerFactory();
- loggerFactory.AddConsole(LogLevel.Trace);
- services.AddSingleton(loggerFactory);
+ loggerFactory.AddConsole(LogLevel.Warning);
- services.AddDbContext(options =>
- {
- options.UseNpgsql(GetDbConnectionString());
- }, ServiceLifetime.Transient);
+ var mvcBuilder = services.AddMvcCore();
- services.AddJsonApi(opt =>
- {
- opt.Namespace = "api/v1";
- opt.DefaultPageSize = 5;
- opt.IncludeTotalRecordCount = true;
- });
+ services
+ .AddSingleton(loggerFactory)
+ .AddDbContext(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient)
+ .AddJsonApi(options => {
+ options.Namespace = "api/v1";
+ options.DefaultPageSize = 5;
+ options.IncludeTotalRecordCount = true;
+ },
+ mvcBuilder,
+ discovery => discovery.AddCurrentAssemblyServices());
- var provider = services.BuildServiceProvider();
- var appContext = provider.GetRequiredService();
- if(appContext == null)
- throw new ArgumentException();
-
+ var provider = services.BuildServiceProvider();
return provider;
}
@@ -60,11 +56,10 @@ public virtual void Configure(
context.Database.EnsureCreated();
loggerFactory.AddConsole(Config.GetSection("Logging"));
- loggerFactory.AddDebug();
app.UseJsonApi();
}
public string GetDbConnectionString() => Config["Data:DefaultConnection"];
}
-}
\ No newline at end of file
+}
diff --git a/src/Examples/JsonApiDotNetCoreExample/appsettings.json b/src/Examples/JsonApiDotNetCoreExample/appsettings.json
index ceb4a1ed73..c468439079 100755
--- a/src/Examples/JsonApiDotNetCoreExample/appsettings.json
+++ b/src/Examples/JsonApiDotNetCoreExample/appsettings.json
@@ -1,14 +1,13 @@
{
- "Data": {
- "DefaultConnection":
- "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=password"
- },
- "Logging": {
- "IncludeScopes": false,
- "LogLevel": {
- "Default": "Trace",
- "System": "Trace",
- "Microsoft": "Trace"
+ "Data": {
+ "DefaultConnection": "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=postgres"
+ },
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Warning",
+ "System": "Warning",
+ "Microsoft": "Warning"
+ }
}
- }
}
diff --git a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj
index afaf0e7cff..efdaa68e5b 100755
--- a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj
+++ b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj
@@ -1,5 +1,4 @@
-
$(NetCoreAppVersion)
@@ -19,7 +18,7 @@
-
+
diff --git a/src/Examples/NoEntityFrameworkExample/Program.cs b/src/Examples/NoEntityFrameworkExample/Program.cs
index 5606e8e9f4..76f3020c52 100755
--- a/src/Examples/NoEntityFrameworkExample/Program.cs
+++ b/src/Examples/NoEntityFrameworkExample/Program.cs
@@ -1,4 +1,4 @@
-using System.IO;
+using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace NoEntityFrameworkExample
@@ -7,14 +7,12 @@ public class Program
{
public static void Main(string[] args)
{
- var host = new WebHostBuilder()
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .UseIISIntegration()
+ BuildWebHost(args).Run();
+ }
+
+ public static IWebHost BuildWebHost(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
.UseStartup()
.Build();
-
- host.Run();
- }
}
}
diff --git a/src/Examples/NoEntityFrameworkExample/Startup.cs b/src/Examples/NoEntityFrameworkExample/Startup.cs
index fdea4fc582..711b54cfa3 100755
--- a/src/Examples/NoEntityFrameworkExample/Startup.cs
+++ b/src/Examples/NoEntityFrameworkExample/Startup.cs
@@ -1,4 +1,4 @@
-using JsonApiDotNetCore.Extensions;
+using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Services;
using JsonApiDotNetCoreExample.Data;
using JsonApiDotNetCoreExample.Models;
@@ -9,6 +9,7 @@
using Microsoft.Extensions.Logging;
using NoEntityFrameworkExample.Services;
using Microsoft.EntityFrameworkCore;
+using System;
namespace NoEntityFrameworkExample
{
@@ -18,7 +19,7 @@ public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
- .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
+ .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
@@ -27,10 +28,10 @@ public Startup(IHostingEnvironment env)
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
+ public virtual IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
- var mvcBuilder = services.AddMvc();
+ var mvcBuilder = services.AddMvcCore();
services.AddJsonApi(options => {
options.Namespace = "api/v1";
@@ -46,13 +47,14 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton(Configuration);
services.AddSingleton>(optionsBuilder.Options);
services.AddScoped();
+
+ return services.BuildServiceProvider();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, AppDbContext context)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
- loggerFactory.AddDebug();
context.Database.EnsureCreated();
diff --git a/src/Examples/OperationsExample/.gitignore b/src/Examples/OperationsExample/.gitignore
new file mode 100644
index 0000000000..0f552f400b
--- /dev/null
+++ b/src/Examples/OperationsExample/.gitignore
@@ -0,0 +1,236 @@
+_data/
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# 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
+# TODO: 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
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Microsoft Azure ApplicationInsights config file
+ApplicationInsights.config
+
+# Windows Store app package directory
+AppPackages/
+BundleArtifacts/
+
+# 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
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# 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
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# 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
+
+# FAKE - F# Make
+.fake/
diff --git a/src/Examples/OperationsExample/Controllers/OperationsController.cs b/src/Examples/OperationsExample/Controllers/OperationsController.cs
new file mode 100644
index 0000000000..6e56791f9c
--- /dev/null
+++ b/src/Examples/OperationsExample/Controllers/OperationsController.cs
@@ -0,0 +1,14 @@
+using JsonApiDotNetCore.Controllers;
+using JsonApiDotNetCore.Services.Operations;
+using Microsoft.AspNetCore.Mvc;
+
+namespace OperationsExample.Controllers
+{
+ [Route("api/bulk")]
+ public class OperationsController : JsonApiOperationsController
+ {
+ public OperationsController(IOperationsProcessor processor)
+ : base(processor)
+ { }
+ }
+}
diff --git a/src/Examples/OperationsExample/OperationsExample.csproj b/src/Examples/OperationsExample/OperationsExample.csproj
new file mode 100644
index 0000000000..efb3f2b3d4
--- /dev/null
+++ b/src/Examples/OperationsExample/OperationsExample.csproj
@@ -0,0 +1,30 @@
+
+
+ $(NetCoreAppVersion)
+ true
+ OperationsExample
+ Exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Examples/OperationsExample/Program.cs b/src/Examples/OperationsExample/Program.cs
new file mode 100644
index 0000000000..1c2b6b267a
--- /dev/null
+++ b/src/Examples/OperationsExample/Program.cs
@@ -0,0 +1,15 @@
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+
+namespace OperationsExample
+{
+ public class Program
+ {
+ public static void Main(string[] args) => BuildWebHost(args).Run();
+
+ public static IWebHost BuildWebHost(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStartup()
+ .Build();
+ }
+}
diff --git a/src/Examples/OperationsExample/Properties/launchSettings.json b/src/Examples/OperationsExample/Properties/launchSettings.json
new file mode 100644
index 0000000000..b0d8e5bd4b
--- /dev/null
+++ b/src/Examples/OperationsExample/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:53656/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "OperationsExample": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:53657/"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Examples/OperationsExample/Startup.cs b/src/Examples/OperationsExample/Startup.cs
new file mode 100644
index 0000000000..a889ad85d6
--- /dev/null
+++ b/src/Examples/OperationsExample/Startup.cs
@@ -0,0 +1,55 @@
+using System;
+using JsonApiDotNetCore.Extensions;
+using JsonApiDotNetCoreExample.Data;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace OperationsExample
+{
+ public class Startup
+ {
+ public readonly IConfiguration Config;
+
+ public Startup(IHostingEnvironment env)
+ {
+ var builder = new ConfigurationBuilder()
+ .SetBasePath(env.ContentRootPath)
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddEnvironmentVariables();
+
+ Config = builder.Build();
+ }
+
+ public virtual IServiceProvider ConfigureServices(IServiceCollection services)
+ {
+ var loggerFactory = new LoggerFactory();
+ loggerFactory.AddConsole(LogLevel.Warning);
+
+ services.AddSingleton(loggerFactory);
+
+ services.AddDbContext(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Scoped);
+
+ services.AddJsonApi(opt => opt.EnableOperations = true);
+
+ return services.BuildServiceProvider();
+ }
+
+ public virtual void Configure(
+ IApplicationBuilder app,
+ IHostingEnvironment env,
+ ILoggerFactory loggerFactory,
+ AppDbContext context)
+ {
+ context.Database.EnsureCreated();
+
+ loggerFactory.AddConsole(Config.GetSection("Logging"));
+ app.UseJsonApi();
+ }
+
+ public string GetDbConnectionString() => Config["Data:DefaultConnection"];
+ }
+}
diff --git a/src/Examples/OperationsExample/appsettings.json b/src/Examples/OperationsExample/appsettings.json
new file mode 100644
index 0000000000..73030b1743
--- /dev/null
+++ b/src/Examples/OperationsExample/appsettings.json
@@ -0,0 +1,13 @@
+{
+ "Data": {
+ "DefaultConnection": "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=postgres"
+ },
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Warning",
+ "System": "Warning",
+ "Microsoft": "Warning"
+ }
+ }
+}
diff --git a/src/Examples/ReportsExample/Controllers/ReportsController.cs b/src/Examples/ReportsExample/Controllers/ReportsController.cs
index 523ad417bd..6f431d9291 100644
--- a/src/Examples/ReportsExample/Controllers/ReportsController.cs
+++ b/src/Examples/ReportsExample/Controllers/ReportsController.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
diff --git a/src/Examples/ReportsExample/Program.cs b/src/Examples/ReportsExample/Program.cs
index 41d4c37780..f3ce6c81b0 100644
--- a/src/Examples/ReportsExample/Program.cs
+++ b/src/Examples/ReportsExample/Program.cs
@@ -1,4 +1,4 @@
-using System.IO;
+using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace ReportsExample
@@ -7,14 +7,12 @@ public class Program
{
public static void Main(string[] args)
{
- var host = new WebHostBuilder()
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .UseIISIntegration()
+ BuildWebHost(args).Run();
+ }
+
+ public static IWebHost BuildWebHost(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
.UseStartup()
.Build();
-
- host.Run();
- }
}
}
diff --git a/src/Examples/ReportsExample/ReportsExample.csproj b/src/Examples/ReportsExample/ReportsExample.csproj
index 2131c545a4..24c01b9a8d 100644
--- a/src/Examples/ReportsExample/ReportsExample.csproj
+++ b/src/Examples/ReportsExample/ReportsExample.csproj
@@ -1,24 +1,23 @@
-
-
-
- $(NetCoreAppVersion)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ $(NetCoreAppVersion)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Examples/ReportsExample/Services/ReportService.cs b/src/Examples/ReportsExample/Services/ReportService.cs
index 7baffc6174..9e5348a612 100644
--- a/src/Examples/ReportsExample/Services/ReportService.cs
+++ b/src/Examples/ReportsExample/Services/ReportService.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using JsonApiDotNetCore.Services;
diff --git a/src/Examples/ReportsExample/Startup.cs b/src/Examples/ReportsExample/Startup.cs
index 39d740da84..43a910a0e6 100644
--- a/src/Examples/ReportsExample/Startup.cs
+++ b/src/Examples/ReportsExample/Startup.cs
@@ -1,15 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
using JsonApiDotNetCore.Extensions;
-using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
namespace ReportsExample
{
@@ -21,25 +15,20 @@ public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
- .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Config = builder.Build();
}
- public void ConfigureServices(IServiceCollection services)
+ public virtual void ConfigureServices(IServiceCollection services)
{
- var mvcBuilder = services.AddMvc();
- services.AddJsonApi(opt =>
- {
- opt.BuildContextGraph(builder => {
- builder.AddResource("reports");
- });
- opt.Namespace = "api";
- }, mvcBuilder);
-
- services.AddScoped, ReportService>();
+ var mvcBuilder = services.AddMvcCore();
+ services.AddJsonApi(
+ opt => opt.Namespace = "api",
+ mvcBuilder,
+ discovery => discovery.AddCurrentAssemblyServices());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
diff --git a/src/Examples/ReportsExample/appsettings.json b/src/Examples/ReportsExample/appsettings.json
index 125f7a4ae9..5766595e6d 100644
--- a/src/Examples/ReportsExample/appsettings.json
+++ b/src/Examples/ReportsExample/appsettings.json
@@ -2,7 +2,7 @@
"Logging": {
"IncludeScopes": false,
"LogLevel": {
- "Default": "Information"
+ "Default": "Warning"
}
}
}
diff --git a/src/JsonApiDotNetCore/AssemblyInfo.cs b/src/JsonApiDotNetCore/AssemblyInfo.cs
new file mode 100644
index 0000000000..47e8eee668
--- /dev/null
+++ b/src/JsonApiDotNetCore/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+using System.Runtime.CompilerServices;
+[assembly: InternalsVisibleTo("OperationsExampleTests")]
+[assembly:InternalsVisibleTo("UnitTests")]
+[assembly:InternalsVisibleTo("JsonApiDotNetCoreExampleTests")]
+[assembly:InternalsVisibleTo("NoEntityFrameworkTests")]
+[assembly:InternalsVisibleTo("Benchmarks")]
\ No newline at end of file
diff --git a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs
index e47dec2065..ba71dbce06 100644
--- a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs
+++ b/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs
@@ -1,18 +1,73 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
-using Microsoft.EntityFrameworkCore;
+using JsonApiDotNetCore.Extensions;
+using JsonApiDotNetCore.Graph;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
-using JsonApiDotNetCore.Extensions;
-using System.Linq;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
namespace JsonApiDotNetCore.Builders
{
+ public interface IContextGraphBuilder
+ {
+ ///
+ /// Construct the
+ ///
+ IContextGraph Build();
+
+ ///
+ /// Add a json:api resource
+ ///
+ /// The resource model type
+ /// The pluralized name that should be exposed by the API
+ IContextGraphBuilder AddResource(string pluralizedTypeName) where TResource : class, IIdentifiable;
+
+ ///
+ /// Add a json:api resource
+ ///
+ /// The resource model type
+ /// The resource model identifier type
+ /// The pluralized name that should be exposed by the API
+ IContextGraphBuilder AddResource(string pluralizedTypeName) where TResource : class, IIdentifiable;
+
+ ///
+ /// Add a json:api resource
+ ///
+ /// The resource model type
+ /// The resource model identifier type
+ /// The pluralized name that should be exposed by the API
+ IContextGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName);
+
+ ///
+ /// Add all the models that are part of the provided
+ /// that also implement
+ ///
+ /// The implementation type.
+ IContextGraphBuilder AddDbContext() where T : DbContext;
+
+ ///
+ /// Specify the used to format resource names.
+ ///
+ /// Formatter used to define exposed resource names by convention.
+ IContextGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter);
+
+ ///
+ /// Which links to include. Defaults to .
+ ///
+ Link DocumentLinks { get; set; }
+ }
+
public class ContextGraphBuilder : IContextGraphBuilder
{
private List _entities = new List();
+ private List _validationResults = new List();
+
private bool _usesDbContext;
+ private IResourceNameFormatter _resourceNameFormatter = new DefaultResourceNameFormatter();
+
public Link DocumentLinks { get; set; } = Link.All;
public IContextGraph Build()
@@ -20,29 +75,38 @@ public IContextGraph Build()
// this must be done at build so that call order doesn't matter
_entities.ForEach(e => e.Links = GetLinkFlags(e.EntityType));
- var graph = new ContextGraph()
- {
- Entities = _entities,
- UsesDbContext = _usesDbContext
- };
+ var graph = new ContextGraph(_entities, _usesDbContext, _validationResults);
return graph;
}
- public void AddResource(string pluralizedTypeName) where TResource : class
+ ///
+ public IContextGraphBuilder AddResource(string pluralizedTypeName) where TResource : class, IIdentifiable
+ => AddResource(pluralizedTypeName);
+
+ ///
+ public IContextGraphBuilder AddResource(string pluralizedTypeName) where TResource : class, IIdentifiable
+ => AddResource(typeof(TResource), typeof(TId), pluralizedTypeName);
+
+ ///
+ public IContextGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName)
{
- var entityType = typeof(TResource);
+ AssertEntityIsNotAlreadyDefined(entityType);
- VerifyEntityIsNotAlreadyDefined(entityType);
+ _entities.Add(GetEntity(pluralizedTypeName, entityType, idType));
- _entities.Add(new ContextEntity
- {
- EntityName = pluralizedTypeName,
- EntityType = entityType,
- Attributes = GetAttributes(entityType),
- Relationships = GetRelationships(entityType)
- });
+ return this;
}
+ private ContextEntity GetEntity(string pluralizedTypeName, Type entityType, Type idType) => new ContextEntity
+ {
+ EntityName = pluralizedTypeName,
+ EntityType = entityType,
+ IdentityType = idType,
+ Attributes = GetAttributes(entityType),
+ Relationships = GetRelationships(entityType),
+ ResourceType = GetResourceDefinitionType(entityType)
+ };
+
private Link GetLinkFlags(Type entityType)
{
var attribute = (LinksAttribute)entityType.GetTypeInfo().GetCustomAttribute(typeof(LinksAttribute));
@@ -61,8 +125,12 @@ protected virtual List GetAttributes(Type entityType)
foreach (var prop in properties)
{
var attribute = (AttrAttribute)prop.GetCustomAttribute(typeof(AttrAttribute));
- if (attribute == null) continue;
+ if (attribute == null)
+ continue;
+
attribute.InternalAttributeName = prop.Name;
+ attribute.PropertyInfo = prop;
+
attributes.Add(attribute);
}
return attributes;
@@ -93,7 +161,10 @@ protected virtual Type GetRelationshipType(RelationshipAttribute relation, Prope
return prop.PropertyType;
}
- public void AddDbContext() where T : DbContext
+ private Type GetResourceDefinitionType(Type entityType) => typeof(ResourceDefinition<>).MakeGenericType(entityType);
+
+ ///
+ public IContextGraphBuilder AddDbContext() where T : DbContext
{
_usesDbContext = true;
@@ -110,32 +181,60 @@ public void AddDbContext() where T : DbContext
{
var entityType = dbSetType.GetGenericArguments()[0];
- VerifyEntityIsNotAlreadyDefined(entityType);
+ AssertEntityIsNotAlreadyDefined(entityType);
- _entities.Add(new ContextEntity
- {
- EntityName = GetResourceName(property),
- EntityType = entityType,
- Attributes = GetAttributes(entityType),
- Relationships = GetRelationships(entityType)
- });
+ var (isJsonApiResource, idType) = GetIdType(entityType);
+
+ if (isJsonApiResource)
+ _entities.Add(GetEntity(GetResourceNameFromDbSetProperty(property, entityType), entityType, idType));
}
}
+
+ return this;
}
- private string GetResourceName(PropertyInfo property)
+ private string GetResourceNameFromDbSetProperty(PropertyInfo property, Type resourceType)
{
- var resourceAttribute = property.GetCustomAttribute(typeof(ResourceAttribute));
- if (resourceAttribute == null)
- return property.Name.Dasherize();
+ // this check is actually duplicated in the DefaultResourceNameFormatter
+ // however, we perform it here so that we allow class attributes to be prioritized over
+ // the DbSet attribute. Eventually, the DbSet attribute should be deprecated.
+ //
+ // check the class definition first
+ // [Resource("models"] public class Model : Identifiable { /* ... */ }
+ if (resourceType.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute classResourceAttribute)
+ return classResourceAttribute.ResourceName;
+
+ // check the DbContext member next
+ // [Resource("models")] public DbSet Models { get; set; }
+ if (property.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute resourceAttribute)
+ return resourceAttribute.ResourceName;
+
+ // fallback to dsherized...this should actually check for a custom IResourceNameFormatter
+ return _resourceNameFormatter.FormatResourceName(resourceType);
+ }
+
+ private (bool isJsonApiResource, Type idType) GetIdType(Type resourceType)
+ {
+ var possible = TypeLocator.GetIdType(resourceType);
+ if (possible.isJsonApiResource)
+ return possible;
+
+ _validationResults.Add(new ValidationResult(LogLevel.Warning, $"{resourceType} does not implement 'IIdentifiable<>'. "));
- return ((ResourceAttribute)resourceAttribute).ResourceName;
+ return (false, null);
}
- private void VerifyEntityIsNotAlreadyDefined(Type entityType)
+ private void AssertEntityIsNotAlreadyDefined(Type entityType)
{
if (_entities.Any(e => e.EntityType == entityType))
throw new InvalidOperationException($"Cannot add entity type {entityType} to context graph, there is already an entity of that type configured.");
}
+
+ ///
+ public IContextGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter)
+ {
+ _resourceNameFormatter = resourceNameFormatter;
+ return this;
+ }
}
}
diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
index d8d38390d8..c6f5f999b4 100644
--- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
+++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
@@ -1,6 +1,8 @@
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
+using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;
@@ -12,31 +14,34 @@ public class DocumentBuilder : IDocumentBuilder
private readonly IJsonApiContext _jsonApiContext;
private readonly IContextGraph _contextGraph;
private readonly IRequestMeta _requestMeta;
-
- public DocumentBuilder(IJsonApiContext jsonApiContext)
- {
- _jsonApiContext = jsonApiContext;
- _contextGraph = jsonApiContext.ContextGraph;
- }
-
- public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
+ private readonly DocumentBuilderOptions _documentBuilderOptions;
+ private readonly IScopedServiceProvider _scopedServiceProvider;
+
+ public DocumentBuilder(
+ IJsonApiContext jsonApiContext,
+ IRequestMeta requestMeta = null,
+ IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null,
+ IScopedServiceProvider scopedServiceProvider = null)
{
_jsonApiContext = jsonApiContext;
_contextGraph = jsonApiContext.ContextGraph;
_requestMeta = requestMeta;
+ _documentBuilderOptions = documentBuilderOptionsProvider?.GetDocumentBuilderOptions() ?? new DocumentBuilderOptions();
+ _scopedServiceProvider = scopedServiceProvider;
}
public Document Build(IIdentifiable entity)
{
var contextEntity = _contextGraph.GetContextEntity(entity.GetType());
+ var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition;
var document = new Document
{
- Data = GetData(contextEntity, entity),
+ Data = GetData(contextEntity, entity, resourceDefinition),
Meta = GetMeta(entity)
};
- if(ShouldIncludePageLinks(contextEntity))
+ if (ShouldIncludePageLinks(contextEntity))
document.Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext));
document.Included = AppendIncludedObject(document.Included, contextEntity, entity);
@@ -46,11 +51,9 @@ public Document Build(IIdentifiable entity)
public Documents Build(IEnumerable entities)
{
- var entityType = entities
- .GetType()
- .GenericTypeArguments[0];
-
+ var entityType = entities.GetElementType();
var contextEntity = _contextGraph.GetContextEntity(entityType);
+ var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition;
var enumeratedEntities = entities as IList ?? entities.ToList();
var documents = new Documents
@@ -59,12 +62,12 @@ public Documents Build(IEnumerable entities)
Meta = GetMeta(enumeratedEntities.FirstOrDefault())
};
- if(ShouldIncludePageLinks(contextEntity))
+ if (ShouldIncludePageLinks(contextEntity))
documents.Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext));
foreach (var entity in enumeratedEntities)
{
- documents.Data.Add(GetData(contextEntity, entity));
+ documents.Data.Add(GetData(contextEntity, entity, resourceDefinition));
documents.Included = AppendIncludedObject(documents.Included, contextEntity, entity);
}
@@ -73,21 +76,20 @@ public Documents Build(IEnumerable entities)
private Dictionary GetMeta(IIdentifiable entity)
{
- if (entity == null) return null;
-
var builder = _jsonApiContext.MetaBuilder;
-
- if(entity is IHasMeta metaEntity)
- builder.Add(metaEntity.GetMeta(_jsonApiContext));
-
- if(_jsonApiContext.Options.IncludeTotalRecordCount)
+ if (_jsonApiContext.Options.IncludeTotalRecordCount && _jsonApiContext.PageManager.TotalRecords != null)
builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords);
-
- if(_requestMeta != null)
+
+ if (_requestMeta != null)
builder.Add(_requestMeta.GetMeta());
+ if (entity != null && entity is IHasMeta metaEntity)
+ builder.Add(metaEntity.GetMeta(_jsonApiContext));
+
var meta = builder.Build();
- if(meta.Count > 0) return meta;
+ if (meta.Count > 0)
+ return meta;
+
return null;
}
@@ -104,7 +106,11 @@ private List AppendIncludedObject(List includedObjec
return includedObject;
}
- private DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)
+ [Obsolete("You should specify an IResourceDefinition implementation using the GetData/3 overload.")]
+ public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)
+ => GetData(contextEntity, entity, resourceDefinition: null);
+
+ public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity, IResourceDefinition resourceDefinition = null)
{
var data = new DocumentData
{
@@ -112,15 +118,19 @@ private DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)
Id = entity.StringId
};
- if (_jsonApiContext.IsRelationshipData)
+ if (_jsonApiContext.IsRelationshipPath)
return data;
data.Attributes = new Dictionary();
- contextEntity.Attributes.ForEach(attr =>
+ var resourceAttributes = resourceDefinition?.GetOutputAttrs(entity) ?? contextEntity.Attributes;
+ resourceAttributes.ForEach(attr =>
{
- if(ShouldIncludeAttribute(attr))
- data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
+ var attributeValue = attr.GetValue(entity);
+ if (ShouldIncludeAttribute(attr, attributeValue))
+ {
+ data.Attributes.Add(attr.PublicAttributeName, attributeValue);
+ }
});
if (contextEntity.Relationships.Count > 0)
@@ -129,47 +139,58 @@ private DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)
return data;
}
- private bool ShouldIncludeAttribute(AttrAttribute attr)
+ private bool ShouldIncludeAttribute(AttrAttribute attr, object attributeValue)
{
- return (_jsonApiContext.QuerySet == null
- || _jsonApiContext.QuerySet.Fields.Count == 0
- || _jsonApiContext.QuerySet.Fields.Contains(attr.InternalAttributeName));
+ return OmitNullValuedAttribute(attr, attributeValue) == false
+ && ((_jsonApiContext.QuerySet == null
+ || _jsonApiContext.QuerySet.Fields.Count == 0)
+ || _jsonApiContext.QuerySet.Fields.Contains(attr.InternalAttributeName));
+ }
+
+ private bool OmitNullValuedAttribute(AttrAttribute attr, object attributeValue)
+ {
+ return attributeValue == null && _documentBuilderOptions.OmitNullValuedAttributes;
}
private void AddRelationships(DocumentData data, ContextEntity contextEntity, IIdentifiable entity)
{
data.Relationships = new Dictionary();
+ contextEntity.Relationships.ForEach(r =>
+ data.Relationships.Add(
+ r.PublicRelationshipName,
+ GetRelationshipData(r, contextEntity, entity)
+ )
+ );
+ }
+
+ private RelationshipData GetRelationshipData(RelationshipAttribute attr, ContextEntity contextEntity, IIdentifiable entity)
+ {
var linkBuilder = new LinkBuilder(_jsonApiContext);
- contextEntity.Relationships.ForEach(r =>
- {
- var relationshipData = new RelationshipData();
+ var relationshipData = new RelationshipData();
- if(r.DocumentLinks.HasFlag(Link.None) == false)
- {
- relationshipData.Links = new Links();
- if(r.DocumentLinks.HasFlag(Link.Self))
- relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, r.PublicRelationshipName);
-
- if(r.DocumentLinks.HasFlag(Link.Related))
- relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, r.PublicRelationshipName);
- }
+ if (attr.DocumentLinks.HasFlag(Link.None) == false)
+ {
+ relationshipData.Links = new Links();
+ if (attr.DocumentLinks.HasFlag(Link.Self))
+ relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName);
- if (RelationshipIsIncluded(r.PublicRelationshipName))
- {
- var navigationEntity = _jsonApiContext.ContextGraph
- .GetRelationship(entity, r.InternalRelationshipName);
-
- if(navigationEntity == null)
- relationshipData.SingleData = null;
- else if (navigationEntity is IEnumerable)
- relationshipData.ManyData = GetRelationships((IEnumerable