From de942690102b6ab88dde692143570478abb0780d Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 3 Mar 2021 18:10:45 +0100 Subject: [PATCH 01/17] Added inspectcode/cleanupcode scripts and cibuild validation --- .config/dotnet-tools.json | 18 + .editorconfig | 126 +++++- Build.ps1 | 51 ++- JetBrainsInspectCodeTransform.xslt | 51 +++ JsonApiDotNetCore.sln.DotSettings | 629 +++++++++++++++++++++++++++++ cleanupcode.ps1 | 17 + inspectcode.ps1 | 33 ++ 7 files changed, 910 insertions(+), 15 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100644 JetBrainsInspectCodeTransform.xslt create mode 100644 JsonApiDotNetCore.sln.DotSettings create mode 100644 cleanupcode.ps1 create mode 100644 inspectcode.ps1 diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000000..fb4dc5b46b --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "jetbrains.resharper.globaltools": { + "version": "2020.3.3", + "commands": [ + "jb" + ] + }, + "regitlint": { + "version": "2.1.3", + "commands": [ + "regitlint" + ] + } + } +} diff --git a/.editorconfig b/.editorconfig index 3499a1f7a6..b6d9a8990c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,25 +1,123 @@ -# EditorConfig is awesome: http://EditorConfig.org - -# top-most EditorConfig file +# Remove the line below if you want to inherit .editorconfig settings from higher directories root = true [*] -end_of_line = lf -insert_final_newline = true indent_style = space indent_size = 4 charset = utf-8 +trim_trailing_whitespace = true +end_of_line = lf +insert_final_newline = true -[*.{csproj,props}] +[*.{csproj,json}] indent_size = 2 -[*.{cs,vb}] -dotnet_naming_rule.private_members_with_underscore.symbols = private_fields -dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore -dotnet_naming_rule.private_members_with_underscore.severity = suggestion +[*.{cs}] +#### .NET Coding Conventions #### + +# Organize usings +dotnet_sort_system_directives_first = true + +# this. preferences +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +csharp_style_pattern_local_over_anonymous_function = false:silent + +# Expression-level preferences +dotnet_style_operator_placement_when_wrapping = end_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = non_public:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_constructors = false:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = false:suggestion +csharp_style_expression_bodied_methods = false:suggestion +csharp_style_expression_bodied_operators = false:suggestion +csharp_style_expression_bodied_properties = true:suggestion + +# Code-block preferences +csharp_prefer_braces = true:suggestion + +# Expression-level preferences +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:suggestion + + +#### C# Formatting Rules #### + +# Indentation preferences +csharp_indent_case_contents_when_block = false + +# Wrapping preferences +csharp_preserve_single_line_statements = false + + +#### Naming styles #### + +dotnet_diagnostic.IDE1006.severity = warning + +# Naming rules +dotnet_naming_rule.private_const_fields_should_be_pascal_case.symbols = private_const_fields +dotnet_naming_rule.private_const_fields_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.private_const_fields_should_be_pascal_case.severity = warning + +dotnet_naming_rule.private_static_readonly_fields_should_be_pascal_case.symbols = private_static_readonly_fields +dotnet_naming_rule.private_static_readonly_fields_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.private_static_readonly_fields_should_be_pascal_case.severity = warning + +dotnet_naming_rule.private_static_or_readonly_fields_should_start_with_underscore.symbols = private_static_or_readonly_fields +dotnet_naming_rule.private_static_or_readonly_fields_should_start_with_underscore.style = camel_case_prefix_with_underscore +dotnet_naming_rule.private_static_or_readonly_fields_should_start_with_underscore.severity = warning + +dotnet_naming_rule.locals_and_parameters_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_and_parameters_should_be_camel_case.style = camel_case +dotnet_naming_rule.locals_and_parameters_should_be_camel_case.severity = warning + +dotnet_naming_rule.types_and_members_should_be_pascal_case.symbols = types_and_members +dotnet_naming_rule.types_and_members_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.types_and_members_should_be_pascal_case.severity = warning + +# Symbol specifications +dotnet_naming_symbols.private_const_fields.applicable_kinds = field +dotnet_naming_symbols.private_const_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_const_fields.required_modifiers = const + +dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static,readonly + +dotnet_naming_symbols.private_static_or_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_or_readonly_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_static_or_readonly_fields.required_modifiers = static readonly + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = local,parameter +dotnet_naming_symbols.locals_and_parameters.applicable_accessibilities = * + +dotnet_naming_symbols.types_and_members.applicable_kinds = * +dotnet_naming_symbols.types_and_members.applicable_accessibilities = * + +# Naming styles +dotnet_naming_style.pascal_case.capitalization = pascal_case -dotnet_naming_symbols.private_fields.applicable_kinds = field -dotnet_naming_symbols.private_fields.applicable_accessibilities = private +dotnet_naming_style.camel_case_prefix_with_underscore.required_prefix = _ +dotnet_naming_style.camel_case_prefix_with_underscore.capitalization = camel_case -dotnet_naming_style.prefix_underscore.capitalization = camel_case -dotnet_naming_style.prefix_underscore.required_prefix = _ \ No newline at end of file +dotnet_naming_style.camel_case.capitalization = camel_case diff --git a/Build.ps1 b/Build.ps1 index 25a0bac1f7..e614f84c29 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -17,15 +17,64 @@ function CheckLastExitCode { } } +function RunInspectCode { + $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') + dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal + CheckLastExitCode + + [xml]$xml = Get-Content "$outputPath" + if ($xml.report.Issues -and $xml.report.Issues.Project) { + foreach ($project in $xml.report.Issues.Project) { + if ($project.Issue.Count -gt 0) { + $project.ForEach({ + Write-Output "`nProject $($project.Name)" + $failed = $true + + $_.Issue.ForEach({ + $issueType = $xml.report.IssueTypes.SelectSingleNode("IssueType[@Id='$($_.TypeId)']") + $severity = $_.Severity ?? $issueType.Severity + + Write-Output "[$severity] $($_.File):$($_.Line) $($_.Message)" + }) + }) + } + } + + if ($failed) { + throw "One or more projects failed code inspection."; + } + } +} + +function RunCleanupCode { + # When running in cibuild for a pull request, this reformats only the files changed in the PR and fails if the reformat produces changes. + + if ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT) { + Write-Output "Running code cleanup in cibuild for pull request" + + $sourceCommitHash = $env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT + $targetCommitHash = git rev-parse "$env:APPVEYOR_REPO_BRANCH" + + Write-Output "Source commit hash = $sourceCommitHash" + Write-Output "Target commit hash = $targetCommitHash" + + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --jb --profile --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $sourceCommitHash -b $targetCommitHash --fail-on-diff --print-diff + CheckLastExitCode + } +} + $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 tool restore CheckLastExitCode dotnet build -c Release CheckLastExitCode +RunInspectCode +RunCleanupCode + dotnet test -c Release --no-build CheckLastExitCode diff --git a/JetBrainsInspectCodeTransform.xslt b/JetBrainsInspectCodeTransform.xslt new file mode 100644 index 0000000000..098821f29f --- /dev/null +++ b/JetBrainsInspectCodeTransform.xslt @@ -0,0 +1,51 @@ + + + + + + + + + JetBrains Inspect Code Report + + + + +

JetBrains InspectCode Report

+ + +

+ : +

+ + + + + + + + + + + + + +
FileLine NumberMessage
+ + + + + +
+
+
+
+
+ + +
+
diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings new file mode 100644 index 0000000000..0404af6c5b --- /dev/null +++ b/JsonApiDotNetCore.sln.DotSettings @@ -0,0 +1,629 @@ + + // Use the following placeholders: +// $EXPR$ -- source expression +// $NAME$ -- source name (string literal or 'nameof' expression) +// $MESSAGE$ -- string literal in the form of "$NAME$ != null" +JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$); + 199 + 5000 + 99 + 100 + 200 + 1000 + 500 + 3000 + 50 + False + SOLUTION + True + True + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + WARNING + SUGGESTION + SUGGESTION + WARNING + WARNING + HINT + WARNING + WARNING + SUGGESTION + WARNING + SUGGESTION + SUGGESTION + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + DO_NOT_SHOW + HINT + SUGGESTION + SUGGESTION + WARNING + SUGGESTION + SUGGESTION + DO_NOT_SHOW + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + WARNING + SUGGESTION + SUGGESTION + SUGGESTION + WARNING + WARNING + WARNING + SUGGESTION + WARNING + HINT + WARNING + WARNING + <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile> + JADNC Full Cleanup + Required + Required + Required + Required + Conditional + False + 1 + 1 + 1 + 1 + True + True + True + True + True + True + 1 + 1 + False + False + False + False + False + False + False + False + True + NEVER + NEVER + False + NEVER + False + False + NEVER + False + True + False + True + False + False + CHOP_ALWAYS + True + True + True + WRAP_IF_LONG + 160 + WRAP_IF_LONG + CHOP_ALWAYS + CHOP_ALWAYS + True + True + 2 + True + 2 + False + False + 2 + RemoveIndent + RemoveIndent + False + 8 + OneStep + OnSingleLine + 2 + OnSingleLine + False + 150 + False + 2 + False + True + 1 + OneStep + OnSingleLine + True + 2 + OneStep + OnSingleLine + False + 160 + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="Non-reorderable types" RemoveRegions="All"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + <HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /> + <HasAttribute Name="JetBrains.Annotations.NoReorder" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasMember> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" Inherited="True" /> + </And> + </HasMember> + </And> + </TypePattern.Match> + <Entry DisplayName="Constants"> + <Entry.Match> + <And> + <Kind Is="Constant" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Group DisplayName="Fields"> + <Entry DisplayName="Static Readonly"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Static /> + <Readonly /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Static"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Static /> + <Not> + <Readonly /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Instance Readonly"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <Static /> + </Not> + <Readonly /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Instance"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <And> + <Static /> + <Readonly /> + </And> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + </Group> + <Group DisplayName="Properties"> + <Entry DisplayName="Static"> + <Entry.Match> + <And> + <Kind Is="Property" /> + <Static /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Instance"> + <Entry.Match> + <And> + <Kind Is="Property" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + </Group> + <Entry DisplayName="Indexers"> + <Entry.Match> + <And> + <Kind Is="Indexer" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Group DisplayName="Events"> + <Entry DisplayName="Static"> + <Entry.Match> + <And> + <Kind Is="Event" /> + <Static /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Instance"> + <Entry.Match> + <And> + <Kind Is="Event" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + </Group> + <Entry DisplayName="Setup/Teardown methods"> + <Entry.Match> + <Or> + <Kind Is="Constructor" /> + <And> + <Kind Is="Method" /> + <ImplementsInterface Name="System.IDisposable" /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constructor" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Test methods" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="Xunit.FactAttribute" /> + <HasAttribute Name="Xunit.TheoryAttribute" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Other methods" Priority="100"> + <Entry.Match> + <Kind Is="Method" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Operators"> + <Entry.Match> + <Kind Is="Operator" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Destructor"> + <Entry.Match> + <And> + <Kind Is="Destructor" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Nested types"> + <Entry.Match> + <Kind Is="Type" /> + </Entry.Match> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern" RemoveRegions="All"> + <Entry DisplayName="Constants"> + <Entry.Match> + <And> + <Kind Is="Constant" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Group DisplayName="Fields"> + <Entry DisplayName="Static Readonly"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Static /> + <Readonly /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Static"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Static /> + <Not> + <Readonly /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Instance Readonly"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <Static /> + </Not> + <Readonly /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Instance"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <And> + <Static /> + <Readonly /> + </And> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + </Group> + <Group DisplayName="Properties"> + <Entry DisplayName="Static"> + <Entry.Match> + <And> + <Kind Is="Property" /> + <Static /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Instance"> + <Entry.Match> + <And> + <Kind Is="Property" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + </Group> + <Entry DisplayName="Indexers"> + <Entry.Match> + <And> + <Kind Is="Indexer" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Group DisplayName="Events"> + <Entry DisplayName="Static"> + <Entry.Match> + <And> + <Kind Is="Event" /> + <Static /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Instance"> + <Entry.Match> + <And> + <Kind Is="Event" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + </Group> + <Group DisplayName="Constructors"> + <Entry DisplayName="Static"> + <Entry.Match> + <And> + <Kind Is="Constructor" /> + <Static /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Instance"> + <Entry.Match> + <And> + <Kind Is="Constructor" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Access /> + </Entry.SortBy> + </Entry> + </Group> + <Entry DisplayName="Methods"> + <Entry.Match> + <Kind Is="Method" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Operators"> + <Entry.Match> + <Kind Is="Operator" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Destructor"> + <Entry.Match> + <And> + <Kind Is="Destructor" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Private Internal Protected ProtectedInternal Public" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Nested types"> + <Entry.Match> + <Kind Is="Type" /> + </Entry.Match> + </Entry> + </TypePattern> +</Patterns> + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident + True + False + False + False + False + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + True + True + True + True + True + True + Replace argument null check using throw expression with Guard clause + True + True + False + + IdentifierPlaceholder + True + True + False + + IdentifierPlaceholder + True + True + False + + IdentifierPlaceholder + True + CSHARP + False + Replace argument null check with Guard clause + JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$)); +$left$ = $right$; + $left$ = $right$ ?? throw new ArgumentNullException(nameof($argument$)); + SUGGESTION + True + Replace classic argument null check with Guard clause + True + True + False + + IdentifierPlaceholder + True + CSHARP + False + Replace argument null check with Guard clause + JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$)); + if ($argument$ == null) throw new ArgumentNullException(nameof($argument$)); + SUGGESTION + True + Replace collection null/empty check with extension method + True + False + + ExpressionPlaceholder + True + CSHARP + False + Replace collection null/empty check with extension method + $collection$.IsNullOrEmpty() + $collection$ == null || !$collection$.Any() + SUGGESTION + True + True + True + True + True + True + True + True + diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 new file mode 100644 index 0000000000..605ebff705 --- /dev/null +++ b/cleanupcode.ps1 @@ -0,0 +1,17 @@ +#Requires -Version 7.0 + +# This script reformats the entire codebase to make it compliant with our coding guidelines. + +dotnet tool restore + +if ($LASTEXITCODE -ne 0) { + throw "Tool restore failed with exit code $LASTEXITCODE" +} + +dotnet build -c Release + +if ($LASTEXITCODE -ne 0) { + throw "Build failed with exit code $LASTEXITCODE" +} + +dotnet regitlint -s JsonApiDotNetCore.sln --print-command --jb --profile --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN diff --git a/inspectcode.ps1 b/inspectcode.ps1 new file mode 100644 index 0000000000..83987d5e69 --- /dev/null +++ b/inspectcode.ps1 @@ -0,0 +1,33 @@ +#Requires -Version 7.0 + +# This script runs code inspection and opens the results in a web browser. + +dotnet tool restore + +if ($LASTEXITCODE -ne 0) { + throw "Tool restore failed with exit code $LASTEXITCODE" +} + +dotnet build -c Release + +if ($LASTEXITCODE -ne 0) { + throw "Build failed with exit code $LASTEXITCODE" +} + +$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') +$resultPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.html') +dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal + +if ($LASTEXITCODE -ne 0) { + throw "Code inspection failed with exit code $LASTEXITCODE" +} + +[xml]$xml = Get-Content "$outputPath" +if ($xml.report.Issues -and $xml.report.Issues.Project) { + $xslt = new-object System.Xml.Xsl.XslCompiledTransform; + $xslt.Load("$pwd/JetBrainsInspectCodeTransform.xslt"); + $xslt.Transform($outputPath, $resultPath); + + Write-Output "Opening results in browser" + Invoke-Item "$resultPath" +} From 2092ad516fd141cb0d392912bd03c5bdae532df0 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 3 Mar 2021 21:00:19 +0100 Subject: [PATCH 02/17] Added missing formatting directives to HostingFakers --- .../IntegrationTests/HostingInIIS/HostingFakers.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingFakers.cs index 428c27ad69..0bbe24f429 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingFakers.cs @@ -2,6 +2,9 @@ using Bogus; using TestBuildingBlocks; +// @formatter:wrap_chained_method_calls chop_always +// @formatter:keep_existing_linebreaks true + namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS { internal sealed class HostingFakers : FakerContainer From 30b46f63d860acbf4788cbceb01fbe87ff948d8b Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 3 Mar 2021 20:57:40 +0100 Subject: [PATCH 03/17] Auto-formatted codebase using Resharper --- benchmarks/BenchmarkResource.cs | 2 +- benchmarks/BenchmarkResourcePublicNames.cs | 2 +- benchmarks/DependencyFactory.cs | 2 +- ...nkBuilderGetNamespaceFromPathBenchmarks.cs | 17 +- benchmarks/Program.cs | 1 + benchmarks/Query/QueryParserBenchmarks.cs | 39 ++- .../JsonApiDeserializerBenchmarks.cs | 5 +- .../JsonApiSerializerBenchmarks.cs | 13 +- .../Controllers/PeopleController.cs | 8 +- src/Examples/GettingStarted/Program.cs | 13 +- .../Properties/launchSettings.json | 50 ++-- src/Examples/GettingStarted/Startup.cs | 24 +- src/Examples/GettingStarted/appsettings.json | 16 +- .../Controllers/ArticlesController.cs | 8 +- .../Controllers/AuthorsController.cs | 8 +- .../Controllers/NonJsonApiController.cs | 12 +- .../Controllers/OperationsController.cs | 4 +- .../Controllers/PeopleController.cs | 8 +- .../Controllers/TodoItemsController.cs | 8 +- .../Controllers/UsersController.cs | 8 +- .../Data/AppDbContext.cs | 15 +- .../Definitions/ArticleHooksDefinition.cs | 6 +- .../Definitions/LockableHooksDefinition.cs | 9 +- .../Definitions/PassportHooksDefinition.cs | 3 +- .../Definitions/PersonHooksDefinition.cs | 8 +- .../Definitions/TagHooksDefinition.cs | 5 +- .../Definitions/TodoItemHooksDefinition.cs | 5 +- .../Models/Article.cs | 2 + .../Models/IdentifiableArticleTag.cs | 2 + .../JsonApiDotNetCoreExample/Program.cs | 13 +- .../Properties/launchSettings.json | 52 ++-- .../Startups/EmptyStartup.cs | 4 +- .../Startups/Startup.cs | 2 +- .../JsonApiDotNetCoreExample/appsettings.json | 22 +- .../Controllers/ResourceAsController.cs | 3 +- .../Controllers/ResourceBsController.cs | 3 +- src/Examples/MultiDbContextExample/Program.cs | 3 +- .../Properties/launchSettings.json | 52 ++-- .../Repositories/DbContextARepository.cs | 5 +- .../Repositories/DbContextBRepository.cs | 5 +- src/Examples/MultiDbContextExample/Startup.cs | 9 +- .../MultiDbContextExample/appsettings.json | 16 +- .../Controllers/WorkItemsController.cs | 8 +- .../NoEntityFrameworkExample/Program.cs | 13 +- .../Properties/launchSettings.json | 52 ++-- .../Services/WorkItemService.cs | 27 +- .../NoEntityFrameworkExample/Startup.cs | 8 +- .../NoEntityFrameworkExample/appsettings.json | 22 +- .../Controllers/ReportsController.cs | 10 +- src/Examples/ReportsExample/Program.cs | 13 +- .../Properties/launchSettings.json | 52 ++-- .../ReportsExample/Services/ReportService.cs | 2 +- src/Examples/ReportsExample/Startup.cs | 6 +- src/Examples/ReportsExample/appsettings.json | 16 +- .../EntityFrameworkCoreTransactionFactory.cs | 8 +- .../IOperationProcessorAccessor.cs | 4 +- .../AtomicOperations/LocalIdTracker.cs | 4 +- .../AtomicOperations/LocalIdValidator.cs | 14 +- .../OperationProcessorAccessor.cs | 13 +- .../AtomicOperations/OperationsProcessor.cs | 27 +- .../Processors/AddToRelationshipProcessor.cs | 11 +- .../Processors/CreateProcessor.cs | 12 +- .../Processors/DeleteProcessor.cs | 5 +- .../Processors/IAddToRelationshipProcessor.cs | 8 +- .../Processors/ICreateProcessor.cs | 8 +- .../Processors/IDeleteProcessor.cs | 8 +- .../Processors/ISetRelationshipProcessor.cs | 8 +- .../Processors/IUpdateProcessor.cs | 12 +- .../RemoveFromRelationshipProcessor.cs | 11 +- .../Processors/SetRelationshipProcessor.cs | 15 +- .../Processors/UpdateProcessor.cs | 7 +- .../ApplicationBuilderExtensions.cs | 12 +- .../Configuration/GenericServiceFactory.cs | 2 +- .../Configuration/IGenericServiceFactory.cs | 8 +- .../IInverseNavigationResolver.cs | 13 +- .../Configuration/IJsonApiOptions.cs | 93 +++--- .../IRequestScopedServiceProvider.cs | 9 +- .../Configuration/IResourceContextProvider.cs | 5 +- .../Configuration/IResourceGraph.cs | 75 +++-- .../InverseNavigationResolver.cs | 8 +- .../JsonApiApplicationBuilder.cs | 47 ++- .../JsonApiModelMetadataProvider.cs | 5 +- .../Configuration/JsonApiOptions.cs | 8 +- .../Configuration/JsonApiValidationFilter.cs | 4 +- .../RequestScopedServiceProvider.cs | 3 +- .../Configuration/ResourceContext.cs | 36 +-- .../ResourceDescriptorAssemblyCache.cs | 12 +- .../Configuration/ResourceGraph.cs | 54 ++-- .../Configuration/ResourceGraphBuilder.cs | 153 ++++++---- .../ServiceCollectionExtensions.cs | 60 ++-- .../Configuration/ServiceDiscoveryFacade.cs | 53 ++-- .../Configuration/TypeLocator.cs | 68 +++-- .../DisableQueryStringAttribute.cs | 13 +- .../DisableRoutingConventionAttribute.cs | 3 +- .../Annotations/HttpReadOnlyAttribute.cs | 7 +- .../Annotations/HttpRestrictAttribute.cs | 6 +- .../Annotations/NoHttpDeleteAttribute.cs | 5 +- .../Annotations/NoHttpPatchAttribute.cs | 5 +- .../Annotations/NoHttpPostAttribute.cs | 5 +- .../Controllers/BaseJsonApiController.cs | 261 +++++++++------- .../BaseJsonApiOperationsController.cs | 49 +-- .../Controllers/CoreJsonApiController.cs | 2 +- .../Controllers/JsonApiCommandController.cs | 47 +-- .../Controllers/JsonApiController.cs | 94 +++--- .../JsonApiOperationsController.cs | 12 +- .../Controllers/JsonApiQueryController.cs | 36 +-- ...annotClearRequiredRelationshipException.cs | 14 +- .../Errors/InvalidConfigurationException.cs | 6 +- .../Errors/InvalidModelStateException.cs | 35 +-- .../Errors/InvalidQueryException.cs | 2 +- .../InvalidQueryStringParameterException.cs | 3 +- .../Errors/InvalidRequestBodyException.cs | 4 +- .../Errors/JsonApiException.cs | 6 +- .../MissingTransactionSupportException.cs | 6 +- .../Errors/NonSharedTransactionException.cs | 6 +- .../Errors/RelationshipNotFoundException.cs | 11 +- ...ceIdInCreateResourceNotAllowedException.cs | 4 +- .../Errors/ResourceIdMismatchException.cs | 3 +- .../Errors/ResourceTypeMismatchException.cs | 12 +- ...sourcesInRelationshipsNotFoundException.cs | 3 +- .../UnsuccessfulActionResultException.cs | 14 +- .../Internal/Discovery/HooksDiscovery.cs | 32 +- .../Internal/Discovery/IHooksDiscovery.cs | 11 +- .../Execution/DiffableResourceHashSet.cs | 37 ++- .../Internal/Execution/HookExecutorHelper.cs | 90 +++--- .../Execution/IByAffectedRelationships.cs | 6 +- .../Execution/IDiffableResourceHashSet.cs | 17 +- .../Internal/Execution/IHookExecutorHelper.cs | 61 ++-- .../Execution/IRelationshipGetters.cs | 15 +- .../Execution/IRelationshipsDictionary.cs | 14 +- .../Internal/Execution/IResourceHashSet.cs | 13 +- .../Execution/RelationshipsDictionary.cs | 32 +- .../Internal/Execution/ResourceDiffPair.cs | 19 +- .../Internal/Execution/ResourceHashSet.cs | 30 +- .../Hooks/Internal/Execution/ResourceHook.cs | 2 - .../Internal/Execution/ResourcePipeline.cs | 6 +- .../Hooks/Internal/ICreateHookContainer.cs | 56 ++-- .../Hooks/Internal/ICreateHookExecutor.cs | 55 ++-- .../Hooks/Internal/IDeleteHookContainer.cs | 51 ++-- .../Hooks/Internal/IDeleteHookExecutor.cs | 57 ++-- .../Hooks/Internal/IOnReturnHookContainer.cs | 27 +- .../Hooks/Internal/IOnReturnHookExecutor.cs | 25 +- .../Hooks/Internal/IReadHookContainer.cs | 38 ++- .../Hooks/Internal/IReadHookExecutor.cs | 43 ++- .../Hooks/Internal/IResourceHookContainer.cs | 16 +- .../Hooks/Internal/IResourceHookExecutor.cs | 16 +- .../Hooks/Internal/IUpdateHookContainer.cs | 137 +++++---- .../Hooks/Internal/IUpdateHookExecutor.cs | 64 ++-- .../NeverResourceHookExecutorFacade.cs | 2 +- .../Hooks/Internal/ResourceHookExecutor.cs | 269 ++++++++++------- .../Internal/ResourceHookExecutorFacade.cs | 6 +- .../Hooks/Internal/Traversal/ChildNode.cs | 25 +- .../Internal/Traversal/IRelationshipGroup.cs | 2 +- .../IRelationshipsFromPreviousLayer.cs | 11 +- .../Hooks/Internal/Traversal/IResourceNode.cs | 15 +- .../Internal/Traversal/ITraversalHelper.cs | 22 +- .../Hooks/Internal/Traversal/NodeLayer.cs | 14 +- .../Internal/Traversal/RelationshipGroup.cs | 4 +- .../Internal/Traversal/RelationshipProxy.cs | 59 ++-- .../RelationshipsFromPreviousLayer.cs | 3 +- .../Hooks/Internal/Traversal/RootNode.cs | 43 ++- .../Internal/Traversal/TraversalHelper.cs | 247 +++++++++------- .../AsyncConvertEmptyActionResultFilter.cs | 5 +- .../Middleware/AsyncJsonApiExceptionFilter.cs | 5 +- .../AsyncQueryStringActionFilter.cs | 2 +- .../Middleware/ExceptionHandler.cs | 32 +- .../IAsyncConvertEmptyActionResultFilter.cs | 8 +- .../IAsyncJsonApiExceptionFilter.cs | 6 +- .../IAsyncQueryStringActionFilter.cs | 4 +- .../Middleware/IJsonApiInputFormatter.cs | 4 +- .../Middleware/IJsonApiOutputFormatter.cs | 4 +- .../Middleware/IJsonApiRequest.cs | 18 +- .../Middleware/IJsonApiRoutingConvention.cs | 7 +- .../Middleware/JsonApiMiddleware.cs | 87 +++--- .../Middleware/JsonApiRequest.cs | 14 +- .../Middleware/JsonApiRoutingConvention.cs | 65 ++-- .../Middleware/TraceLogWriter.cs | 11 +- src/JsonApiDotNetCore/ObjectExtensions.cs | 10 +- .../Properties/AssemblyInfo.cs | 8 +- .../Properties/launchSettings.json | 2 +- .../Queries/ExpressionInScope.cs | 2 +- .../CollectionNotEmptyExpression.cs | 2 +- .../Expressions/ComparisonExpression.cs | 2 +- .../Queries/Expressions/CountExpression.cs | 2 +- .../Expressions/EqualsAnyOfExpression.cs | 7 +- .../Expressions/IncludeChainConverter.cs | 23 +- .../Expressions/IncludeElementExpression.cs | 8 +- .../Queries/Expressions/IncludeExpression.cs | 19 +- .../Expressions/LiteralConstantExpression.cs | 2 +- .../Queries/Expressions/LogicalExpression.cs | 4 +- .../Expressions/MatchTextExpression.cs | 5 +- .../Queries/Expressions/NotExpression.cs | 2 +- ...nationElementQueryStringValueExpression.cs | 4 +- .../Expressions/PaginationExpression.cs | 4 +- .../PaginationQueryStringValueExpression.cs | 10 +- .../Queries/Expressions/QueryExpression.cs | 4 +- .../Expressions/QueryExpressionRewriter.cs | 34 +-- .../Expressions/QueryExpressionVisitor.cs | 2 +- .../QueryStringParameterScopeExpression.cs | 2 +- .../Expressions/QueryableHandlerExpression.cs | 6 +- .../ResourceFieldChainExpression.cs | 7 +- .../Expressions/SortElementExpression.cs | 4 +- .../Queries/Expressions/SortExpression.cs | 4 +- .../Expressions/SparseFieldSetExpression.cs | 4 +- .../SparseFieldSetExpressionExtensions.cs | 13 +- .../Expressions/SparseFieldTableExpression.cs | 6 +- .../Queries/IPaginationContext.cs | 18 +- .../Queries/IQueryLayerComposer.cs | 13 +- .../Parsing/FieldChainRequirements.cs | 14 +- .../Queries/Internal/Parsing/FilterParser.cs | 14 +- .../Queries/Internal/Parsing/IncludeParser.cs | 13 +- .../Internal/Parsing/PaginationParser.cs | 13 +- .../Internal/Parsing/QueryExpressionParser.cs | 10 +- .../Internal/Parsing/QueryParseException.cs | 3 +- .../QueryStringParameterScopeParser.cs | 4 +- .../Internal/Parsing/QueryTokenizer.cs | 4 +- .../Parsing/ResourceFieldChainResolver.cs | 58 ++-- .../Queries/Internal/Parsing/SortParser.cs | 5 +- .../Internal/Parsing/SparseFieldSetParser.cs | 7 +- .../Internal/Parsing/SparseFieldTypeParser.cs | 7 +- .../Queries/Internal/QueryLayerComposer.cs | 132 ++++----- .../QueryableBuilding/IncludeClauseBuilder.cs | 14 +- .../QueryableBuilding/OrderClauseBuilder.cs | 14 +- .../QueryableBuilding/QueryClauseBuilder.cs | 27 +- .../QueryableBuilding/QueryableBuilder.cs | 15 +- .../QueryableBuilding/SelectClauseBuilder.cs | 45 +-- .../SkipTakeClauseBuilder.cs | 2 +- .../QueryableBuilding/WhereClauseBuilder.cs | 23 +- .../Queries/Internal/SparseFieldSetCache.cs | 33 ++- .../Queries/PaginationContext.cs | 5 +- src/JsonApiDotNetCore/Queries/QueryLayer.cs | 5 +- .../IQueryStringParameterReader.cs | 2 +- .../QueryStrings/IQueryStringReader.cs | 2 +- ...ourceDefinitionQueryableParameterReader.cs | 4 +- .../DefaultsQueryStringParameterReader.cs | 7 +- .../FilterQueryStringParameterReader.cs | 22 +- .../IncludeQueryStringParameterReader.cs | 11 +- .../Internal/LegacyFilterNotationConverter.cs | 18 +- .../NullsQueryStringParameterReader.cs | 7 +- .../PaginationQueryStringParameterReader.cs | 26 +- .../Internal/QueryStringParameterReader.cs | 5 +- .../Internal/QueryStringReader.cs | 14 +- ...ourceDefinitionQueryableParameterReader.cs | 12 +- .../SortQueryStringParameterReader.cs | 7 +- ...parseFieldSetQueryStringParameterReader.cs | 10 +- .../StandardQueryStringParameters.cs | 2 +- .../Repositories/DbContextExtensions.cs | 12 +- .../Repositories/DbContextResolver.cs | 12 +- .../EntityFrameworkCoreRepository.cs | 157 ++++++---- .../Repositories/IDbContextResolver.cs | 2 +- .../IRepositorySupportsTransaction.cs | 2 +- .../Repositories/IResourceReadRepository.cs | 16 +- .../Repositories/IResourceRepository.cs | 15 +- .../IResourceRepositoryAccessor.cs | 27 +- .../Repositories/IResourceWriteRepository.cs | 18 +- .../MemoryLeakDetectionBugRewriter.cs | 16 +- .../PlaceholderResourceCollector.cs | 18 +- .../ResourceRepositoryAccessor.cs | 32 +- .../Resources/Annotations/AttrAttribute.cs | 14 +- .../Resources/Annotations/AttrCapabilities.cs | 17 +- .../Resources/Annotations/HasManyAttribute.cs | 3 +- .../Annotations/HasManyThroughAttribute.cs | 95 +++--- .../Annotations/RelationshipAttribute.cs | 28 +- .../Annotations/ResourceAttribute.cs | 4 +- .../Annotations/ResourceFieldAttribute.cs | 10 +- .../Annotations/ResourceLinksAttribute.cs | 19 +- .../Resources/IIdentifiable.cs | 8 +- .../Resources/IResourceChangeTracker.cs | 11 +- .../Resources/IResourceDefinition.cs | 58 ++-- .../Resources/IResourceDefinitionAccessor.cs | 18 +- .../Resources/Identifiable.cs | 9 +- .../Resources/IdentifiableComparer.cs | 4 +- .../Resources/IdentifiableExtensions.cs | 2 +- .../Resources/JsonApiResourceDefinition.cs | 23 +- .../Resources/OperationContainer.cs | 11 +- .../Resources/QueryStringParameterHandlers.cs | 4 +- .../Resources/ResourceChangeTracker.cs | 27 +- .../Resources/ResourceDefinitionAccessor.cs | 14 +- .../Resources/ResourceFactory.cs | 22 +- .../Resources/ResourceHooksDefinition.cs | 137 ++++++--- .../AtomicOperationsResponseSerializer.cs | 19 +- .../Serialization/BaseDeserializer.cs | 134 +++++---- .../Serialization/BaseSerializer.cs | 63 ++-- .../IIncludedResourceObjectBuilder.cs | 4 +- .../Serialization/Building/ILinkBuilder.cs | 2 +- .../Serialization/Building/IMetaBuilder.cs | 4 +- .../Building/IResourceObjectBuilder.cs | 25 +- .../IResourceObjectBuilderSettingsProvider.cs | 2 +- .../Building/IncludedResourceObjectBuilder.cs | 77 ++--- .../Serialization/Building/LinkBuilder.cs | 72 ++--- .../Serialization/Building/MetaBuilder.cs | 7 +- .../Building/ResourceObjectBuilder.cs | 68 +++-- .../Building/ResourceObjectBuilderSettings.cs | 6 +- .../ResourceObjectBuilderSettingsProvider.cs | 4 +- .../Building/ResponseResourceObjectBuilder.cs | 40 ++- .../Internal/DeserializedResponseBase.cs | 5 +- .../Client/Internal/IRequestSerializer.cs | 33 ++- .../Client/Internal/IResponseDeserializer.cs | 27 +- .../Client/Internal/ManyResponse.cs | 7 +- .../Client/Internal/RequestSerializer.cs | 34 +-- .../Client/Internal/ResponseDeserializer.cs | 62 ++-- .../Client/Internal/SingleResponse.cs | 11 +- .../Serialization/FieldsToSerialize.cs | 20 +- .../Serialization/IFieldsToSerialize.cs | 9 +- .../Serialization/IJsonApiDeserializer.cs | 8 +- .../Serialization/IJsonApiReader.cs | 3 +- .../Serialization/IJsonApiSerializer.cs | 8 +- .../Serialization/IResponseMeta.cs | 4 +- .../Serialization/JsonApiReader.cs | 39 ++- .../JsonApiSerializationException.cs | 3 +- .../Serialization/JsonApiWriter.cs | 24 +- .../Serialization/Objects/Error.cs | 36 ++- .../Serialization/Objects/ErrorDocument.cs | 7 +- .../Serialization/Objects/ErrorMeta.cs | 3 +- .../Serialization/Objects/ErrorSource.cs | 3 +- .../Serialization/Objects/ExposableData.cs | 60 ++-- .../Serialization/Objects/TopLevelLinks.cs | 41 ++- .../Serialization/RequestDeserializer.cs | 202 +++++-------- .../Serialization/ResponseSerializer.cs | 64 ++-- .../ResponseSerializerFactory.cs | 13 +- .../Services/AsyncCollectionExtensions.cs | 4 +- .../Services/IAddToRelationshipService.cs | 22 +- .../Services/ICreateService.cs | 3 +- .../Services/IDeleteService.cs | 3 +- .../Services/IGetAllService.cs | 3 +- .../Services/IGetByIdService.cs | 3 +- .../Services/IGetRelationshipService.cs | 3 +- .../Services/IGetSecondaryService.cs | 6 +- .../IRemoveFromRelationshipService.cs | 22 +- .../Services/IResourceCommandService.cs | 39 ++- .../Services/IResourceQueryService.cs | 34 ++- .../Services/IResourceService.cs | 24 +- .../Services/ISetRelationshipService.cs | 19 +- .../Services/IUpdateService.cs | 7 +- .../Services/JsonApiResourceService.cs | 192 +++++++----- src/JsonApiDotNetCore/TypeHelper.cs | 116 +++++--- .../ServiceDiscoveryFacadeTests.cs | 67 ++--- .../ExampleIntegrationTestContext.cs | 8 +- ...micConstrainedOperationsControllerTests.cs | 26 +- .../CreateMusicTrackOperationsController.cs | 7 +- .../Creating/AtomicCreateResourceTests.cs | 116 ++++---- ...reateResourceWithClientGeneratedIdTests.cs | 37 +-- ...eateResourceWithToManyRelationshipTests.cs | 78 ++--- ...reateResourceWithToOneRelationshipTests.cs | 81 ++--- .../Deleting/AtomicDeleteResourceTests.cs | 84 +++--- .../Links/AtomicAbsoluteLinksTests.cs | 15 +- .../AtomicRelativeLinksWithNamespaceTests.cs | 7 +- .../LocalIds/AtomicLocalIdTests.cs | 279 +++++++++--------- .../AtomicOperations/Lyric.cs | 2 +- .../Meta/AtomicResourceMetaTests.cs | 16 +- .../Meta/AtomicResponseMeta.cs | 2 +- .../Meta/AtomicResponseMetaTests.cs | 16 +- .../Meta/MusicTrackMetaDefinition.cs | 3 +- .../Meta/TextLanguageMetaDefinition.cs | 3 +- .../Mixed/AtomicRequestBodyTests.cs | 33 ++- .../Mixed/MaximumOperationsPerRequestTests.cs | 16 +- .../AtomicModelStateValidationTests.cs | 72 ++--- .../AtomicOperations/MusicTrack.cs | 4 +- .../AtomicOperations/MusicTracksController.cs | 3 +- .../AtomicOperations/OperationsDbContext.cs | 6 +- .../QueryStrings/AtomicQueryStringTests.cs | 63 ++-- .../MusicTrackReleaseDefinition.cs | 6 +- ...icSparseFieldSetResourceDefinitionTests.cs | 12 +- .../Transactions/AtomicRollbackTests.cs | 20 +- .../AtomicTransactionConsistencyTests.cs | 13 +- .../Transactions/LyricRepository.cs | 5 +- .../Transactions/MusicTrackRepository.cs | 5 +- .../Transactions/PerformerRepository.cs | 3 +- .../AtomicAddToToManyRelationshipTests.cs | 110 ++++--- ...AtomicRemoveFromToManyRelationshipTests.cs | 104 ++++--- .../AtomicReplaceToManyRelationshipTests.cs | 127 ++++---- .../AtomicUpdateToOneRelationshipTests.cs | 180 +++++------ .../AtomicReplaceToManyRelationshipTests.cs | 83 +++--- .../Resources/AtomicUpdateResourceTests.cs | 194 ++++++------ .../AtomicUpdateToOneRelationshipTests.cs | 138 ++++----- .../IntegrationTests/CompositeKeys/Car.cs | 3 +- .../CompositeKeys/CarExpressionRewriter.cs | 55 ++-- .../CompositeKeys/CarRepository.cs | 7 +- .../CompositeKeys/CarsController.cs | 3 +- .../CompositeKeys/CompositeDbContext.cs | 6 +- .../CompositeKeys/CompositeKeyTests.cs | 83 +++--- .../CompositeKeys/Dealership.cs | 2 +- .../CompositeKeys/DealershipsController.cs | 3 +- .../IntegrationTests/CompositeKeys/Engine.cs | 2 +- .../CompositeKeys/EnginesController.cs | 3 +- .../ContentNegotiation/AcceptHeaderTests.cs | 41 +-- .../ContentTypeHeaderTests.cs | 47 +-- .../ContentNegotiation/PoliciesController.cs | 3 +- .../ActionResultTests.cs | 42 +-- .../BaseToothbrushesController.cs | 4 +- .../ToothbrushesController.cs | 3 +- .../ApiControllerAttributeTests.cs | 8 +- .../CustomRoutes/CiviliansController.cs | 3 +- .../CustomRoutes/CustomRouteTests.cs | 15 +- .../IntegrationTests/CustomRoutes/Town.cs | 2 +- .../CustomRoutes/TownsController.cs | 10 +- .../IntegrationTests/EagerLoading/Building.cs | 2 +- .../EagerLoading/BuildingRepository.cs | 7 +- .../EagerLoading/BuildingsController.cs | 3 +- .../EagerLoading/EagerLoadingTests.cs | 62 ++-- .../EagerLoading/StatesController.cs | 3 +- .../EagerLoading/StreetsController.cs | 3 +- .../ConsumerArticleService.cs | 13 +- .../ConsumerArticlesController.cs | 3 +- .../ExceptionHandlerTests.cs | 19 +- .../ThrowingArticlesController.cs | 3 +- .../HostingInIIS/ArtGalleriesController.cs | 3 +- .../HostingInIIS/HostingTests.cs | 12 +- .../HostingInIIS/PaintingsController.cs | 6 +- .../IdObfuscation/BankAccountsController.cs | 3 +- .../IdObfuscation/DebitCardsController.cs | 3 +- .../IdObfuscation/HexadecimalCodec.cs | 7 +- .../IdObfuscation/IdObfuscationTests.cs | 118 ++++---- .../ObfuscatedIdentifiableController.cs | 15 +- .../Links/AbsoluteLinksWithNamespaceTests.cs | 49 +-- .../AbsoluteLinksWithoutNamespaceTests.cs | 49 +-- .../Links/LinkInclusionTests.cs | 10 +- .../Links/PhotoAlbumsController.cs | 3 +- .../Links/PhotoLocationsController.cs | 3 +- .../Links/PhotosController.cs | 3 +- .../Links/RelativeLinksWithNamespaceTests.cs | 49 +-- .../RelativeLinksWithoutNamespaceTests.cs | 49 +-- .../Logging/AuditEntriesController.cs | 3 +- .../IntegrationTests/Logging/LoggingTests.cs | 14 +- .../Meta/ProductFamiliesController.cs | 3 +- .../Meta/ResourceMetaTests.cs | 15 +- .../Meta/ResponseMetaTests.cs | 8 +- .../Meta/SupportTicketDefinition.cs | 5 +- .../Meta/SupportTicketsController.cs | 3 +- .../Meta/TopLevelCountTests.cs | 24 +- .../ModelStateDbContext.cs | 3 +- .../ModelStateValidationTests.cs | 68 ++--- .../NoModelStateValidationTests.cs | 5 +- .../SystemDirectoriesController.cs | 3 +- .../ModelStateValidation/SystemDirectory.cs | 6 +- .../SystemFilesController.cs | 3 +- .../DivingBoardsController.cs | 3 +- .../NamingConventions/KebabCasingTests.cs | 34 +-- .../SwimmingPoolsController.cs | 3 +- .../NonJsonApiControllerTests.cs | 24 +- .../IntegrationTests/QueryStrings/Blog.cs | 2 +- .../IntegrationTests/QueryStrings/BlogPost.cs | 1 + .../QueryStrings/BlogPostsController.cs | 3 +- .../QueryStrings/BlogsController.cs | 3 +- .../QueryStrings/CalendarsController.cs | 3 +- .../QueryStrings/CommentsController.cs | 3 +- .../Filtering/FilterDataTypeTests.cs | 78 +++-- .../QueryStrings/Filtering/FilterDbContext.cs | 3 +- .../Filtering/FilterDepthTests.cs | 81 ++--- .../Filtering/FilterOperatorTests.cs | 58 ++-- .../QueryStrings/Filtering/FilterTests.cs | 25 +- .../Filtering/FilterableResource.cs | 87 ++++-- .../FilterableResourcesController.cs | 3 +- .../QueryStrings/Includes/IncludeTests.cs | 126 ++++---- .../IntegrationTests/QueryStrings/Label.cs | 1 + .../PaginationWithTotalCountTests.cs | 111 +++---- .../PaginationWithoutTotalCountTests.cs | 30 +- .../Pagination/RangeValidationTests.cs | 32 +- .../RangeValidationWithMaximumTests.cs | 38 +-- .../QueryStrings/QueryStringDbContext.cs | 9 +- .../QueryStrings/QueryStringFakers.cs | 2 +- .../QueryStrings/QueryStringTests.cs | 24 +- .../SerializerDefaultValueHandlingTests.cs | 15 +- .../SerializerNullValueHandlingTests.cs | 15 +- .../QueryStrings/Sorting/SortTests.cs | 91 +++--- .../ResultCapturingRepository.cs | 11 +- .../SparseFieldSets/SparseFieldSetTests.cs | 121 ++++---- .../QueryStrings/WebAccountsController.cs | 3 +- .../ReadWrite/Creating/CreateResourceTests.cs | 116 ++++---- ...reateResourceWithClientGeneratedIdTests.cs | 48 +-- ...eateResourceWithToManyRelationshipTests.cs | 94 +++--- ...reateResourceWithToOneRelationshipTests.cs | 94 +++--- .../ReadWrite/Deleting/DeleteResourceTests.cs | 63 ++-- .../Fetching/FetchRelationshipTests.cs | 71 ++--- .../ReadWrite/Fetching/FetchResourceTests.cs | 91 +++--- .../ReadWrite/ReadWriteDbContext.cs | 12 +- .../ReadWrite/RgbColorsController.cs | 3 +- .../AddToToManyRelationshipTests.cs | 184 ++++++------ .../RemoveFromToManyRelationshipTests.cs | 179 ++++++----- .../ReplaceToManyRelationshipTests.cs | 205 +++++++------ .../UpdateToOneRelationshipTests.cs | 182 ++++++------ .../ReplaceToManyRelationshipTests.cs | 185 ++++++------ .../Updating/Resources/UpdateResourceTests.cs | 228 +++++++------- .../Resources/UpdateToOneRelationshipTests.cs | 175 +++++------ .../ReadWrite/UserAccountsController.cs | 3 +- .../IntegrationTests/ReadWrite/WorkItem.cs | 6 +- .../ReadWrite/WorkItemGroupsController.cs | 3 +- .../ReadWrite/WorkItemsController.cs | 3 +- .../CustomersController.cs | 3 +- .../DefaultBehaviorTests.cs | 110 +++---- .../RequiredRelationships/OrdersController.cs | 3 +- .../RequiredRelationships/Shipment.cs | 4 +- .../ShipmentsController.cs | 3 +- .../GiftCertificatesController.cs | 3 +- .../InjectionFakers.cs | 2 +- .../PostOffice.cs | 2 +- .../PostOfficesController.cs | 3 +- .../ResourceInjectionTests.cs | 80 +++-- .../ResourceDefinitions/CallableDbContext.cs | 3 +- .../CallableResourceDefinition.cs | 23 +- .../CallableResourcesController.cs | 3 +- .../ResourceDefinitions/IUserRolesService.cs | 2 +- .../ResourceDefinitionQueryCallbackTests.cs | 50 ++-- .../ResourceHooks/ResourceHookTests.cs | 163 +++++----- .../InheritanceDbContext.cs | 21 +- .../ResourceInheritance/InheritanceTests.cs | 80 +++-- .../ResourceInheritance/MenController.cs | 7 +- .../Models/FamilyHealthInsurance.cs | 2 +- .../ResourceInheritance/Models/Human.cs | 2 +- .../BlockingHttpDeleteController.cs | 3 +- .../BlockingHttpPatchController.cs | 3 +- .../BlockingHttpPostController.cs | 3 +- .../BlockingWritesController.cs | 3 +- .../DisableQueryStringTests.cs | 16 +- .../HttpReadOnlyTests.cs | 26 +- .../NoHttpDeleteTests.cs | 22 +- .../RestrictedControllers/NoHttpPatchTests.cs | 22 +- .../RestrictedControllers/NoHttpPostTests.cs | 22 +- .../IntegrationTests/Serialization/Meeting.cs | 11 +- .../MeetingAttendeesController.cs | 3 +- .../Serialization/MeetingLocation.cs | 2 +- .../Serialization/MeetingsController.cs | 3 +- .../Serialization/SerializationFakers.cs | 4 +- .../Serialization/SerializationTests.cs | 69 ++--- .../SoftDeletion/CompaniesController.cs | 3 +- .../SoftDeletion/DepartmentsController.cs | 3 +- .../SoftDeletion/SoftDeletionDbContext.cs | 3 +- .../SoftDeletionResourceDefinition.cs | 13 +- .../SoftDeletion/SoftDeletionTests.cs | 45 +-- .../ZeroKeys/EmptyGuidAsKeyTests.cs | 127 ++++---- .../ZeroKeys/GamesController.cs | 3 +- .../ZeroKeys/MapsController.cs | 3 +- .../ZeroKeys/PlayersController.cs | 3 +- .../ZeroKeys/ZeroAsKeyTests.cs | 127 ++++---- .../ZeroKeys/ZeroKeyDbContext.cs | 3 +- .../UnitTests/Links/LinkInclusionTests.cs | 10 +- .../DefaultsParseTests.cs | 14 +- .../QueryStringParameters/FilterParseTests.cs | 27 +- .../IncludeParseTests.cs | 18 +- .../LegacyFilterParseTests.cs | 11 +- .../QueryStringParameters/NullsParseTests.cs | 11 +- .../PaginationParseTests.cs | 28 +- .../QueryStringParameters/SortParseTests.cs | 18 +- .../SparseFieldSetParseTests.cs | 15 +- test/MultiDbContextTests/ResourceTests.cs | 4 +- test/NoEntityFrameworkTests/WorkItemTests.cs | 14 +- .../BaseIntegrationTestContext.cs | 68 ++--- .../TestBuildingBlocks/DbContextExtensions.cs | 34 ++- test/TestBuildingBlocks/FakeLoggerFactory.cs | 20 +- test/TestBuildingBlocks/FakerContainer.cs | 6 +- test/TestBuildingBlocks/FrozenSystemClock.cs | 3 +- .../HttpResponseMessageExtensions.cs | 3 +- test/TestBuildingBlocks/IntegrationTest.cs | 58 ++-- .../ObjectAssertionsExtensions.cs | 28 +- .../Builders/ResourceGraphBuilderTests.cs | 68 +++-- .../Controllers/BaseJsonApiControllerTests.cs | 77 +++-- .../ServiceCollectionExtensionsTests.cs | 275 +++++++++++++---- test/UnitTests/Graph/BaseType.cs | 4 +- test/UnitTests/Graph/DerivedType.cs | 4 +- test/UnitTests/Graph/IGenericInterface.cs | 4 +- test/UnitTests/Graph/Implementation.cs | 4 +- test/UnitTests/Graph/Model.cs | 4 +- .../ResourceDescriptorAssemblyCacheTests.cs | 21 +- test/UnitTests/Graph/TypeLocatorTests.cs | 51 ++-- test/UnitTests/Internal/ErrorDocumentTests.cs | 2 +- .../RequestScopedServiceProviderTests.cs | 2 +- .../Internal/ResourceGraphBuilderTests.cs | 16 +- test/UnitTests/Internal/TypeHelperTests.cs | 44 +-- .../Middleware/JsonApiMiddlewareTests.cs | 77 +++-- .../Middleware/JsonApiRequestTests.cs | 12 +- .../UnitTests/Models/AttributesEqualsTests.cs | 77 ++++- test/UnitTests/Models/IdentifiableTests.cs | 22 +- .../UnitTests/Models/RelationshipDataTests.cs | 17 +- .../ResourceConstructionExpressionTests.cs | 10 +- .../Models/ResourceConstructionTests.cs | 29 +- .../UnitTests/ResourceHooks/DiscoveryTests.cs | 147 ++++++--- test/UnitTests/ResourceHooks/Dummy.cs | 2 + .../Executor/Create/AfterCreateTests.cs | 59 ++-- .../Executor/Create/BeforeCreateTests.cs | 69 +++-- .../Create/BeforeCreateWithDbValuesTests.cs | 145 +++++---- .../Executor/Delete/AfterDeleteTests.cs | 20 +- .../Executor/Delete/BeforeDeleteTests.cs | 21 +- .../Delete/BeforeDeleteWithDbValuesTests.cs | 85 ++++-- .../IdentifiableManyToManyOnReturnTests.cs | 132 ++++++--- .../Executor/ManyToManyOnReturnTests.cs | 112 ++++--- .../Executor/Read/BeforeReadTests.cs | 89 +++--- .../IdentifiableManyToManyAfterReadTests.cs | 121 +++++--- .../Executor/Read/ManyToManyAfterReadTests.cs | 67 +++-- .../Executor/SameResourceTypeTests.cs | 94 ++++-- .../Executor/Update/AfterUpdateTests.cs | 59 ++-- .../Executor/Update/BeforeUpdateTests.cs | 69 +++-- .../Update/BeforeUpdateWithDbValuesTests.cs | 232 ++++++++++----- .../UnitTests/ResourceHooks/HooksDummyData.cs | 76 ++--- .../ResourceHooks/HooksTestsSetup.cs | 184 +++++------- test/UnitTests/ResourceHooks/NotTargeted.cs | 4 +- .../RelationshipDictionaryTests.cs | 144 +++++++-- test/UnitTests/ResourceHooks/ToMany.cs | 6 +- test/UnitTests/ResourceHooks/ToOne.cs | 6 +- .../Client/RequestSerializerTests.cs | 97 ++++-- .../Client/ResponseDeserializerTests.cs | 243 ++++++++++----- .../Common/BaseDocumentBuilderTests.cs | 18 +- .../Common/BaseDocumentParserTests.cs | 108 ++++--- .../Common/ResourceObjectBuilderTests.cs | 97 ++++-- .../Serialization/DeserializerTestsSetup.cs | 43 ++- .../Serialization/SerializerTestsSetup.cs | 58 ++-- .../IncludedResourceObjectBuilderTests.cs | 138 +++++---- .../Server/RequestDeserializerTests.cs | 16 +- .../ResponseResourceObjectBuilderTests.cs | 72 +++-- .../Server/ResponseSerializerTests.cs | 157 +++++++--- test/UnitTests/TestModels/Article.cs | 14 +- test/UnitTests/TestModels/BaseModel.cs | 6 +- test/UnitTests/TestModels/Blog.cs | 11 +- .../UnitTests/TestModels/FirstDerivedModel.cs | 3 +- test/UnitTests/TestModels/Food.cs | 3 +- .../TestModels/IdentifiableWithAttribute.cs | 3 +- .../MultipleRelationshipsDependentPart.cs | 19 +- .../MultipleRelationshipsPrincipalPart.cs | 19 +- .../TestModels/OneToManyDependent.cs | 4 +- .../TestModels/OneToManyPrincipal.cs | 3 +- .../TestModels/OneToManyRequiredDependent.cs | 4 +- .../UnitTests/TestModels/OneToOneDependent.cs | 4 +- .../UnitTests/TestModels/OneToOnePrincipal.cs | 3 +- .../TestModels/OneToOneRequiredDependent.cs | 4 +- test/UnitTests/TestModels/Person.cs | 15 +- .../TestModels/SecondDerivedModel.cs | 3 +- test/UnitTests/TestModels/Song.cs | 3 +- test/UnitTests/TestModels/TestResource.cs | 26 +- .../TestResourceWithAbstractRelationship.cs | 7 +- .../TestModels/TestResourceWithList.cs | 3 +- 629 files changed, 10673 insertions(+), 8567 deletions(-) diff --git a/benchmarks/BenchmarkResource.cs b/benchmarks/BenchmarkResource.cs index 50c132c8a7..acc1511844 100644 --- a/benchmarks/BenchmarkResource.cs +++ b/benchmarks/BenchmarkResource.cs @@ -7,7 +7,7 @@ namespace Benchmarks [UsedImplicitly(ImplicitUseTargetFlags.Members)] public sealed class BenchmarkResource : Identifiable { - [Attr(PublicName = BenchmarkResourcePublicNames.NameAttr)] + [Attr(PublicName = BenchmarkResourcePublicNames.NameAttr)] public string Name { get; set; } [HasOne] diff --git a/benchmarks/BenchmarkResourcePublicNames.cs b/benchmarks/BenchmarkResourcePublicNames.cs index b97db1ea64..b8d6fdae12 100644 --- a/benchmarks/BenchmarkResourcePublicNames.cs +++ b/benchmarks/BenchmarkResourcePublicNames.cs @@ -5,4 +5,4 @@ internal static class BenchmarkResourcePublicNames public const string NameAttr = "full-name"; public const string Type = "simple-types"; } -} \ No newline at end of file +} diff --git a/benchmarks/DependencyFactory.cs b/benchmarks/DependencyFactory.cs index 62aee6f301..d5ca4af6b6 100644 --- a/benchmarks/DependencyFactory.cs +++ b/benchmarks/DependencyFactory.cs @@ -7,7 +7,7 @@ internal static class DependencyFactory { public static IResourceGraph CreateResourceGraph(IJsonApiOptions options) { - ResourceGraphBuilder builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance); + var builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance); builder.Add(BenchmarkResourcePublicNames.Type); return builder.Build(); } diff --git a/benchmarks/LinkBuilder/LinkBuilderGetNamespaceFromPathBenchmarks.cs b/benchmarks/LinkBuilder/LinkBuilderGetNamespaceFromPathBenchmarks.cs index 3486ce30d9..b3dbef6232 100644 --- a/benchmarks/LinkBuilder/LinkBuilderGetNamespaceFromPathBenchmarks.cs +++ b/benchmarks/LinkBuilder/LinkBuilderGetNamespaceFromPathBenchmarks.cs @@ -5,7 +5,9 @@ namespace Benchmarks.LinkBuilder { // ReSharper disable once ClassCanBeSealed.Global - [MarkdownExporter, SimpleJob(launchCount: 3, warmupCount: 10, targetCount: 20), MemoryDiagnoser] + [MarkdownExporter] + [SimpleJob(3, 10, 20)] + [MemoryDiagnoser] public class LinkBuilderGetNamespaceFromPathBenchmarks { private const string RequestPath = "/api/some-really-long-namespace-path/resources/current/articles/?some"; @@ -13,14 +15,20 @@ public class LinkBuilderGetNamespaceFromPathBenchmarks private const char PathDelimiter = '/'; [Benchmark] - public void UsingStringSplit() => GetNamespaceFromPathUsingStringSplit(RequestPath, ResourceName); + public void UsingStringSplit() + { + GetNamespaceFromPathUsingStringSplit(RequestPath, ResourceName); + } [Benchmark] - public void UsingReadOnlySpan() => GetNamespaceFromPathUsingReadOnlySpan(RequestPath, ResourceName); + public void UsingReadOnlySpan() + { + GetNamespaceFromPathUsingReadOnlySpan(RequestPath, ResourceName); + } private static void GetNamespaceFromPathUsingStringSplit(string path, string resourceName) { - StringBuilder namespaceBuilder = new StringBuilder(path.Length); + var namespaceBuilder = new StringBuilder(path.Length); string[] segments = path.Split('/'); for (int index = 1; index < segments.Length; index++) @@ -56,6 +64,7 @@ private static void GetNamespaceFromPathUsingReadOnlySpan(string path, string re bool isAtEnd = lastCharacterIndex == pathSpan.Length; bool hasDelimiterAfterSegment = pathSpan.Length >= lastCharacterIndex + 1 && pathSpan[lastCharacterIndex].Equals(PathDelimiter); + if (isAtEnd || hasDelimiterAfterSegment) { _ = pathSpan.Slice(0, index).ToString(); diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index 963b0322e8..0d745a795d 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -16,6 +16,7 @@ private static void Main(string[] args) typeof(QueryParserBenchmarks), typeof(LinkBuilderGetNamespaceFromPathBenchmarks) }); + switcher.Run(args); } } diff --git a/benchmarks/Query/QueryParserBenchmarks.cs b/benchmarks/Query/QueryParserBenchmarks.cs index f34f37f919..8ba23d56f0 100644 --- a/benchmarks/Query/QueryParserBenchmarks.cs +++ b/benchmarks/Query/QueryParserBenchmarks.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.Design; using BenchmarkDotNet.Attributes; using JsonApiDotNetCore; @@ -14,7 +15,9 @@ namespace Benchmarks.Query { // ReSharper disable once ClassCanBeSealed.Global - [MarkdownExporter, SimpleJob(launchCount: 3, warmupCount: 10, targetCount: 20), MemoryDiagnoser] + [MarkdownExporter] + [SimpleJob(3, 10, 20)] + [MemoryDiagnoser] public class QueryParserBenchmarks { private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new FakeRequestQueryStringAccessor(); @@ -40,18 +43,18 @@ public QueryParserBenchmarks() _queryStringReaderForAll = CreateQueryParameterDiscoveryForAll(resourceGraph, request, options, _queryStringAccessor); } - private static QueryStringReader CreateQueryParameterDiscoveryForSort(IResourceGraph resourceGraph, - JsonApiRequest request, IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor) + private static QueryStringReader CreateQueryParameterDiscoveryForSort(IResourceGraph resourceGraph, JsonApiRequest request, IJsonApiOptions options, + FakeRequestQueryStringAccessor queryStringAccessor) { var sortReader = new SortQueryStringParameterReader(request, resourceGraph); - var readers = sortReader.AsEnumerable(); + IEnumerable readers = sortReader.AsEnumerable(); return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance); } - private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph, - JsonApiRequest request, IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor) + private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph, JsonApiRequest request, IJsonApiOptions options, + FakeRequestQueryStringAccessor queryStringAccessor) { var resourceFactory = new ResourceFactory(new ServiceContainer()); @@ -63,7 +66,8 @@ private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGr var defaultsReader = new DefaultsQueryStringParameterReader(options); var nullsReader = new NullsQueryStringParameterReader(options); - var readers = ArrayFactory.Create(includeReader, filterReader, sortReader, sparseFieldSetReader, paginationReader, defaultsReader, nullsReader); + IQueryStringParameterReader[] readers = ArrayFactory.Create(includeReader, filterReader, sortReader, + sparseFieldSetReader, paginationReader, defaultsReader, nullsReader); return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance); } @@ -71,7 +75,7 @@ private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGr [Benchmark] public void AscendingSort() { - var queryString = $"?sort={BenchmarkResourcePublicNames.NameAttr}"; + string queryString = $"?sort={BenchmarkResourcePublicNames.NameAttr}"; _queryStringAccessor.SetQueryString(queryString); _queryStringReaderForSort.ReadAll(null); @@ -80,23 +84,26 @@ public void AscendingSort() [Benchmark] public void DescendingSort() { - var queryString = $"?sort=-{BenchmarkResourcePublicNames.NameAttr}"; + string queryString = $"?sort=-{BenchmarkResourcePublicNames.NameAttr}"; _queryStringAccessor.SetQueryString(queryString); _queryStringReaderForSort.ReadAll(null); } [Benchmark] - public void ComplexQuery() => Run(100, () => + public void ComplexQuery() { - const string resourceName = BenchmarkResourcePublicNames.Type; - const string attrName = BenchmarkResourcePublicNames.NameAttr; + Run(100, () => + { + const string resourceName = BenchmarkResourcePublicNames.Type; + const string attrName = BenchmarkResourcePublicNames.NameAttr; - var queryString = $"?filter[{attrName}]=abc,eq:abc&sort=-{attrName}&include=child&page[size]=1&fields[{resourceName}]={attrName}"; + string queryString = $"?filter[{attrName}]=abc,eq:abc&sort=-{attrName}&include=child&page[size]=1&fields[{resourceName}]={attrName}"; - _queryStringAccessor.SetQueryString(queryString); - _queryStringReaderForAll.ReadAll(null); - }); + _queryStringAccessor.SetQueryString(queryString); + _queryStringReaderForAll.ReadAll(null); + }); + } private void Run(int iterations, Action action) { diff --git a/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs b/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs index 3d43a1fe87..9ecc308e7b 100644 --- a/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs @@ -44,6 +44,9 @@ public JsonApiDeserializerBenchmarks() } [Benchmark] - public object DeserializeSimpleObject() => _jsonApiDeserializer.Deserialize(Content); + public object DeserializeSimpleObject() + { + return _jsonApiDeserializer.Deserialize(Content); + } } } diff --git a/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs b/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs index c3f613ab56..088be638c4 100644 --- a/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs +++ b/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs @@ -29,9 +29,9 @@ public JsonApiSerializerBenchmarks() IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options); IFieldsToSerialize fieldsToSerialize = CreateFieldsToSerialize(resourceGraph); - var metaBuilder = new Mock().Object; - var linkBuilder = new Mock().Object; - var includeBuilder = new Mock().Object; + IMetaBuilder metaBuilder = new Mock().Object; + ILinkBuilder linkBuilder = new Mock().Object; + IIncludedResourceObjectBuilder includeBuilder = new Mock().Object; var resourceObjectBuilder = new ResourceObjectBuilder(resourceGraph, new ResourceObjectBuilderSettings()); @@ -48,12 +48,15 @@ private static FieldsToSerialize CreateFieldsToSerialize(IResourceGraph resource new SparseFieldSetQueryStringParameterReader(request, resourceGraph) }; - var accessor = new Mock().Object; + IResourceDefinitionAccessor accessor = new Mock().Object; return new FieldsToSerialize(resourceGraph, constraintProviders, accessor, request); } [Benchmark] - public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content); + public object SerializeSimpleObject() + { + return _jsonApiSerializer.Serialize(Content); + } } } diff --git a/src/Examples/GettingStarted/Controllers/PeopleController.cs b/src/Examples/GettingStarted/Controllers/PeopleController.cs index 0337a159d3..c7600be15a 100644 --- a/src/Examples/GettingStarted/Controllers/PeopleController.cs +++ b/src/Examples/GettingStarted/Controllers/PeopleController.cs @@ -8,11 +8,9 @@ namespace GettingStarted.Controllers { public sealed class PeopleController : JsonApiController { - public PeopleController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + public PeopleController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) - { } + { + } } } diff --git a/src/Examples/GettingStarted/Program.cs b/src/Examples/GettingStarted/Program.cs index fad81e1bba..68bca0ae86 100644 --- a/src/Examples/GettingStarted/Program.cs +++ b/src/Examples/GettingStarted/Program.cs @@ -10,11 +10,12 @@ public static void Main(string[] args) CreateHostBuilder(args).Build().Run(); } - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } } } diff --git a/src/Examples/GettingStarted/Properties/launchSettings.json b/src/Examples/GettingStarted/Properties/launchSettings.json index 9ea0e9d79b..b68c2481ed 100644 --- a/src/Examples/GettingStarted/Properties/launchSettings.json +++ b/src/Examples/GettingStarted/Properties/launchSettings.json @@ -1,29 +1,29 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:14141" - } + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:14141" + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "launchUrl": "api/people", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "launchUrl": "api/people", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Kestrel": { - "commandName": "Project", - "launchBrowser": false, - "launchUrl": "api/people", - "applicationUrl": "http://localhost:14141", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } + "Kestrel": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "api/people", + "applicationUrl": "http://localhost:14141", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } + } } diff --git a/src/Examples/GettingStarted/Startup.cs b/src/Examples/GettingStarted/Startup.cs index 8d7eacdeea..b942ecc98d 100644 --- a/src/Examples/GettingStarted/Startup.cs +++ b/src/Examples/GettingStarted/Startup.cs @@ -14,21 +14,19 @@ public sealed class Startup // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddDbContext( - options => options.UseSqlite("Data Source=sample.db")); + services.AddDbContext(options => options.UseSqlite("Data Source=sample.db")); - services.AddJsonApi( - options => - { - options.Namespace = "api"; - options.UseRelativeLinks = true; - options.IncludeTotalResourceCount = true; - options.SerializerSettings.Formatting = Formatting.Indented; - }); + services.AddJsonApi(options => + { + options.Namespace = "api"; + options.UseRelativeLinks = true; + options.IncludeTotalResourceCount = true; + options.SerializerSettings.Formatting = Formatting.Indented; + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - [UsedImplicitly] + [UsedImplicitly] public void Configure(IApplicationBuilder app, SampleDbContext context) { context.Database.EnsureDeleted(); @@ -41,8 +39,8 @@ public void Configure(IApplicationBuilder app, SampleDbContext context) } private static void CreateSampleData(SampleDbContext context) - { - // Note: The generate-examples.ps1 script (to create example requests in documentation) depends on these. + { + // Note: The generate-examples.ps1 script (to create example requests in documentation) depends on these. context.Books.AddRange(new Book { diff --git a/src/Examples/GettingStarted/appsettings.json b/src/Examples/GettingStarted/appsettings.json index eb57d97280..c2ba4deaa9 100644 --- a/src/Examples/GettingStarted/appsettings.json +++ b/src/Examples/GettingStarted/appsettings.json @@ -1,10 +1,10 @@ { - "Logging": { - "LogLevel": { - "Default": "Warning", - "Microsoft": "Warning", - "Microsoft.EntityFrameworkCore.Database.Command": "Information" - } - }, - "AllowedHosts": "*" + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning", + "Microsoft.EntityFrameworkCore.Database.Command": "Information" + } + }, + "AllowedHosts": "*" } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs index a553851ccd..0735952114 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs @@ -8,11 +8,9 @@ namespace JsonApiDotNetCoreExample.Controllers { public sealed class ArticlesController : JsonApiController
{ - public ArticlesController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService
resourceService) + public ArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService
resourceService) : base(options, loggerFactory, resourceService) - { } + { + } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/AuthorsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/AuthorsController.cs index 789c31cb95..963e85e996 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/AuthorsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/AuthorsController.cs @@ -8,11 +8,9 @@ namespace JsonApiDotNetCoreExample.Controllers { public sealed class AuthorsController : JsonApiController { - public AuthorsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + public AuthorsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) - { } + { + } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/NonJsonApiController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/NonJsonApiController.cs index 90a864f07c..49708c5465 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/NonJsonApiController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/NonJsonApiController.cs @@ -10,7 +10,11 @@ public sealed class NonJsonApiController : ControllerBase [HttpGet] public IActionResult Get() { - var result = new[] {"Welcome!"}; + string[] result = + { + "Welcome!" + }; + return Ok(result); } @@ -24,21 +28,21 @@ public async Task PostAsync() return BadRequest("Please send your name."); } - var result = "Hello, " + name; + string result = "Hello, " + name; return Ok(result); } [HttpPut] public IActionResult Put([FromBody] string name) { - var result = "Hi, " + name; + string result = "Hi, " + name; return Ok(result); } [HttpPatch] public IActionResult Patch(string name) { - var result = "Good day, " + name; + string result = "Good day, " + name; return Ok(result); } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs index 247bd52d78..4851336a9a 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs @@ -9,8 +9,8 @@ namespace JsonApiDotNetCoreExample.Controllers { public sealed class OperationsController : JsonApiOperationsController { - public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IOperationsProcessor processor, IJsonApiRequest request, ITargetedFields targetedFields) + public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) : base(options, loggerFactory, processor, request, targetedFields) { } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs index 4b0116ece6..430790bc6e 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs @@ -8,11 +8,9 @@ namespace JsonApiDotNetCoreExample.Controllers { public sealed class PeopleController : JsonApiController { - public PeopleController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + public PeopleController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) - { } + { + } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs index dbd9057a12..a28a7033d6 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs @@ -8,11 +8,9 @@ namespace JsonApiDotNetCoreExample.Controllers { public sealed class TodoItemsController : JsonApiController { - public TodoItemsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + public TodoItemsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) - { } + { + } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs index 312c0f50bc..11a742ced4 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs @@ -8,11 +8,9 @@ namespace JsonApiDotNetCoreExample.Controllers { public sealed class UsersController : JsonApiController { - public UsersController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + public UsersController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) - { } + { + } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs index a438f44831..9d13b17493 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs @@ -15,7 +15,8 @@ public sealed class AppDbContext : DbContext public DbSet AuthorDifferentDbContextName { get; set; } public DbSet Users { get; set; } - public AppDbContext(DbContextOptions options) : base(options) + public AppDbContext(DbContextOptions options) + : base(options) { } @@ -30,10 +31,18 @@ protected override void OnModelCreating(ModelBuilder builder) .WithMany(p => p.TodoItems); builder.Entity() - .HasKey(bc => new {bc.ArticleId, bc.TagId}); + .HasKey(bc => new + { + bc.ArticleId, + bc.TagId + }); builder.Entity() - .HasKey(bc => new {bc.ArticleId, bc.TagId}); + .HasKey(bc => new + { + bc.ArticleId, + bc.TagId + }); builder.Entity() .HasOne(t => t.StakeHolderTodoItem) diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs index 6d4bc02cdb..298471b73d 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs @@ -14,7 +14,10 @@ namespace JsonApiDotNetCoreExample.Definitions [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class ArticleHooksDefinition : ResourceHooksDefinition
{ - public ArticleHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public ArticleHooksDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) + { + } public override IEnumerable
OnReturn(HashSet
resources, ResourcePipeline pipeline) { @@ -30,4 +33,3 @@ public override IEnumerable
OnReturn(HashSet
resources, Resour } } } - diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/LockableHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/LockableHooksDefinition.cs index 845fc61eea..54e20746c7 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/LockableHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/LockableHooksDefinition.cs @@ -9,17 +9,20 @@ namespace JsonApiDotNetCoreExample.Definitions { - public abstract class LockableHooksDefinition : ResourceHooksDefinition where T : class, IIsLockable, IIdentifiable + public abstract class LockableHooksDefinition : ResourceHooksDefinition + where T : class, IIsLockable, IIdentifiable { private readonly IResourceGraph _resourceGraph; - protected LockableHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) + + protected LockableHooksDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) { _resourceGraph = resourceGraph; } protected void DisallowLocked(IEnumerable resources) { - foreach (var resource in resources ?? Enumerable.Empty()) + foreach (T resource in resources ?? Enumerable.Empty()) { if (resource.IsLocked) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs index 8d73f9eaec..4d38b618c1 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs @@ -13,7 +13,8 @@ namespace JsonApiDotNetCoreExample.Definitions [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class PassportHooksDefinition : LockableHooksDefinition { - public PassportHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) + public PassportHooksDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) { } diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs index 28e1d51955..1d70f5892b 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs @@ -10,9 +10,13 @@ namespace JsonApiDotNetCoreExample.Definitions [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class PersonHooksDefinition : LockableHooksDefinition { - public PersonHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public PersonHooksDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) + { + } - public override IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) + public override IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary resourcesByRelationship, + ResourcePipeline pipeline) { BeforeImplicitUpdateRelationship(resourcesByRelationship, pipeline); return ids; diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs index 2ae1a543c6..3c44aec26f 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs @@ -11,7 +11,10 @@ namespace JsonApiDotNetCoreExample.Definitions [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class TagHooksDefinition : ResourceHooksDefinition { - public TagHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public TagHooksDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) + { + } public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs index 892b12c7a8..e6c2b11e23 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs @@ -13,7 +13,10 @@ namespace JsonApiDotNetCoreExample.Definitions [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class TodoItemHooksDefinition : LockableHooksDefinition { - public TodoItemHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public TodoItemHooksDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) + { + } public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs index 84de22fcdf..df1adfdbcd 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs @@ -18,11 +18,13 @@ public sealed class Article : Identifiable [NotMapped] [HasManyThrough(nameof(ArticleTags))] public ISet Tags { get; set; } + public ISet ArticleTags { get; set; } [NotMapped] [HasManyThrough(nameof(IdentifiableArticleTags))] public ICollection IdentifiableTags { get; set; } + public ICollection IdentifiableArticleTags { get; set; } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/IdentifiableArticleTag.cs b/src/Examples/JsonApiDotNetCoreExample/Models/IdentifiableArticleTag.cs index ba44bf56ad..d8cbab1366 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/IdentifiableArticleTag.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/IdentifiableArticleTag.cs @@ -8,10 +8,12 @@ namespace JsonApiDotNetCoreExample.Models public sealed class IdentifiableArticleTag : Identifiable { public int ArticleId { get; set; } + [HasOne] public Article Article { get; set; } public int TagId { get; set; } + [HasOne] public Tag Tag { get; set; } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Program.cs b/src/Examples/JsonApiDotNetCoreExample/Program.cs index e2866c25ec..4c97d8a7f4 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Program.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Program.cs @@ -11,11 +11,12 @@ public static void Main(string[] args) CreateHostBuilder(args).Build().Run(); } - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Properties/launchSettings.json b/src/Examples/JsonApiDotNetCoreExample/Properties/launchSettings.json index 1e3998e4f0..6a5108a8ad 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Properties/launchSettings.json +++ b/src/Examples/JsonApiDotNetCoreExample/Properties/launchSettings.json @@ -1,30 +1,30 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:14140", - "sslPort": 44340 - } + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:14140", + "sslPort": 44340 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "launchUrl": "api/v1/todoItems", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "launchUrl": "api/v1/todoItems", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Kestrel": { - "commandName": "Project", - "launchBrowser": false, - "launchUrl": "api/v1/todoItems", - "applicationUrl": "https://localhost:44340;http://localhost:14140", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } + "Kestrel": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "api/v1/todoItems", + "applicationUrl": "https://localhost:44340;http://localhost:14140", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } + } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Startups/EmptyStartup.cs b/src/Examples/JsonApiDotNetCoreExample/Startups/EmptyStartup.cs index 0ee7454da6..19879aef27 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startups/EmptyStartup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startups/EmptyStartup.cs @@ -5,8 +5,8 @@ namespace JsonApiDotNetCoreExample.Startups { /// - /// Empty startup class, required for integration tests. - /// Changes in ASP.NET Core 3 no longer allow Startup class to be defined in test projects. See https://github.com/aspnet/AspNetCore/issues/15373. + /// Empty startup class, required for integration tests. Changes in ASP.NET Core 3 no longer allow Startup class to be defined in test projects. See + /// https://github.com/aspnet/AspNetCore/issues/15373. /// public abstract class EmptyStartup { diff --git a/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs index fa7580865d..2db12c7434 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs @@ -49,7 +49,7 @@ private void ConfigureJsonApiOptions(JsonApiOptions options) public override void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { - using (var scope = app.ApplicationServices.CreateScope()) + using (IServiceScope scope = app.ApplicationServices.CreateScope()) { var appDbContext = scope.ServiceProvider.GetRequiredService(); appDbContext.Database.EnsureCreated(); diff --git a/src/Examples/JsonApiDotNetCoreExample/appsettings.json b/src/Examples/JsonApiDotNetCoreExample/appsettings.json index 2b56699951..b1f0227dcd 100644 --- a/src/Examples/JsonApiDotNetCoreExample/appsettings.json +++ b/src/Examples/JsonApiDotNetCoreExample/appsettings.json @@ -1,13 +1,13 @@ { - "Data": { - "DefaultConnection": "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=###" - }, - "Logging": { - "LogLevel": { - "Default": "Warning", - "Microsoft": "Warning", - "Microsoft.EntityFrameworkCore.Database.Command": "Warning" - } - }, - "AllowedHosts": "*" + "Data": { + "DefaultConnection": "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=###" + }, + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning", + "Microsoft.EntityFrameworkCore.Database.Command": "Warning" + } + }, + "AllowedHosts": "*" } diff --git a/src/Examples/MultiDbContextExample/Controllers/ResourceAsController.cs b/src/Examples/MultiDbContextExample/Controllers/ResourceAsController.cs index b406411c41..4e976acdc0 100644 --- a/src/Examples/MultiDbContextExample/Controllers/ResourceAsController.cs +++ b/src/Examples/MultiDbContextExample/Controllers/ResourceAsController.cs @@ -8,8 +8,7 @@ namespace MultiDbContextExample.Controllers { public sealed class ResourceAsController : JsonApiController { - public ResourceAsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public ResourceAsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/src/Examples/MultiDbContextExample/Controllers/ResourceBsController.cs b/src/Examples/MultiDbContextExample/Controllers/ResourceBsController.cs index 7268bb7969..bd61b7aa2e 100644 --- a/src/Examples/MultiDbContextExample/Controllers/ResourceBsController.cs +++ b/src/Examples/MultiDbContextExample/Controllers/ResourceBsController.cs @@ -8,8 +8,7 @@ namespace MultiDbContextExample.Controllers { public sealed class ResourceBsController : JsonApiController { - public ResourceBsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public ResourceBsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/src/Examples/MultiDbContextExample/Program.cs b/src/Examples/MultiDbContextExample/Program.cs index 5d138239c0..d5800d95e8 100644 --- a/src/Examples/MultiDbContextExample/Program.cs +++ b/src/Examples/MultiDbContextExample/Program.cs @@ -12,8 +12,7 @@ public static void Main(string[] args) private static IHostBuilder CreateHostBuilder(string[] args) { - return Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); } } } diff --git a/src/Examples/MultiDbContextExample/Properties/launchSettings.json b/src/Examples/MultiDbContextExample/Properties/launchSettings.json index 1f33cc0f31..6d7e1b5cbd 100644 --- a/src/Examples/MultiDbContextExample/Properties/launchSettings.json +++ b/src/Examples/MultiDbContextExample/Properties/launchSettings.json @@ -1,30 +1,30 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:14150", - "sslPort": 44350 - } + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:14150", + "sslPort": 44350 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "launchUrl": "/resourceBs", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "launchUrl": "/resourceBs", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Kestrel": { - "commandName": "Project", - "launchBrowser": false, - "launchUrl": "/resourceBs", - "applicationUrl": "https://localhost:44350;http://localhost:14150", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } + "Kestrel": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "/resourceBs", + "applicationUrl": "https://localhost:44350;http://localhost:14150", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } + } } diff --git a/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs b/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs index 44174777f8..13fb0bbe48 100644 --- a/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs +++ b/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs @@ -13,9 +13,8 @@ namespace MultiDbContextExample.Repositories public sealed class DbContextARepository : EntityFrameworkCoreRepository where TResource : class, IIdentifiable { - public DbContextARepository(ITargetedFields targetedFields, DbContextResolver contextResolver, - IResourceGraph resourceGraph, IResourceFactory resourceFactory, - IEnumerable constraintProviders, ILoggerFactory loggerFactory) + public DbContextARepository(ITargetedFields targetedFields, DbContextResolver contextResolver, IResourceGraph resourceGraph, + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) { } diff --git a/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs b/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs index eb2cc5fc2b..0b65caa945 100644 --- a/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs +++ b/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs @@ -13,9 +13,8 @@ namespace MultiDbContextExample.Repositories public sealed class DbContextBRepository : EntityFrameworkCoreRepository where TResource : class, IIdentifiable { - public DbContextBRepository(ITargetedFields targetedFields, DbContextResolver contextResolver, - IResourceGraph resourceGraph, IResourceFactory resourceFactory, - IEnumerable constraintProviders, ILoggerFactory loggerFactory) + public DbContextBRepository(ITargetedFields targetedFields, DbContextResolver contextResolver, IResourceGraph resourceGraph, + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) { } diff --git a/src/Examples/MultiDbContextExample/Startup.cs b/src/Examples/MultiDbContextExample/Startup.cs index b1b7b6c57e..bc76c3cd9a 100644 --- a/src/Examples/MultiDbContextExample/Startup.cs +++ b/src/Examples/MultiDbContextExample/Startup.cs @@ -25,13 +25,16 @@ public void ConfigureServices(IServiceCollection services) services.AddJsonApi(options => { options.IncludeExceptionStackTraceInErrors = true; - }, dbContextTypes: new[] {typeof(DbContextA), typeof(DbContextB)}); + }, dbContextTypes: new[] + { + typeof(DbContextA), + typeof(DbContextB) + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. [UsedImplicitly] - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DbContextA dbContextA, - DbContextB dbContextB) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DbContextA dbContextA, DbContextB dbContextB) { if (env.IsDevelopment()) { diff --git a/src/Examples/MultiDbContextExample/appsettings.json b/src/Examples/MultiDbContextExample/appsettings.json index 4442d5117f..d9d9a9bff6 100644 --- a/src/Examples/MultiDbContextExample/appsettings.json +++ b/src/Examples/MultiDbContextExample/appsettings.json @@ -1,10 +1,10 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" } diff --git a/src/Examples/NoEntityFrameworkExample/Controllers/WorkItemsController.cs b/src/Examples/NoEntityFrameworkExample/Controllers/WorkItemsController.cs index 79b994d479..63ab620b93 100644 --- a/src/Examples/NoEntityFrameworkExample/Controllers/WorkItemsController.cs +++ b/src/Examples/NoEntityFrameworkExample/Controllers/WorkItemsController.cs @@ -8,11 +8,9 @@ namespace NoEntityFrameworkExample.Controllers { public sealed class WorkItemsController : JsonApiController { - public WorkItemsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + public WorkItemsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) - { } + { + } } } diff --git a/src/Examples/NoEntityFrameworkExample/Program.cs b/src/Examples/NoEntityFrameworkExample/Program.cs index c5b2eaa194..6653408dc7 100755 --- a/src/Examples/NoEntityFrameworkExample/Program.cs +++ b/src/Examples/NoEntityFrameworkExample/Program.cs @@ -10,11 +10,12 @@ public static void Main(string[] args) CreateHostBuilder(args).Build().Run(); } - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } } } diff --git a/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json b/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json index e7b6f86e76..32bc82dfc2 100644 --- a/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json +++ b/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json @@ -1,30 +1,30 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:14149", - "sslPort": 44349 - } + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:14149", + "sslPort": 44349 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "launchUrl": "/api/reports", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "launchUrl": "/api/reports", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Kestrel": { - "commandName": "Project", - "launchBrowser": false, - "launchUrl": "/api/reports", - "applicationUrl": "https://localhost:44349;http://localhost:14149", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } + "Kestrel": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "/api/reports", + "applicationUrl": "https://localhost:44349;http://localhost:14149", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } + } } diff --git a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs index c0fdc425cf..f934e7dc9b 100644 --- a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs +++ b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs @@ -36,9 +36,13 @@ public async Task> GetAsync(CancellationToken canc public async Task GetAsync(int id, CancellationToken cancellationToken) { const string commandText = @"select * from ""WorkItems"" where ""Id""=@id"; - var commandDefinition = new CommandDefinition(commandText, new {id}, cancellationToken: cancellationToken); - var workItems = await QueryAsync(async connection => await connection.QueryAsync(commandDefinition)); + var commandDefinition = new CommandDefinition(commandText, new + { + id + }, cancellationToken: cancellationToken); + + IReadOnlyCollection workItems = await QueryAsync(async connection => await connection.QueryAsync(commandDefinition)); return workItems.Single(); } @@ -59,14 +63,18 @@ public async Task CreateAsync(WorkItem resource, CancellationToken can var commandDefinition = new CommandDefinition(commandText, new { - title = resource.Title, isBlocked = resource.IsBlocked, durationInHours = resource.DurationInHours, projectId = resource.ProjectId + title = resource.Title, + isBlocked = resource.IsBlocked, + durationInHours = resource.DurationInHours, + projectId = resource.ProjectId }, cancellationToken: cancellationToken); - var workItems = await QueryAsync(async connection => await connection.QueryAsync(commandDefinition)); + IReadOnlyCollection workItems = await QueryAsync(async connection => await connection.QueryAsync(commandDefinition)); return workItems.Single(); } - public Task AddToToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) + public Task AddToToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, + CancellationToken cancellationToken) { throw new NotImplementedException(); } @@ -85,11 +93,14 @@ public async Task DeleteAsync(int id, CancellationToken cancellationToken) { const string commandText = @"delete from ""WorkItems"" where ""Id""=@id"; - await QueryAsync(async connection => - await connection.QueryAsync(new CommandDefinition(commandText, new {id}, cancellationToken: cancellationToken))); + await QueryAsync(async connection => await connection.QueryAsync(new CommandDefinition(commandText, new + { + id + }, cancellationToken: cancellationToken))); } - public Task RemoveFromToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) + public Task RemoveFromToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, + CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/Examples/NoEntityFrameworkExample/Startup.cs b/src/Examples/NoEntityFrameworkExample/Startup.cs index 6699be3376..8e0d20afa5 100644 --- a/src/Examples/NoEntityFrameworkExample/Startup.cs +++ b/src/Examples/NoEntityFrameworkExample/Startup.cs @@ -25,17 +25,13 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddJsonApi( - options => options.Namespace = "api/v1", - resources: builder => builder.Add("workItems") - ); + services.AddJsonApi(options => options.Namespace = "api/v1", resources: builder => builder.Add("workItems")); services.AddScoped, WorkItemService>(); services.AddDbContext(options => { - options.UseNpgsql(_connectionString, - postgresOptions => postgresOptions.SetPostgresVersion(new Version(9, 6))); + options.UseNpgsql(_connectionString, postgresOptions => postgresOptions.SetPostgresVersion(new Version(9, 6))); }); } diff --git a/src/Examples/NoEntityFrameworkExample/appsettings.json b/src/Examples/NoEntityFrameworkExample/appsettings.json index ef303ea8e2..7036e98f9d 100644 --- a/src/Examples/NoEntityFrameworkExample/appsettings.json +++ b/src/Examples/NoEntityFrameworkExample/appsettings.json @@ -1,13 +1,13 @@ { - "Data": { - "DefaultConnection": "Host=localhost;Port=5432;Database=NoEntityFrameworkExample;User ID=postgres;Password=###" - }, - "Logging": { - "LogLevel": { - "Default": "Warning", - "Microsoft": "Warning", - "Microsoft.EntityFrameworkCore.Database.Command": "Information" - } - }, - "AllowedHosts": "*" + "Data": { + "DefaultConnection": "Host=localhost;Port=5432;Database=NoEntityFrameworkExample;User ID=postgres;Password=###" + }, + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning", + "Microsoft.EntityFrameworkCore.Database.Command": "Information" + } + }, + "AllowedHosts": "*" } diff --git a/src/Examples/ReportsExample/Controllers/ReportsController.cs b/src/Examples/ReportsExample/Controllers/ReportsController.cs index c9820f8a22..c154dc5562 100644 --- a/src/Examples/ReportsExample/Controllers/ReportsController.cs +++ b/src/Examples/ReportsExample/Controllers/ReportsController.cs @@ -10,14 +10,12 @@ namespace ReportsExample.Controllers { [Route("api/[controller]")] - public class ReportsController : BaseJsonApiController + public class ReportsController : BaseJsonApiController { - public ReportsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IGetAllService getAll) + public ReportsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IGetAllService getAll) : base(options, loggerFactory, getAll) - { } + { + } [HttpGet] public override async Task GetAsync(CancellationToken cancellationToken) diff --git a/src/Examples/ReportsExample/Program.cs b/src/Examples/ReportsExample/Program.cs index ee8edaa620..6356b0f52a 100644 --- a/src/Examples/ReportsExample/Program.cs +++ b/src/Examples/ReportsExample/Program.cs @@ -10,11 +10,12 @@ public static void Main(string[] args) CreateHostBuilder(args).Build().Run(); } - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } } } diff --git a/src/Examples/ReportsExample/Properties/launchSettings.json b/src/Examples/ReportsExample/Properties/launchSettings.json index 0fcb421625..ee2eba1f80 100644 --- a/src/Examples/ReportsExample/Properties/launchSettings.json +++ b/src/Examples/ReportsExample/Properties/launchSettings.json @@ -1,30 +1,30 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:14148", - "sslPort": 44348 - } + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:14148", + "sslPort": 44348 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "launchUrl": "/api/reports", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "launchUrl": "/api/reports", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Kestrel": { - "commandName": "Project", - "launchBrowser": false, - "launchUrl": "/api/reports", - "applicationUrl": "https://localhost:44348;http://localhost:14148", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } + "Kestrel": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "/api/reports", + "applicationUrl": "https://localhost:44348;http://localhost:14148", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } + } } diff --git a/src/Examples/ReportsExample/Services/ReportService.cs b/src/Examples/ReportsExample/Services/ReportService.cs index 2fcb4367f0..cd9447bd3f 100644 --- a/src/Examples/ReportsExample/Services/ReportService.cs +++ b/src/Examples/ReportsExample/Services/ReportService.cs @@ -22,7 +22,7 @@ public Task> GetAsync(CancellationToken cancellation { _logger.LogInformation("GetAsync"); - var reports = GetReports(); + IReadOnlyCollection reports = GetReports(); return Task.FromResult(reports); } diff --git a/src/Examples/ReportsExample/Startup.cs b/src/Examples/ReportsExample/Startup.cs index fcb5274e8f..a030441258 100644 --- a/src/Examples/ReportsExample/Startup.cs +++ b/src/Examples/ReportsExample/Startup.cs @@ -6,12 +6,10 @@ namespace ReportsExample { public sealed class Startup { - // This method gets called by the runtime. Use this method to add services to the container. + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddJsonApi( - options => options.Namespace = "api", - discovery => discovery.AddCurrentAssembly()); + services.AddJsonApi(options => options.Namespace = "api", discovery => discovery.AddCurrentAssembly()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Examples/ReportsExample/appsettings.json b/src/Examples/ReportsExample/appsettings.json index 69a873bd79..357fc4f63c 100644 --- a/src/Examples/ReportsExample/appsettings.json +++ b/src/Examples/ReportsExample/appsettings.json @@ -1,9 +1,9 @@ { - "Logging": { - "LogLevel": { - "Default": "Warning", - "Microsoft": "Warning" - } - }, - "AllowedHosts": "*" -} + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs index e115c133be..3d30ebe089 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Repositories; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; namespace JsonApiDotNetCore.AtomicOperations { @@ -26,11 +27,10 @@ public EntityFrameworkCoreTransactionFactory(IDbContextResolver dbContextResolve /// public async Task BeginTransactionAsync(CancellationToken cancellationToken) { - var dbContext = _dbContextResolver.GetContext(); + DbContext dbContext = _dbContextResolver.GetContext(); - var transaction = _options.TransactionIsolationLevel != null - ? await dbContext.Database.BeginTransactionAsync(_options.TransactionIsolationLevel.Value, - cancellationToken) + IDbContextTransaction transaction = _options.TransactionIsolationLevel != null + ? await dbContext.Database.BeginTransactionAsync(_options.TransactionIsolationLevel.Value, cancellationToken) : await dbContext.Database.BeginTransactionAsync(cancellationToken); return new EntityFrameworkCoreTransaction(transaction, dbContext); diff --git a/src/JsonApiDotNetCore/AtomicOperations/IOperationProcessorAccessor.cs b/src/JsonApiDotNetCore/AtomicOperations/IOperationProcessorAccessor.cs index 3045e33f7d..693bd6098b 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/IOperationProcessorAccessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/IOperationProcessorAccessor.cs @@ -6,12 +6,12 @@ namespace JsonApiDotNetCore.AtomicOperations { /// - /// Retrieves an instance from the D/I container and invokes a method on it. + /// Retrieves an instance from the D/I container and invokes a method on it. /// public interface IOperationProcessorAccessor { /// - /// Invokes on a processor compatible with the operation kind. + /// Invokes on a processor compatible with the operation kind. /// Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs index 2c8b7ab53c..56faba2b66 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs @@ -49,7 +49,7 @@ public void Assign(string localId, string resourceType, string stringId) AssertIsDeclared(localId); - var item = _idsTracked[localId]; + LocalIdState item = _idsTracked[localId]; AssertSameResourceType(resourceType, item.ResourceType, localId); @@ -69,7 +69,7 @@ public string GetValue(string localId, string resourceType) AssertIsDeclared(localId); - var item = _idsTracked[localId]; + LocalIdState item = _idsTracked[localId]; AssertSameResourceType(resourceType, item.ResourceType, localId); diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs index 95ce18da6d..47ab571cb3 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Serialization.Objects; namespace JsonApiDotNetCore.AtomicOperations { @@ -35,7 +36,7 @@ public void Validate(IEnumerable operations) try { - foreach (var operation in operations) + foreach (OperationContainer operation in operations) { ValidateOperation(operation); @@ -44,7 +45,7 @@ public void Validate(IEnumerable operations) } catch (JsonApiException exception) { - foreach (var error in exception.Errors) + foreach (Error error in exception.Errors) { error.Source.Pointer = $"/atomic:operations[{operationIndex}]" + error.Source.Pointer; } @@ -64,7 +65,7 @@ private void ValidateOperation(OperationContainer operation) AssertLocalIdIsAssigned(operation.Resource); } - foreach (var secondaryResource in operation.GetSecondaryResources()) + foreach (IIdentifiable secondaryResource in operation.GetSecondaryResources()) { AssertLocalIdIsAssigned(secondaryResource); } @@ -79,7 +80,7 @@ private void DeclareLocalId(IIdentifiable resource) { if (resource.LocalId != null) { - var resourceContext = _resourceContextProvider.GetResourceContext(resource.GetType()); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resource.GetType()); _localIdTracker.Declare(resource.LocalId, resourceContext.PublicName); } } @@ -88,8 +89,7 @@ private void AssignLocalId(OperationContainer operation) { if (operation.Resource.LocalId != null) { - var resourceContext = - _resourceContextProvider.GetResourceContext(operation.Resource.GetType()); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(operation.Resource.GetType()); _localIdTracker.Assign(operation.Resource.LocalId, resourceContext.PublicName, string.Empty); } @@ -99,7 +99,7 @@ private void AssertLocalIdIsAssigned(IIdentifiable resource) { if (resource.LocalId != null) { - var resourceContext = _resourceContextProvider.GetResourceContext(resource.GetType()); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resource.GetType()); _localIdTracker.GetValue(resource.LocalId, resourceContext.PublicName); } } diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs index 551e564358..f387316720 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs @@ -17,8 +17,7 @@ public class OperationProcessorAccessor : IOperationProcessorAccessor private readonly IResourceContextProvider _resourceContextProvider; private readonly IServiceProvider _serviceProvider; - public OperationProcessorAccessor(IResourceContextProvider resourceContextProvider, - IServiceProvider serviceProvider) + public OperationProcessorAccessor(IResourceContextProvider resourceContextProvider, IServiceProvider serviceProvider) { ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); ArgumentGuard.NotNull(serviceProvider, nameof(serviceProvider)); @@ -32,17 +31,17 @@ public Task ProcessAsync(OperationContainer operation, Cance { ArgumentGuard.NotNull(operation, nameof(operation)); - var processor = ResolveProcessor(operation); + IOperationProcessor processor = ResolveProcessor(operation); return processor.ProcessAsync(operation, cancellationToken); } protected virtual IOperationProcessor ResolveProcessor(OperationContainer operation) { - var processorInterface = GetProcessorInterface(operation.Kind); - var resourceContext = _resourceContextProvider.GetResourceContext(operation.Resource.GetType()); + Type processorInterface = GetProcessorInterface(operation.Kind); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(operation.Resource.GetType()); - var processorType = processorInterface.MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType); - return (IOperationProcessor) _serviceProvider.GetRequiredService(processorType); + Type processorType = processorInterface.MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType); + return (IOperationProcessor)_serviceProvider.GetRequiredService(processorType); } private static Type GetProcessorInterface(OperationKind kind) diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs index 4cbd1f938b..e522dabe51 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs @@ -24,9 +24,8 @@ public class OperationsProcessor : IOperationsProcessor private readonly ITargetedFields _targetedFields; private readonly LocalIdValidator _localIdValidator; - public OperationsProcessor(IOperationProcessorAccessor operationProcessorAccessor, - IOperationsTransactionFactory operationsTransactionFactory, ILocalIdTracker localIdTracker, - IResourceContextProvider resourceContextProvider, IJsonApiRequest request, ITargetedFields targetedFields) + public OperationsProcessor(IOperationProcessorAccessor operationProcessorAccessor, IOperationsTransactionFactory operationsTransactionFactory, + ILocalIdTracker localIdTracker, IResourceContextProvider resourceContextProvider, IJsonApiRequest request, ITargetedFields targetedFields) { ArgumentGuard.NotNull(operationProcessorAccessor, nameof(operationProcessorAccessor)); ArgumentGuard.NotNull(operationsTransactionFactory, nameof(operationsTransactionFactory)); @@ -45,8 +44,7 @@ public OperationsProcessor(IOperationProcessorAccessor operationProcessorAccesso } /// - public virtual async Task> ProcessAsync(IList operations, - CancellationToken cancellationToken) + public virtual async Task> ProcessAsync(IList operations, CancellationToken cancellationToken) { ArgumentGuard.NotNull(operations, nameof(operations)); @@ -55,16 +53,17 @@ public virtual async Task> ProcessAsync(IList(); - await using var transaction = await _operationsTransactionFactory.BeginTransactionAsync(cancellationToken); + await using IOperationsTransaction transaction = await _operationsTransactionFactory.BeginTransactionAsync(cancellationToken); + try { - foreach (var operation in operations) + foreach (OperationContainer operation in operations) { operation.SetTransactionId(transaction.TransactionId); await transaction.BeforeProcessOperationAsync(cancellationToken); - var result = await ProcessOperationAsync(operation, cancellationToken); + OperationContainer result = await ProcessOperationAsync(operation, cancellationToken); results.Add(result); await transaction.AfterProcessOperationAsync(cancellationToken); @@ -78,7 +77,7 @@ public virtual async Task> ProcessAsync(IList> ProcessAsync(IList ProcessOperationAsync(OperationContainer operation, - CancellationToken cancellationToken) + protected virtual async Task ProcessOperationAsync(OperationContainer operation, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -128,7 +125,7 @@ protected void TrackLocalIdsForOperation(OperationContainer operation) AssignStringId(operation.Resource); } - foreach (var secondaryResource in operation.GetSecondaryResources()) + foreach (IIdentifiable secondaryResource in operation.GetSecondaryResources()) { AssignStringId(secondaryResource); } @@ -138,7 +135,7 @@ private void DeclareLocalId(IIdentifiable resource) { if (resource.LocalId != null) { - var resourceContext = _resourceContextProvider.GetResourceContext(resource.GetType()); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resource.GetType()); _localIdTracker.Declare(resource.LocalId, resourceContext.PublicName); } } @@ -147,7 +144,7 @@ private void AssignStringId(IIdentifiable resource) { if (resource.LocalId != null) { - var resourceContext = _resourceContextProvider.GetResourceContext(resource.GetType()); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resource.GetType()); resource.StringId = _localIdTracker.GetValue(resource.LocalId, resourceContext.PublicName); } } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs index c988d8da00..127316556d 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -21,16 +22,14 @@ public AddToRelationshipProcessor(IAddToRelationshipService serv } /// - public virtual async Task ProcessAsync(OperationContainer operation, - CancellationToken cancellationToken) + public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { ArgumentGuard.NotNull(operation, nameof(operation)); - var primaryId = (TId) operation.Resource.GetTypedId(); - var secondaryResourceIds = operation.GetSecondaryResources(); + var primaryId = (TId)operation.Resource.GetTypedId(); + ISet secondaryResourceIds = operation.GetSecondaryResources(); - await _service.AddToToManyRelationshipAsync(primaryId, operation.Request.Relationship.PublicName, - secondaryResourceIds, cancellationToken); + await _service.AddToToManyRelationshipAsync(primaryId, operation.Request.Relationship.PublicName, secondaryResourceIds, cancellationToken); return null; } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs index 9c8547a676..8a8bdef8ad 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs @@ -16,8 +16,7 @@ public class CreateProcessor : ICreateProcessor private readonly ILocalIdTracker _localIdTracker; private readonly IResourceContextProvider _resourceContextProvider; - public CreateProcessor(ICreateService service, ILocalIdTracker localIdTracker, - IResourceContextProvider resourceContextProvider) + public CreateProcessor(ICreateService service, ILocalIdTracker localIdTracker, IResourceContextProvider resourceContextProvider) { ArgumentGuard.NotNull(service, nameof(service)); ArgumentGuard.NotNull(localIdTracker, nameof(localIdTracker)); @@ -29,17 +28,16 @@ public CreateProcessor(ICreateService service, ILocalIdTracker l } /// - public virtual async Task ProcessAsync(OperationContainer operation, - CancellationToken cancellationToken) + public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { ArgumentGuard.NotNull(operation, nameof(operation)); - var newResource = await _service.CreateAsync((TResource) operation.Resource, cancellationToken); + TResource newResource = await _service.CreateAsync((TResource)operation.Resource, cancellationToken); if (operation.Resource.LocalId != null) { - var serverId = newResource != null ? newResource.StringId : operation.Resource.StringId; - var resourceContext = _resourceContextProvider.GetResourceContext(); + string serverId = newResource != null ? newResource.StringId : operation.Resource.StringId; + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); _localIdTracker.Assign(operation.Resource.LocalId, resourceContext.PublicName, serverId); } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs index dd91f23384..929ffe73a9 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs @@ -21,12 +21,11 @@ public DeleteProcessor(IDeleteService service) } /// - public virtual async Task ProcessAsync(OperationContainer operation, - CancellationToken cancellationToken) + public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { ArgumentGuard.NotNull(operation, nameof(operation)); - var id = (TId) operation.Resource.GetTypedId(); + var id = (TId)operation.Resource.GetTypedId(); await _service.DeleteAsync(id, cancellationToken); return null; diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/IAddToRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/IAddToRelationshipProcessor.cs index 113771ab4d..2f7c10a3f7 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/IAddToRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/IAddToRelationshipProcessor.cs @@ -8,8 +8,12 @@ namespace JsonApiDotNetCore.AtomicOperations.Processors /// /// Processes a single operation to add resources to a to-many relationship. /// - /// The resource type. - /// The resource identifier type. + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// [PublicAPI] public interface IAddToRelationshipProcessor : IOperationProcessor where TResource : class, IIdentifiable diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/ICreateProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/ICreateProcessor.cs index 79ffaf4090..0f747a9dd0 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/ICreateProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/ICreateProcessor.cs @@ -8,8 +8,12 @@ namespace JsonApiDotNetCore.AtomicOperations.Processors /// /// Processes a single operation to create a new resource with attributes, relationships or both. /// - /// The resource type. - /// The resource identifier type. + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// [PublicAPI] public interface ICreateProcessor : IOperationProcessor where TResource : class, IIdentifiable diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/IDeleteProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/IDeleteProcessor.cs index 3c5fbff948..4e5206054d 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/IDeleteProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/IDeleteProcessor.cs @@ -8,8 +8,12 @@ namespace JsonApiDotNetCore.AtomicOperations.Processors /// /// Processes a single operation to delete an existing resource. /// - /// The resource type. - /// The resource identifier type. + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// [PublicAPI] public interface IDeleteProcessor : IOperationProcessor where TResource : class, IIdentifiable diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/ISetRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/ISetRelationshipProcessor.cs index 4943cdf97f..8dafc839a4 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/ISetRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/ISetRelationshipProcessor.cs @@ -8,8 +8,12 @@ namespace JsonApiDotNetCore.AtomicOperations.Processors /// /// Processes a single operation to perform a complete replacement of a relationship on an existing resource. /// - /// The resource type. - /// The resource identifier type. + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// [PublicAPI] public interface ISetRelationshipProcessor : IOperationProcessor where TResource : class, IIdentifiable diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/IUpdateProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/IUpdateProcessor.cs index 7c8967e8e7..48847f6ddb 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/IUpdateProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/IUpdateProcessor.cs @@ -6,11 +6,15 @@ namespace JsonApiDotNetCore.AtomicOperations.Processors { /// - /// Processes a single operation to update the attributes and/or relationships of an existing resource. - /// Only the values of sent attributes are replaced. And only the values of sent relationships are replaced. + /// Processes a single operation to update the attributes and/or relationships of an existing resource. Only the values of sent attributes are replaced. + /// And only the values of sent relationships are replaced. /// - /// The resource type. - /// The resource identifier type. + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// [PublicAPI] public interface IUpdateProcessor : IOperationProcessor where TResource : class, IIdentifiable diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs index 5a3c339417..b41169510d 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -21,16 +22,14 @@ public RemoveFromRelationshipProcessor(IRemoveFromRelationshipService - public virtual async Task ProcessAsync(OperationContainer operation, - CancellationToken cancellationToken) + public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { ArgumentGuard.NotNull(operation, nameof(operation)); - var primaryId = (TId) operation.Resource.GetTypedId(); - var secondaryResourceIds = operation.GetSecondaryResources(); + var primaryId = (TId)operation.Resource.GetTypedId(); + ISet secondaryResourceIds = operation.GetSecondaryResources(); - await _service.RemoveFromToManyRelationshipAsync(primaryId, operation.Request.Relationship.PublicName, - secondaryResourceIds, cancellationToken); + await _service.RemoveFromToManyRelationshipAsync(primaryId, operation.Request.Relationship.PublicName, secondaryResourceIds, cancellationToken); return null; } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs index 1f9e825537..b2f9ca3678 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -23,28 +24,26 @@ public SetRelationshipProcessor(ISetRelationshipService service) } /// - public virtual async Task ProcessAsync(OperationContainer operation, - CancellationToken cancellationToken) + public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { ArgumentGuard.NotNull(operation, nameof(operation)); - var primaryId = (TId) operation.Resource.GetTypedId(); + var primaryId = (TId)operation.Resource.GetTypedId(); object rightValue = GetRelationshipRightValue(operation); - await _service.SetRelationshipAsync(primaryId, operation.Request.Relationship.PublicName, rightValue, - cancellationToken); + await _service.SetRelationshipAsync(primaryId, operation.Request.Relationship.PublicName, rightValue, cancellationToken); return null; } private static object GetRelationshipRightValue(OperationContainer operation) { - var relationship = operation.Request.Relationship; - var rightValue = relationship.GetValue(operation.Resource); + RelationshipAttribute relationship = operation.Request.Relationship; + object rightValue = relationship.GetValue(operation.Resource); if (relationship is HasManyAttribute) { - var rightResources = TypeHelper.ExtractResources(rightValue); + ICollection rightResources = TypeHelper.ExtractResources(rightValue); return rightResources.ToHashSet(IdentifiableComparer.Instance); } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs index f1828caaaf..151d91adfe 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs @@ -21,13 +21,12 @@ public UpdateProcessor(IUpdateService service) } /// - public virtual async Task ProcessAsync(OperationContainer operation, - CancellationToken cancellationToken) + public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { ArgumentGuard.NotNull(operation, nameof(operation)); - var resource = (TResource) operation.Resource; - var updated = await _service.UpdateAsync(resource.Id, resource, cancellationToken); + var resource = (TResource)operation.Resource; + TResource updated = await _service.UpdateAsync(resource.Id, resource, cancellationToken); return updated == null ? null : operation.WithResource(updated); } diff --git a/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs b/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs index f0a4e6715e..740cbdac0f 100644 --- a/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs +++ b/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs @@ -9,10 +9,11 @@ public static class ApplicationBuilderExtensions /// /// Registers the JsonApiDotNetCore middleware. /// - /// The to add the middleware to. + /// + /// The to add the middleware to. + /// /// - /// The code below is the minimal that is required for proper activation, - /// which should be added to your Startup.Configure method. + /// The code below is the minimal that is required for proper activation, which should be added to your Startup.Configure method. /// ().CreateScope(); + using IServiceScope scope = builder.ApplicationServices.GetRequiredService().CreateScope(); var inverseNavigationResolver = scope.ServiceProvider.GetRequiredService(); inverseNavigationResolver.Resolve(); - var jsonApiApplicationBuilder = builder.ApplicationServices.GetRequiredService(); + var jsonApiApplicationBuilder = builder.ApplicationServices.GetRequiredService(); + jsonApiApplicationBuilder.ConfigureMvcOptions = options => { var inputFormatter = builder.ApplicationServices.GetRequiredService(); diff --git a/src/JsonApiDotNetCore/Configuration/GenericServiceFactory.cs b/src/JsonApiDotNetCore/Configuration/GenericServiceFactory.cs index ca71ae0470..5806790fb5 100644 --- a/src/JsonApiDotNetCore/Configuration/GenericServiceFactory.cs +++ b/src/JsonApiDotNetCore/Configuration/GenericServiceFactory.cs @@ -35,7 +35,7 @@ public TInterface Get(Type openGenericType, Type resourceType, Type private TInterface GetInternal(Type openGenericType, params Type[] types) { - var concreteType = openGenericType.MakeGenericType(types); + Type concreteType = openGenericType.MakeGenericType(types); return (TInterface)_serviceProvider.GetService(concreteType); } diff --git a/src/JsonApiDotNetCore/Configuration/IGenericServiceFactory.cs b/src/JsonApiDotNetCore/Configuration/IGenericServiceFactory.cs index 63177fdfd2..a730642e71 100644 --- a/src/JsonApiDotNetCore/Configuration/IGenericServiceFactory.cs +++ b/src/JsonApiDotNetCore/Configuration/IGenericServiceFactory.cs @@ -4,14 +4,14 @@ namespace JsonApiDotNetCore.Configuration { /// - /// Represents the Service Locator design pattern. Used to obtain object instances for types are not known until runtime. - /// This is only used by resource hooks and subject to be removed in a future version. + /// Represents the Service Locator design pattern. Used to obtain object instances for types are not known until runtime. This is only used by resource + /// hooks and subject to be removed in a future version. /// [PublicAPI] public interface IGenericServiceFactory { /// - /// Constructs the generic type and locates the service, then casts to . + /// Constructs the generic type and locates the service, then casts to . /// /// /// (Type openGenericType, Type resourceType); /// - /// Constructs the generic type and locates the service, then casts to . + /// Constructs the generic type and locates the service, then casts to . /// /// /// - /// Responsible for populating . - /// - /// This service is instantiated in the configure phase of the application. - /// - /// When using a data access layer different from EF Core, and when using ResourceHooks - /// that depend on the inverse navigation property (BeforeImplicitUpdateRelationship), - /// you will need to override this service, or set explicitly. + /// Responsible for populating . This service is instantiated in the configure phase of the + /// application. When using a data access layer different from EF Core, and when using ResourceHooks that depend on the inverse navigation property + /// (BeforeImplicitUpdateRelationship), you will need to override this service, or set + /// explicitly. /// [PublicAPI] public interface IInverseNavigationResolver { /// - /// This method is called upon startup by JsonApiDotNetCore. It resolves inverse navigations. + /// This method is called upon startup by JsonApiDotNetCore. It resolves inverse navigations. /// void Resolve(); } diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index ab46a78af2..707d1d0944 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -12,6 +12,15 @@ namespace JsonApiDotNetCore.Configuration /// public interface IJsonApiOptions { + internal NamingStrategy SerializerNamingStrategy + { + get + { + var contractResolver = SerializerSettings.ContractResolver as DefaultContractResolver; + return contractResolver?.NamingStrategy ?? JsonApiOptions.DefaultNamingStrategy; + } + } + /// /// The URL prefix to use for exposed endpoints. /// @@ -21,14 +30,12 @@ public interface IJsonApiOptions string Namespace { get; } /// - /// Specifies the default query string capabilities that can be used on exposed JSON:API attributes. - /// Defaults to . + /// Specifies the default query string capabilities that can be used on exposed JSON:API attributes. Defaults to . /// AttrCapabilities DefaultAttrCapabilities { get; } /// - /// Whether or not stack traces should be serialized in objects. - /// False by default. + /// Whether or not stack traces should be serialized in objects. False by default. /// bool IncludeExceptionStackTraceInErrors { get; } @@ -57,33 +64,26 @@ public interface IJsonApiOptions bool UseRelativeLinks { get; } /// - /// Configures which links to show in the - /// object. Defaults to . - /// This setting can be overruled per resource type by - /// adding on the class definition of a resource. + /// Configures which links to show in the object. Defaults to . This + /// setting can be overruled per resource type by adding on the class definition of a resource. /// LinkTypes TopLevelLinks { get; } /// - /// Configures which links to show in the - /// object. Defaults to . - /// This setting can be overruled per resource type by - /// adding on the class definition of a resource. + /// Configures which links to show in the object. Defaults to . This + /// setting can be overruled per resource type by adding on the class definition of a resource. /// LinkTypes ResourceLinks { get; } /// - /// Configures which links to show in the - /// object. Defaults to . - /// This setting can be overruled for all relationships per resource type by - /// adding on the class definition of a resource. - /// This can be further overruled per relationship by setting . + /// Configures which links to show in the object. Defaults to . This + /// setting can be overruled for all relationships per resource type by adding on the class definition of a + /// resource. This can be further overruled per relationship by setting . /// LinkTypes RelationshipLinks { get; } /// - /// Whether or not the total resource count should be included in all document-level meta objects. - /// False by default. + /// Whether or not the total resource count should be included in all document-level meta objects. False by default. /// bool IncludeTotalResourceCount { get; } @@ -103,78 +103,68 @@ public interface IJsonApiOptions PageNumber MaximumPageNumber { get; } /// - /// Whether or not to enable ASP.NET Core model state validation. - /// False by default. + /// Whether or not to enable ASP.NET Core model state validation. False by default. /// bool ValidateModelState { get; } /// - /// Whether or not clients can provide IDs when creating resources. When not allowed, a 403 Forbidden response is returned - /// if a client attempts to create a resource with a defined ID. - /// False by default. + /// Whether or not clients can provide IDs when creating resources. When not allowed, a 403 Forbidden response is returned if a client attempts to create + /// a resource with a defined ID. False by default. /// bool AllowClientGeneratedIds { get; } /// - /// Whether or not resource hooks are enabled. - /// This is currently an experimental feature and subject to change in future versions. - /// Defaults to False. + /// Whether or not resource hooks are enabled. This is currently an experimental feature and subject to change in future versions. Defaults to False. /// public bool EnableResourceHooks { get; } /// - /// Whether or not database values should be included by default for resource hooks. - /// Ignored if EnableResourceHooks is set to false. - /// False by default. + /// Whether or not database values should be included by default for resource hooks. Ignored if EnableResourceHooks is set to false. False by default. /// bool LoadDatabaseValues { get; } /// - /// Whether or not to produce an error on unknown query string parameters. - /// False by default. + /// Whether or not to produce an error on unknown query string parameters. False by default. /// bool AllowUnknownQueryStringParameters { get; } /// - /// Determines whether legacy filter notation in query strings, such as =eq:, =like:, and =in: is enabled. - /// False by default. + /// Determines whether legacy filter notation in query strings, such as =eq:, =like:, and =in: is enabled. False by default. /// bool EnableLegacyFilterNotation { get; } /// - /// Determines whether the serialization setting can be controlled using a query string parameter. - /// False by default. + /// Determines whether the serialization setting can be controlled using a query string + /// parameter. False by default. /// bool AllowQueryStringOverrideForSerializerNullValueHandling { get; } /// - /// Determines whether the serialization setting can be controlled using a query string parameter. - /// False by default. + /// Determines whether the serialization setting can be controlled using a query string + /// parameter. False by default. /// bool AllowQueryStringOverrideForSerializerDefaultValueHandling { get; } /// - /// Controls how many levels deep includes are allowed to be nested. - /// For example, MaximumIncludeDepth=1 would allow ?include=articles but not ?include=articles.revisions. - /// null by default, which means unconstrained. + /// Controls how many levels deep includes are allowed to be nested. For example, MaximumIncludeDepth=1 would allow ?include=articles but not + /// ?include=articles.revisions. null by default, which means unconstrained. /// int? MaximumIncludeDepth { get; } /// - /// Limits the maximum number of operations allowed per atomic:operations request. Defaults to 10. - /// Set to null for unlimited. + /// Limits the maximum number of operations allowed per atomic:operations request. Defaults to 10. Set to null for unlimited. /// int? MaximumOperationsPerRequest { get; } /// - /// Enables to override the default isolation level for database transactions, enabling to balance between consistency and performance. - /// Defaults to null, which leaves this up to Entity Framework Core to choose (and then it varies per database provider). + /// Enables to override the default isolation level for database transactions, enabling to balance between consistency and performance. Defaults to + /// null, which leaves this up to Entity Framework Core to choose (and then it varies per database provider). /// IsolationLevel? TransactionIsolationLevel { get; } /// - /// Specifies the settings that are used by the . - /// Note that at some places a few settings are ignored, to ensure JSON:API spec compliance. + /// Specifies the settings that are used by the . Note that at some places a few settings are ignored, to ensure JSON:API + /// spec compliance. /// /// The next example changes the naming convention to kebab casing. /// /// JsonSerializerSettings SerializerSettings { get; } - - internal NamingStrategy SerializerNamingStrategy - { - get - { - var contractResolver = SerializerSettings.ContractResolver as DefaultContractResolver; - return contractResolver?.NamingStrategy ?? JsonApiOptions.DefaultNamingStrategy; - } - } } } diff --git a/src/JsonApiDotNetCore/Configuration/IRequestScopedServiceProvider.cs b/src/JsonApiDotNetCore/Configuration/IRequestScopedServiceProvider.cs index a3e72e1fb8..327eeca353 100644 --- a/src/JsonApiDotNetCore/Configuration/IRequestScopedServiceProvider.cs +++ b/src/JsonApiDotNetCore/Configuration/IRequestScopedServiceProvider.cs @@ -3,9 +3,10 @@ namespace JsonApiDotNetCore.Configuration { /// - /// An interface used to separate the registration of the global - /// from a request-scoped service provider. This is useful in cases when we need to - /// manually resolve services from the request scope (e.g. operation processors). + /// An interface used to separate the registration of the global from a request-scoped service provider. This is useful + /// in cases when we need to manually resolve services from the request scope (e.g. operation processors). /// - public interface IRequestScopedServiceProvider : IServiceProvider { } + public interface IRequestScopedServiceProvider : IServiceProvider + { + } } diff --git a/src/JsonApiDotNetCore/Configuration/IResourceContextProvider.cs b/src/JsonApiDotNetCore/Configuration/IResourceContextProvider.cs index a03b40870d..a452bf30c9 100644 --- a/src/JsonApiDotNetCore/Configuration/IResourceContextProvider.cs +++ b/src/JsonApiDotNetCore/Configuration/IResourceContextProvider.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.Configuration { /// - /// Responsible for getting s from the . + /// Responsible for getting s from the . /// public interface IResourceContextProvider { @@ -27,6 +27,7 @@ public interface IResourceContextProvider /// /// Gets the resource metadata for the specified resource type. /// - ResourceContext GetResourceContext() where TResource : class, IIdentifiable; + ResourceContext GetResourceContext() + where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Configuration/IResourceGraph.cs b/src/JsonApiDotNetCore/Configuration/IResourceGraph.cs index a02045257c..acbfc5961e 100644 --- a/src/JsonApiDotNetCore/Configuration/IResourceGraph.cs +++ b/src/JsonApiDotNetCore/Configuration/IResourceGraph.cs @@ -14,53 +14,70 @@ namespace JsonApiDotNetCore.Configuration public interface IResourceGraph : IResourceContextProvider { /// - /// Gets all fields (attributes and relationships) for - /// that are targeted by the selector. If no selector is provided, all - /// exposed fields are returned. + /// Gets all fields (attributes and relationships) for that are targeted by the selector. If no selector is provided, + /// all exposed fields are returned. /// - /// The resource for which to retrieve fields. - /// Should be of the form: (TResource e) => new { e.Field1, e.Field2 } - IReadOnlyCollection GetFields(Expression> selector = null) where TResource : class, IIdentifiable; - + /// + /// The resource for which to retrieve fields. + /// + /// + /// Should be of the form: (TResource e) => new { e.Field1, e.Field2 } + /// + IReadOnlyCollection GetFields(Expression> selector = null) + where TResource : class, IIdentifiable; + /// - /// Gets all attributes for - /// that are targeted by the selector. If no selector is provided, all - /// exposed fields are returned. + /// Gets all attributes for that are targeted by the selector. If no selector is provided, all exposed fields are + /// returned. /// - /// The resource for which to retrieve attributes. - /// Should be of the form: (TResource e) => new { e.Attribute1, e.Attribute2 } - IReadOnlyCollection GetAttributes(Expression> selector = null) where TResource : class, IIdentifiable; - + /// + /// The resource for which to retrieve attributes. + /// + /// + /// Should be of the form: (TResource e) => new { e.Attribute1, e.Attribute2 } + /// + IReadOnlyCollection GetAttributes(Expression> selector = null) + where TResource : class, IIdentifiable; + /// - /// Gets all relationships for - /// that are targeted by the selector. If no selector is provided, all - /// exposed fields are returned. + /// Gets all relationships for that are targeted by the selector. If no selector is provided, all exposed fields are + /// returned. /// - /// The resource for which to retrieve relationships. - /// Should be of the form: (TResource e) => new { e.Relationship1, e.Relationship2 } - IReadOnlyCollection GetRelationships(Expression> selector = null) where TResource : class, IIdentifiable; - + /// + /// The resource for which to retrieve relationships. + /// + /// + /// Should be of the form: (TResource e) => new { e.Relationship1, e.Relationship2 } + /// + IReadOnlyCollection GetRelationships(Expression> selector = null) + where TResource : class, IIdentifiable; + /// /// Gets all exposed fields (attributes and relationships) for the specified type. /// - /// The resource type. Must implement . + /// + /// The resource type. Must implement . + /// IReadOnlyCollection GetFields(Type type); - + /// /// Gets all exposed attributes for the specified type. /// - /// The resource type. Must implement . + /// + /// The resource type. Must implement . + /// IReadOnlyCollection GetAttributes(Type type); - + /// /// Gets all exposed relationships for the specified type. /// - /// The resource type. Must implement . + /// + /// The resource type. Must implement . + /// IReadOnlyCollection GetRelationships(Type type); - + /// - /// Traverses the resource graph, looking for the inverse relationship of the specified - /// . + /// Traverses the resource graph, looking for the inverse relationship of the specified . /// RelationshipAttribute GetInverseRelationship(RelationshipAttribute relationship); } diff --git a/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs b/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs index 4c895a9251..5675a1b023 100644 --- a/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs +++ b/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs @@ -14,8 +14,7 @@ public class InverseNavigationResolver : IInverseNavigationResolver private readonly IResourceContextProvider _resourceContextProvider; private readonly IEnumerable _dbContextResolvers; - public InverseNavigationResolver(IResourceContextProvider resourceContextProvider, - IEnumerable dbContextResolvers) + public InverseNavigationResolver(IResourceContextProvider resourceContextProvider, IEnumerable dbContextResolvers) { ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); ArgumentGuard.NotNull(dbContextResolvers, nameof(dbContextResolvers)); @@ -27,7 +26,7 @@ public InverseNavigationResolver(IResourceContextProvider resourceContextProvide /// public void Resolve() { - foreach (var dbContextResolver in _dbContextResolvers) + foreach (IDbContextResolver dbContextResolver in _dbContextResolvers) { DbContext dbContext = dbContextResolver.GetContext(); Resolve(dbContext); @@ -39,6 +38,7 @@ private void Resolve(DbContext dbContext) foreach (ResourceContext resourceContext in _resourceContextProvider.GetResourceContexts()) { IEntityType entityType = dbContext.Model.FindEntityType(resourceContext.ResourceType); + if (entityType != null) { ResolveRelationships(resourceContext.Relationships, entityType); @@ -48,7 +48,7 @@ private void Resolve(DbContext dbContext) private void ResolveRelationships(IReadOnlyCollection relationships, IEntityType entityType) { - foreach (var relationship in relationships) + foreach (RelationshipAttribute relationship in relationships) { if (!(relationship is HasManyThroughAttribute)) { diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs index 43c0413001..9f04418849 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs @@ -22,14 +22,15 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Configuration { /// - /// A utility class that builds a JsonApi application. It registers all required services - /// and allows the user to override parts of the startup configuration. + /// A utility class that builds a JsonApi application. It registers all required services and allows the user to override parts of the startup + /// configuration. /// internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder, IDisposable { @@ -39,7 +40,7 @@ internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder, ID private readonly ResourceGraphBuilder _resourceGraphBuilder; private readonly ServiceDiscoveryFacade _serviceDiscoveryFacade; private readonly ServiceProvider _intermediateProvider; - + public Action ConfigureMvcOptions { get; set; } public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder) @@ -58,15 +59,15 @@ public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mv } /// - /// Executes the action provided by the user to configure . + /// Executes the action provided by the user to configure . /// public void ConfigureJsonApiOptions(Action configureOptions) { configureOptions?.Invoke(_options); } - + /// - /// Executes the action provided by the user to configure . + /// Executes the action provided by the user to configure . /// public void ConfigureAutoDiscovery(Action configureAutoDiscovery) { @@ -74,13 +75,13 @@ public void ConfigureAutoDiscovery(Action configureAutoD } /// - /// Configures and builds the resource graph with resources from the provided sources and adds it to the DI container. + /// Configures and builds the resource graph with resources from the provided sources and adds it to the DI container. /// public void AddResourceGraph(ICollection dbContextTypes, Action configureResourceGraph) { _serviceDiscoveryFacade.DiscoverResources(); - foreach (var dbContextType in dbContextTypes) + foreach (Type dbContextType in dbContextTypes) { var dbContext = (DbContext)_intermediateProvider.GetRequiredService(dbContextType); AddResourcesFromDbContext(dbContext, _resourceGraphBuilder); @@ -88,7 +89,7 @@ public void AddResourceGraph(ICollection dbContextTypes, Action dbContextTypes) { _services.AddScoped(typeof(DbContextResolver<>)); - foreach (var dbContextType in dbContextTypes) + foreach (Type dbContextType in dbContextTypes) { - var contextResolverType = typeof(DbContextResolver<>).MakeGenericType(dbContextType); + Type contextResolverType = typeof(DbContextResolver<>).MakeGenericType(dbContextType); _services.AddScoped(typeof(IDbContextResolver), contextResolverType); } @@ -183,8 +184,8 @@ private void AddMiddlewareLayer() private void AddResourceLayer() { - RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.ResourceDefinitionInterfaces, - typeof(JsonApiResourceDefinition<>), typeof(JsonApiResourceDefinition<,>)); + RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.ResourceDefinitionInterfaces, typeof(JsonApiResourceDefinition<>), + typeof(JsonApiResourceDefinition<,>)); _services.AddScoped(); _services.AddScoped(); @@ -193,25 +194,23 @@ private void AddResourceLayer() private void AddRepositoryLayer() { - RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.RepositoryInterfaces, - typeof(EntityFrameworkCoreRepository<>), typeof(EntityFrameworkCoreRepository<,>)); + RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.RepositoryInterfaces, typeof(EntityFrameworkCoreRepository<>), + typeof(EntityFrameworkCoreRepository<,>)); _services.AddScoped(); } private void AddServiceLayer() { - RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.ServiceInterfaces, - typeof(JsonApiResourceService<>), typeof(JsonApiResourceService<,>)); + RegisterImplementationForOpenInterfaces(ServiceDiscoveryFacade.ServiceInterfaces, typeof(JsonApiResourceService<>), + typeof(JsonApiResourceService<,>)); } private void RegisterImplementationForOpenInterfaces(HashSet openGenericInterfaces, Type intImplementation, Type implementation) { - foreach (var openGenericInterface in openGenericInterfaces) + foreach (Type openGenericInterface in openGenericInterfaces) { - var implementationType = openGenericInterface.GetGenericArguments().Length == 1 - ? intImplementation - : implementation; + Type implementationType = openGenericInterface.GetGenericArguments().Length == 1 ? intImplementation : implementation; _services.AddScoped(openGenericInterface, implementationType); } @@ -256,7 +255,7 @@ private void RegisterDependentService() } private void AddResourceHooks() - { + { if (_options.EnableResourceHooks) { _services.AddSingleton(typeof(IHooksDiscovery<>), typeof(HooksDiscovery<>)); @@ -303,12 +302,12 @@ private void AddOperationsLayer() private void AddResourcesFromDbContext(DbContext dbContext, ResourceGraphBuilder builder) { - foreach (var entityType in dbContext.Model.GetEntityTypes()) + foreach (IEntityType entityType in dbContext.Model.GetEntityTypes()) { builder.Add(entityType.ClrType); } } - + public void Dispose() { _intermediateProvider.Dispose(); diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiModelMetadataProvider.cs b/src/JsonApiDotNetCore/Configuration/JsonApiModelMetadataProvider.cs index e40935696d..19f9edc531 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiModelMetadataProvider.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiModelMetadataProvider.cs @@ -6,7 +6,7 @@ namespace JsonApiDotNetCore.Configuration { /// - /// Custom implementation of to support JSON:API partial patching. + /// Custom implementation of to support JSON:API partial patching. /// internal sealed class JsonApiModelMetadataProvider : DefaultModelMetadataProvider { @@ -20,7 +20,8 @@ public JsonApiModelMetadataProvider(ICompositeMetadataDetailsProvider detailsPro } /// - public JsonApiModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider, IOptions optionsAccessor, IRequestScopedServiceProvider serviceProvider) + public JsonApiModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider, IOptions optionsAccessor, + IRequestScopedServiceProvider serviceProvider) : base(detailsProvider, optionsAccessor) { _jsonApiValidationFilter = new JsonApiValidationFilter(serviceProvider); diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 85811578fd..c1079fad58 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -12,6 +12,10 @@ public sealed class JsonApiOptions : IJsonApiOptions { internal static readonly NamingStrategy DefaultNamingStrategy = new CamelCaseNamingStrategy(); + // Workaround for https://github.com/dotnet/efcore/issues/21026 + internal bool DisableTopPagination { get; set; } + internal bool DisableChildrenPagination { get; set; } + /// public string Namespace { get; set; } @@ -86,9 +90,5 @@ public sealed class JsonApiOptions : IJsonApiOptions NamingStrategy = DefaultNamingStrategy } }; - - // Workaround for https://github.com/dotnet/efcore/issues/21026 - internal bool DisableTopPagination { get; set; } - internal bool DisableChildrenPagination { get; set; } } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs index 9baa1e3194..f4f9d5e1a7 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs @@ -32,13 +32,15 @@ public bool ShouldValidateEntry(ValidationEntry entry, ValidationEntry parentEnt return true; } - var isTopResourceInPrimaryRequest = string.IsNullOrEmpty(parentEntry.Key) && IsAtPrimaryEndpoint(request); + bool isTopResourceInPrimaryRequest = string.IsNullOrEmpty(parentEntry.Key) && IsAtPrimaryEndpoint(request); + if (!isTopResourceInPrimaryRequest) { return false; } var httpContextAccessor = _serviceProvider.GetRequiredService(); + if (httpContextAccessor.HttpContext.Request.Method == HttpMethods.Patch || request.OperationKind == OperationKind.UpdateResource) { var targetedFields = _serviceProvider.GetRequiredService(); diff --git a/src/JsonApiDotNetCore/Configuration/RequestScopedServiceProvider.cs b/src/JsonApiDotNetCore/Configuration/RequestScopedServiceProvider.cs index 0bd2b71477..649a219c0b 100644 --- a/src/JsonApiDotNetCore/Configuration/RequestScopedServiceProvider.cs +++ b/src/JsonApiDotNetCore/Configuration/RequestScopedServiceProvider.cs @@ -22,8 +22,7 @@ public object GetService(Type serviceType) if (_httpContextAccessor.HttpContext == null) { - throw new InvalidOperationException( - $"Cannot resolve scoped service '{serviceType.FullName}' outside the context of an HTTP request. " + + throw new InvalidOperationException($"Cannot resolve scoped service '{serviceType.FullName}' outside the context of an HTTP request. " + "If you are hitting this error in automated tests, you should instead inject your own " + "IRequestScopedServiceProvider implementation. See the GitHub repository for how we do this internally. " + "https://github.com/json-api-dotnet/JsonApiDotNetCore/search?q=TestScopedServiceProvider&unscoped_q=TestScopedServiceProvider"); diff --git a/src/JsonApiDotNetCore/Configuration/ResourceContext.cs b/src/JsonApiDotNetCore/Configuration/ResourceContext.cs index 355c11e3ac..4ba2975a10 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceContext.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceContext.cs @@ -12,6 +12,8 @@ namespace JsonApiDotNetCore.Configuration [PublicAPI] public class ResourceContext { + private IReadOnlyCollection _fields; + /// /// The publicly exposed resource name. /// @@ -28,14 +30,12 @@ public class ResourceContext public Type IdentityType { get; set; } /// - /// Exposed resource attributes. - /// See https://jsonapi.org/format/#document-resource-object-attributes. + /// Exposed resource attributes. See https://jsonapi.org/format/#document-resource-object-attributes. /// public IReadOnlyCollection Attributes { get; set; } /// - /// Exposed resource relationships. - /// See https://jsonapi.org/format/#document-resource-object-relationships. + /// Exposed resource relationships. See https://jsonapi.org/format/#document-resource-object-relationships. /// public IReadOnlyCollection Relationships { get; set; } @@ -44,42 +44,36 @@ public class ResourceContext /// public IReadOnlyCollection EagerLoads { get; set; } - private IReadOnlyCollection _fields; - /// - /// Exposed resource attributes and relationships. - /// See https://jsonapi.org/format/#document-resource-object-fields. + /// Exposed resource attributes and relationships. See https://jsonapi.org/format/#document-resource-object-fields. /// public IReadOnlyCollection Fields => _fields ??= Attributes.Cast().Concat(Relationships).ToArray(); /// - /// Configures which links to show in the - /// object for this resource type. - /// Defaults to , which falls back to . + /// Configures which links to show in the object for this resource type. Defaults to + /// , which falls back to . /// /// - /// In the process of building the resource graph, this value is set based on usage. + /// In the process of building the resource graph, this value is set based on usage. /// public LinkTypes TopLevelLinks { get; internal set; } = LinkTypes.NotConfigured; /// - /// Configures which links to show in the - /// object for this resource type. - /// Defaults to , which falls back to . + /// Configures which links to show in the object for this resource type. Defaults to + /// , which falls back to . /// /// - /// In the process of building the resource graph, this value is set based on usage. + /// In the process of building the resource graph, this value is set based on usage. /// public LinkTypes ResourceLinks { get; internal set; } = LinkTypes.NotConfigured; /// - /// Configures which links to show in the - /// object for all relationships of this resource type. - /// Defaults to , which falls back to . - /// This can be overruled per relationship by setting . + /// Configures which links to show in the object for all relationships of this resource type. + /// Defaults to , which falls back to . This can be overruled per + /// relationship by setting . /// /// - /// In the process of building the resource graph, this value is set based on usage. + /// In the process of building the resource graph, this value is set based on usage. /// public LinkTypes RelationshipLinks { get; internal set; } = LinkTypes.NotConfigured; diff --git a/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs b/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs index 56f7450214..667d6680fa 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -9,7 +10,8 @@ namespace JsonApiDotNetCore.Configuration /// internal sealed class ResourceDescriptorAssemblyCache { - private readonly Dictionary> _resourceDescriptorsPerAssembly = new Dictionary>(); + private readonly Dictionary> _resourceDescriptorsPerAssembly = + new Dictionary>(); public void RegisterAssembly(Assembly assembly) { @@ -28,8 +30,7 @@ public void RegisterAssembly(Assembly assembly) private void EnsureAssembliesScanned() { - foreach (var assemblyToScan in _resourceDescriptorsPerAssembly.Where(pair => pair.Value == null) - .Select(pair => pair.Key).ToArray()) + foreach (Assembly assemblyToScan in _resourceDescriptorsPerAssembly.Where(pair => pair.Value == null).Select(pair => pair.Key).ToArray()) { _resourceDescriptorsPerAssembly[assemblyToScan] = ScanForResourceDescriptors(assemblyToScan).ToArray(); } @@ -37,9 +38,10 @@ private void EnsureAssembliesScanned() private static IEnumerable ScanForResourceDescriptors(Assembly assembly) { - foreach (var type in assembly.GetTypes()) + foreach (Type type in assembly.GetTypes()) { - var resourceDescriptor = TypeLocator.TryGetResourceDescriptor(type); + ResourceDescriptor resourceDescriptor = TypeLocator.TryGetResourceDescriptor(type); + if (resourceDescriptor != null) { yield return resourceDescriptor; diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs index a52af699c1..3f56e3fffe 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using JetBrains.Annotations; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; @@ -12,8 +13,8 @@ namespace JsonApiDotNetCore.Configuration [PublicAPI] public class ResourceGraph : IResourceGraph { - private readonly IReadOnlyCollection _resources; private static readonly Type ProxyTargetAccessorType = Type.GetType("Castle.DynamicProxy.IProxyTargetAccessor, Castle.Core"); + private readonly IReadOnlyCollection _resources; public ResourceGraph(IReadOnlyCollection resources) { @@ -23,7 +24,10 @@ public ResourceGraph(IReadOnlyCollection resources) } /// - public IReadOnlyCollection GetResourceContexts() => _resources; + public IReadOnlyCollection GetResourceContexts() + { + return _resources; + } /// public ResourceContext GetResourceContext(string resourceName) @@ -44,23 +48,29 @@ public ResourceContext GetResourceContext(Type resourceType) } /// - public ResourceContext GetResourceContext() where TResource : class, IIdentifiable - => GetResourceContext(typeof(TResource)); + public ResourceContext GetResourceContext() + where TResource : class, IIdentifiable + { + return GetResourceContext(typeof(TResource)); + } /// - public IReadOnlyCollection GetFields(Expression> selector = null) where TResource : class, IIdentifiable + public IReadOnlyCollection GetFields(Expression> selector = null) + where TResource : class, IIdentifiable { return Getter(selector); } /// - public IReadOnlyCollection GetAttributes(Expression> selector = null) where TResource : class, IIdentifiable + public IReadOnlyCollection GetAttributes(Expression> selector = null) + where TResource : class, IIdentifiable { return Getter(selector, FieldFilterType.Attribute).Cast().ToArray(); } /// - public IReadOnlyCollection GetRelationships(Expression> selector = null) where TResource : class, IIdentifiable + public IReadOnlyCollection GetRelationships(Expression> selector = null) + where TResource : class, IIdentifiable { return Getter(selector, FieldFilterType.Relationship).Cast().ToArray(); } @@ -99,14 +109,15 @@ public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relati return null; } - return GetResourceContext(relationship.RightType) - .Relationships - .SingleOrDefault(r => r.Property == relationship.InverseNavigationProperty); + return GetResourceContext(relationship.RightType).Relationships.SingleOrDefault(r => r.Property == relationship.InverseNavigationProperty); } - private IReadOnlyCollection Getter(Expression> selector = null, FieldFilterType type = FieldFilterType.None) where TResource : class, IIdentifiable + private IReadOnlyCollection Getter(Expression> selector = null, + FieldFilterType type = FieldFilterType.None) + where TResource : class, IIdentifiable { IReadOnlyCollection available; + if (type == FieldFilterType.Attribute) { available = GetResourceContext(typeof(TResource)).Attributes; @@ -127,10 +138,10 @@ private IReadOnlyCollection Getter(Expression var targeted = new List(); - var selectorBody = RemoveConvert(selector.Body); + Expression selectorBody = RemoveConvert(selector.Body); if (selectorBody is MemberExpression memberExpression) - { + { // model => model.Field1 try { @@ -144,9 +155,10 @@ private IReadOnlyCollection Getter(Expression } if (selectorBody is NewExpression newExpression) - { + { // model => new { model.Field1, model.Field2 } string memberName = null; + try { if (newExpression.Members == null) @@ -154,11 +166,12 @@ private IReadOnlyCollection Getter(Expression return targeted; } - foreach (var member in newExpression.Members) + foreach (MemberInfo member in newExpression.Members) { memberName = member.Name; targeted.Add(available.Single(f => f.Property.Name == memberName)); } + return targeted; } catch (InvalidOperationException) @@ -167,17 +180,18 @@ private IReadOnlyCollection Getter(Expression } } - throw new ArgumentException( - $"The expression '{selector}' should select a single property or select multiple properties into an anonymous type. " + + throw new ArgumentException($"The expression '{selector}' should select a single property or select multiple properties into an anonymous type. " + "For example: 'article => article.Title' or 'article => new { article.Title, article.PageCount }'."); } - private bool IsLazyLoadingProxyForResourceType(Type resourceType) => - ProxyTargetAccessorType?.IsAssignableFrom(resourceType) ?? false; + private bool IsLazyLoadingProxyForResourceType(Type resourceType) + { + return ProxyTargetAccessorType?.IsAssignableFrom(resourceType) ?? false; + } private static Expression RemoveConvert(Expression expression) { - var innerExpression = expression; + Expression innerExpression = expression; while (true) { diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs index 7b07847682..a6bb501ffd 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs @@ -11,7 +11,7 @@ namespace JsonApiDotNetCore.Configuration { /// - /// Builds and configures the . + /// Builds and configures the . /// [PublicAPI] public class ResourceGraphBuilder @@ -30,7 +30,7 @@ public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactor } /// - /// Constructs the . + /// Constructs the . /// public IResourceGraph Build() { @@ -41,6 +41,7 @@ public IResourceGraph Build() private void SetResourceLinksOptions(ResourceContext resourceContext) { var attribute = (ResourceLinksAttribute)resourceContext.ResourceType.GetCustomAttribute(typeof(ResourceLinksAttribute)); + if (attribute != null) { resourceContext.RelationshipLinks = attribute.RelationshipLinks; @@ -48,38 +49,54 @@ private void SetResourceLinksOptions(ResourceContext resourceContext) resourceContext.TopLevelLinks = attribute.TopLevelLinks; } } - + /// /// Adds a JSON:API resource with int as the identifier type. /// - /// The resource model type. + /// + /// The resource model type. + /// /// - /// The name under which the resource is publicly exposed by the API. - /// If nothing is specified, the configured naming convention formatter will be applied. + /// The name under which the resource is publicly exposed by the API. If nothing is specified, the configured naming convention formatter will be + /// applied. /// - public ResourceGraphBuilder Add(string publicName = null) where TResource : class, IIdentifiable - => Add(publicName); - + public ResourceGraphBuilder Add(string publicName = null) + where TResource : class, IIdentifiable + { + return Add(publicName); + } + /// /// Adds a JSON:API resource. /// - /// The resource model type. - /// The resource model identifier type. + /// + /// The resource model type. + /// + /// + /// The resource model identifier type. + /// /// - /// The name under which the resource is publicly exposed by the API. - /// If nothing is specified, the configured naming convention formatter will be applied. + /// The name under which the resource is publicly exposed by the API. If nothing is specified, the configured naming convention formatter will be + /// applied. /// - public ResourceGraphBuilder Add(string publicName = null) where TResource : class, IIdentifiable - => Add(typeof(TResource), typeof(TId), publicName); - + public ResourceGraphBuilder Add(string publicName = null) + where TResource : class, IIdentifiable + { + return Add(typeof(TResource), typeof(TId), publicName); + } + /// /// Adds a JSON:API resource. /// - /// The resource model type. - /// The resource model identifier type. + /// + /// The resource model type. + /// + /// + /// The resource model identifier type. + /// /// - /// The name under which the resource is publicly exposed by the API. - /// If nothing is specified, the configured naming convention formatter will be applied. + /// The name under which the resource is publicly exposed by the API. If nothing is specified, the configured naming convention formatter will be + /// applied. /// public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string publicName = null) { @@ -92,10 +109,10 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu if (TypeHelper.IsOrImplementsInterface(resourceType, typeof(IIdentifiable))) { - var effectivePublicName = publicName ?? FormatResourceName(resourceType); - var effectiveIdType = idType ?? TypeLocator.TryGetIdType(resourceType); - - var resourceContext = CreateResourceContext(effectivePublicName, resourceType, effectiveIdType); + string effectivePublicName = publicName ?? FormatResourceName(resourceType); + Type effectiveIdType = idType ?? TypeLocator.TryGetIdType(resourceType); + + ResourceContext resourceContext = CreateResourceContext(effectivePublicName, resourceType, effectiveIdType); _resources.Add(resourceContext); } else @@ -106,15 +123,18 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu return this; } - private ResourceContext CreateResourceContext(string publicName, Type resourceType, Type idType) => new ResourceContext + private ResourceContext CreateResourceContext(string publicName, Type resourceType, Type idType) { - PublicName = publicName, - ResourceType = resourceType, - IdentityType = idType, - Attributes = GetAttributes(resourceType), - Relationships = GetRelationships(resourceType), - EagerLoads = GetEagerLoads(resourceType) - }; + return new ResourceContext + { + PublicName = publicName, + ResourceType = resourceType, + IdentityType = idType, + Attributes = GetAttributes(resourceType), + Relationships = GetRelationships(resourceType), + EagerLoads = GetEagerLoads(resourceType) + }; + } private IReadOnlyCollection GetAttributes(Type resourceType) { @@ -122,7 +142,7 @@ private IReadOnlyCollection GetAttributes(Type resourceType) var attributes = new List(); - foreach (var property in resourceType.GetProperties()) + foreach (PropertyInfo property in resourceType.GetProperties()) { var attribute = (AttrAttribute)property.GetCustomAttribute(typeof(AttrAttribute)); @@ -137,6 +157,7 @@ private IReadOnlyCollection GetAttributes(Type resourceType) Property = property, Capabilities = _options.DefaultAttrCapabilities }; + attributes.Add(idAttr); continue; } @@ -156,6 +177,7 @@ private IReadOnlyCollection GetAttributes(Type resourceType) attributes.Add(attribute); } + return attributes; } @@ -164,10 +186,12 @@ private IReadOnlyCollection GetRelationships(Type resourc ArgumentGuard.NotNull(resourceType, nameof(resourceType)); var attributes = new List(); - var properties = resourceType.GetProperties(); - foreach (var prop in properties) + PropertyInfo[] properties = resourceType.GetProperties(); + + foreach (PropertyInfo prop in properties) { var attribute = (RelationshipAttribute)prop.GetCustomAttribute(typeof(RelationshipAttribute)); + if (attribute == null) { continue; @@ -181,14 +205,16 @@ private IReadOnlyCollection GetRelationships(Type resourc if (attribute is HasManyThroughAttribute hasManyThroughAttribute) { - var throughProperty = properties.SingleOrDefault(p => p.Name == hasManyThroughAttribute.ThroughPropertyName); + PropertyInfo throughProperty = properties.SingleOrDefault(p => p.Name == hasManyThroughAttribute.ThroughPropertyName); + if (throughProperty == null) { throw new InvalidConfigurationException($"Invalid {nameof(HasManyThroughAttribute)} on '{resourceType}.{attribute.Property.Name}': " + $"Resource does not contain a property named '{hasManyThroughAttribute.ThroughPropertyName}'."); } - var throughType = TryGetThroughType(throughProperty); + Type throughType = TryGetThroughType(throughProperty); + if (throughType == null) { throw new InvalidConfigurationException($"Invalid {nameof(HasManyThroughAttribute)} on '{resourceType}.{attribute.Property.Name}': " + @@ -201,45 +227,52 @@ private IReadOnlyCollection GetRelationships(Type resourc // ArticleTag hasManyThroughAttribute.ThroughType = throughType; - var throughProperties = throughType.GetProperties(); + PropertyInfo[] throughProperties = throughType.GetProperties(); // ArticleTag.Article if (hasManyThroughAttribute.LeftPropertyName != null) { // In case of a self-referencing many-to-many relationship, the left property name must be specified. - hasManyThroughAttribute.LeftProperty = hasManyThroughAttribute.ThroughType.GetProperty(hasManyThroughAttribute.LeftPropertyName) - ?? throw new InvalidConfigurationException($"'{throughType}' does not contain a navigation property named '{hasManyThroughAttribute.LeftPropertyName}'."); + hasManyThroughAttribute.LeftProperty = hasManyThroughAttribute.ThroughType.GetProperty(hasManyThroughAttribute.LeftPropertyName) ?? + throw new InvalidConfigurationException( + $"'{throughType}' does not contain a navigation property named '{hasManyThroughAttribute.LeftPropertyName}'."); } else { // In case of a non-self-referencing many-to-many relationship, we just pick the single compatible type. - hasManyThroughAttribute.LeftProperty = throughProperties.SingleOrDefault(x => x.PropertyType.IsAssignableFrom(resourceType)) - ?? throw new InvalidConfigurationException($"'{throughType}' does not contain a navigation property to type '{resourceType}'."); + hasManyThroughAttribute.LeftProperty = throughProperties.SingleOrDefault(x => x.PropertyType.IsAssignableFrom(resourceType)) ?? + throw new InvalidConfigurationException($"'{throughType}' does not contain a navigation property to type '{resourceType}'."); } // ArticleTag.ArticleId - var leftIdPropertyName = hasManyThroughAttribute.LeftIdPropertyName ?? hasManyThroughAttribute.LeftProperty.Name + "Id"; - hasManyThroughAttribute.LeftIdProperty = throughProperties.SingleOrDefault(x => x.Name == leftIdPropertyName) - ?? throw new InvalidConfigurationException($"'{throughType}' does not contain a relationship ID property to type '{resourceType}' with name '{leftIdPropertyName}'."); + string leftIdPropertyName = hasManyThroughAttribute.LeftIdPropertyName ?? hasManyThroughAttribute.LeftProperty.Name + "Id"; + + hasManyThroughAttribute.LeftIdProperty = throughProperties.SingleOrDefault(x => x.Name == leftIdPropertyName) ?? + throw new InvalidConfigurationException( + $"'{throughType}' does not contain a relationship ID property to type '{resourceType}' with name '{leftIdPropertyName}'."); // ArticleTag.Tag if (hasManyThroughAttribute.RightPropertyName != null) { // In case of a self-referencing many-to-many relationship, the right property name must be specified. - hasManyThroughAttribute.RightProperty = hasManyThroughAttribute.ThroughType.GetProperty(hasManyThroughAttribute.RightPropertyName) - ?? throw new InvalidConfigurationException($"'{throughType}' does not contain a navigation property named '{hasManyThroughAttribute.RightPropertyName}'."); + hasManyThroughAttribute.RightProperty = hasManyThroughAttribute.ThroughType.GetProperty(hasManyThroughAttribute.RightPropertyName) ?? + throw new InvalidConfigurationException( + $"'{throughType}' does not contain a navigation property named '{hasManyThroughAttribute.RightPropertyName}'."); } else { // In case of a non-self-referencing many-to-many relationship, we just pick the single compatible type. - hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.RightType) - ?? throw new InvalidConfigurationException($"'{throughType}' does not contain a navigation property to type '{hasManyThroughAttribute.RightType}'."); + hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.RightType) ?? + throw new InvalidConfigurationException( + $"'{throughType}' does not contain a navigation property to type '{hasManyThroughAttribute.RightType}'."); } // ArticleTag.TagId - var rightIdPropertyName = hasManyThroughAttribute.RightIdPropertyName ?? hasManyThroughAttribute.RightProperty.Name + "Id"; - hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(x => x.Name == rightIdPropertyName) - ?? throw new InvalidConfigurationException($"'{throughType}' does not contain a relationship ID property to type '{hasManyThroughAttribute.RightType}' with name '{rightIdPropertyName}'."); + string rightIdPropertyName = hasManyThroughAttribute.RightIdPropertyName ?? hasManyThroughAttribute.RightProperty.Name + "Id"; + + hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(x => x.Name == rightIdPropertyName) ?? + throw new InvalidConfigurationException( + $"'{throughType}' does not contain a relationship ID property to type '{hasManyThroughAttribute.RightType}' with name '{rightIdPropertyName}'."); } } @@ -250,10 +283,12 @@ private Type TryGetThroughType(PropertyInfo throughProperty) { if (throughProperty.PropertyType.IsGenericType) { - var typeArguments = throughProperty.PropertyType.GetGenericArguments(); + Type[] typeArguments = throughProperty.PropertyType.GetGenericArguments(); + if (typeArguments.Length == 1) { - var constructedThroughType = typeof(ICollection<>).MakeGenericType(typeArguments[0]); + Type constructedThroughType = typeof(ICollection<>).MakeGenericType(typeArguments[0]); + if (TypeHelper.IsOrImplementsInterface(throughProperty.PropertyType, constructedThroughType)) { return typeArguments[0]; @@ -281,11 +316,12 @@ private IReadOnlyCollection GetEagerLoads(Type resourceType, } var attributes = new List(); - var properties = resourceType.GetProperties(); + PropertyInfo[] properties = resourceType.GetProperties(); - foreach (var property in properties) + foreach (PropertyInfo property in properties) { - var attribute = (EagerLoadAttribute) property.GetCustomAttribute(typeof(EagerLoadAttribute)); + var attribute = (EagerLoadAttribute)property.GetCustomAttribute(typeof(EagerLoadAttribute)); + if (attribute == null) { continue; @@ -303,8 +339,7 @@ private IReadOnlyCollection GetEagerLoads(Type resourceType, private Type TypeOrElementType(Type type) { - var interfaces = type.GetInterfaces() - .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)).ToArray(); + Type[] interfaces = type.GetInterfaces().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)).ToArray(); return interfaces.Length == 1 ? interfaces.Single().GenericTypeArguments[0] : type; } diff --git a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs index ac2e28b22a..a63d734fe4 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs @@ -19,17 +19,13 @@ public static class ServiceCollectionExtensions /// /// Configures JsonApiDotNetCore by registering resources manually. /// - public static IServiceCollection AddJsonApi(this IServiceCollection services, - Action options = null, - Action discovery = null, - Action resources = null, - IMvcCoreBuilder mvcBuilder = null, + public static IServiceCollection AddJsonApi(this IServiceCollection services, Action options = null, + Action discovery = null, Action resources = null, IMvcCoreBuilder mvcBuilder = null, ICollection dbContextTypes = null) { ArgumentGuard.NotNull(services, nameof(services)); - SetupApplicationBuilder(services, options, discovery, resources, mvcBuilder, - dbContextTypes ?? Array.Empty()); + SetupApplicationBuilder(services, options, discovery, resources, mvcBuilder, dbContextTypes ?? Array.Empty()); return services; } @@ -37,19 +33,16 @@ public static IServiceCollection AddJsonApi(this IServiceCollection services, /// /// Configures JsonApiDotNetCore by registering resources from an Entity Framework Core model. /// - public static IServiceCollection AddJsonApi(this IServiceCollection services, - Action options = null, - Action discovery = null, - Action resources = null, - IMvcCoreBuilder mvcBuilder = null) + public static IServiceCollection AddJsonApi(this IServiceCollection services, Action options = null, + Action discovery = null, Action resources = null, IMvcCoreBuilder mvcBuilder = null) where TDbContext : DbContext { return AddJsonApi(services, options, discovery, resources, mvcBuilder, typeof(TDbContext).AsArray()); } private static void SetupApplicationBuilder(IServiceCollection services, Action configureOptions, - Action configureAutoDiscovery, - Action configureResourceGraph, IMvcCoreBuilder mvcBuilder, ICollection dbContextTypes) + Action configureAutoDiscovery, Action configureResourceGraph, IMvcCoreBuilder mvcBuilder, + ICollection dbContextTypes) { using var applicationBuilder = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore()); @@ -60,28 +53,29 @@ private static void SetupApplicationBuilder(IServiceCollection services, Action< applicationBuilder.DiscoverInjectables(); applicationBuilder.ConfigureServiceContainer(dbContextTypes); } - + /// - /// Enables client serializers for sending requests and receiving responses - /// in JSON:API format. Internally only used for testing. - /// Will be extended in the future to be part of a JsonApiClientDotNetCore package. + /// Enables client serializers for sending requests and receiving responses in JSON:API format. Internally only used for testing. Will be extended in the + /// future to be part of a JsonApiClientDotNetCore package. /// public static IServiceCollection AddClientSerialization(this IServiceCollection services) { ArgumentGuard.NotNull(services, nameof(services)); services.AddScoped(); + services.AddScoped(sp => { var graph = sp.GetRequiredService(); return new RequestSerializer(graph, new ResourceObjectBuilder(graph, new ResourceObjectBuilderSettings())); }); + return services; } /// - /// Adds IoC container registrations for the various JsonApiDotNetCore resource service interfaces, - /// such as , and the various others. + /// Adds IoC container registrations for the various JsonApiDotNetCore resource service interfaces, such as , + /// and the various others. /// public static IServiceCollection AddResourceService(this IServiceCollection services) { @@ -93,8 +87,8 @@ public static IServiceCollection AddResourceService(this IServiceColle } /// - /// Adds IoC container registrations for the various JsonApiDotNetCore resource repository interfaces, - /// such as and . + /// Adds IoC container registrations for the various JsonApiDotNetCore resource repository interfaces, such as + /// and . /// public static IServiceCollection AddResourceRepository(this IServiceCollection services) { @@ -108,22 +102,23 @@ public static IServiceCollection AddResourceRepository(this IServic private static void RegisterForConstructedType(IServiceCollection services, Type implementationType, IEnumerable openGenericInterfaces) { bool seenCompatibleInterface = false; - var resourceDescriptor = TryGetResourceTypeFromServiceImplementation(implementationType); + ResourceDescriptor resourceDescriptor = TryGetResourceTypeFromServiceImplementation(implementationType); if (resourceDescriptor != null) { - foreach (var openGenericInterface in openGenericInterfaces) + foreach (Type openGenericInterface in openGenericInterfaces) { // A shorthand interface is one where the ID type is omitted. // e.g. IResourceService is the shorthand for IResourceService - var isShorthandInterface = openGenericInterface.GetTypeInfo().GenericTypeParameters.Length == 1; + bool isShorthandInterface = openGenericInterface.GetTypeInfo().GenericTypeParameters.Length == 1; + if (isShorthandInterface && resourceDescriptor.IdType != typeof(int)) { // We can't create a shorthand for ID types other than int. continue; } - var constructedType = isShorthandInterface + Type constructedType = isShorthandInterface ? openGenericInterface.MakeGenericType(resourceDescriptor.ResourceType) : openGenericInterface.MakeGenericType(resourceDescriptor.ResourceType, resourceDescriptor.IdType); @@ -137,21 +132,20 @@ private static void RegisterForConstructedType(IServiceCollection services, Type if (!seenCompatibleInterface) { - throw new InvalidConfigurationException( - $"{implementationType} does not implement any of the expected JsonApiDotNetCore interfaces.");} + throw new InvalidConfigurationException($"{implementationType} does not implement any of the expected JsonApiDotNetCore interfaces."); + } } private static ResourceDescriptor TryGetResourceTypeFromServiceImplementation(Type serviceType) { - foreach (var @interface in serviceType.GetInterfaces()) + foreach (Type @interface in serviceType.GetInterfaces()) { - var firstGenericArgument = @interface.IsGenericType - ? @interface.GenericTypeArguments.First() - : null; + Type firstGenericArgument = @interface.IsGenericType ? @interface.GenericTypeArguments.First() : null; if (firstGenericArgument != null) { - var resourceDescriptor = TypeLocator.TryGetResourceDescriptor(firstGenericArgument); + ResourceDescriptor resourceDescriptor = TypeLocator.TryGetResourceDescriptor(firstGenericArgument); + if (resourceDescriptor != null) { return resourceDescriptor; diff --git a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs index bdcd027a84..41e3a390bc 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs @@ -19,7 +19,8 @@ namespace JsonApiDotNetCore.Configuration [PublicAPI] public class ServiceDiscoveryFacade { - internal static readonly HashSet ServiceInterfaces = new HashSet { + internal static readonly HashSet ServiceInterfaces = new HashSet + { typeof(IResourceService<>), typeof(IResourceService<,>), typeof(IResourceCommandService<>), @@ -48,7 +49,8 @@ public class ServiceDiscoveryFacade typeof(IRemoveFromRelationshipService<,>) }; - internal static readonly HashSet RepositoryInterfaces = new HashSet { + internal static readonly HashSet RepositoryInterfaces = new HashSet + { typeof(IResourceRepository<>), typeof(IResourceRepository<,>), typeof(IResourceWriteRepository<>), @@ -57,7 +59,8 @@ public class ServiceDiscoveryFacade typeof(IResourceReadRepository<,>) }; - internal static readonly HashSet ResourceDefinitionInterfaces = new HashSet { + internal static readonly HashSet ResourceDefinitionInterfaces = new HashSet + { typeof(IResourceDefinition<>), typeof(IResourceDefinition<,>) }; @@ -68,7 +71,8 @@ public class ServiceDiscoveryFacade private readonly IJsonApiOptions _options; private readonly ResourceDescriptorAssemblyCache _assemblyCache = new ResourceDescriptorAssemblyCache(); - public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, IJsonApiOptions options, ILoggerFactory loggerFactory) + public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, IJsonApiOptions options, + ILoggerFactory loggerFactory) { ArgumentGuard.NotNull(services, nameof(services)); ArgumentGuard.NotNull(resourceGraphBuilder, nameof(resourceGraphBuilder)); @@ -84,7 +88,10 @@ public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder /// /// Mark the calling assembly for scanning of resources and injectables. /// - public ServiceDiscoveryFacade AddCurrentAssembly() => AddAssembly(Assembly.GetCallingAssembly()); + public ServiceDiscoveryFacade AddCurrentAssembly() + { + return AddAssembly(Assembly.GetCallingAssembly()); + } /// /// Mark the specified assembly for scanning of resources and injectables. @@ -101,7 +108,7 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly) internal void DiscoverResources() { - foreach (var resourceDescriptor in _assemblyCache.GetResourceDescriptorsPerAssembly().SelectMany(tuple => tuple.resourceDescriptors)) + foreach (ResourceDescriptor resourceDescriptor in _assemblyCache.GetResourceDescriptorsPerAssembly().SelectMany(tuple => tuple.resourceDescriptors)) { AddResource(resourceDescriptor); } @@ -109,7 +116,7 @@ internal void DiscoverResources() internal void DiscoverInjectables() { - foreach (var (assembly, resourceDescriptors) in _assemblyCache.GetResourceDescriptorsPerAssembly()) + foreach ((Assembly assembly, IReadOnlyCollection resourceDescriptors) in _assemblyCache.GetResourceDescriptorsPerAssembly()) { AddDbContextResolvers(assembly); AddInjectables(resourceDescriptors, assembly); @@ -118,7 +125,7 @@ internal void DiscoverInjectables() private void AddInjectables(IReadOnlyCollection resourceDescriptors, Assembly assembly) { - foreach (var resourceDescriptor in resourceDescriptors) + foreach (ResourceDescriptor resourceDescriptor in resourceDescriptors) { AddServices(assembly, resourceDescriptor); AddRepositories(assembly, resourceDescriptor); @@ -133,14 +140,15 @@ private void AddInjectables(IReadOnlyCollection resourceDesc private void AddDbContextResolvers(Assembly assembly) { - var dbContextTypes = TypeLocator.GetDerivedTypes(assembly, typeof(DbContext)); - foreach (var dbContextType in dbContextTypes) + IEnumerable dbContextTypes = TypeLocator.GetDerivedTypes(assembly, typeof(DbContext)); + + foreach (Type dbContextType in dbContextTypes) { - var resolverType = typeof(DbContextResolver<>).MakeGenericType(dbContextType); + Type resolverType = typeof(DbContextResolver<>).MakeGenericType(dbContextType); _services.AddScoped(typeof(IDbContextResolver), resolverType); } } - + private void AddResource(ResourceDescriptor resourceDescriptor) { _resourceGraphBuilder.Add(resourceDescriptor.ResourceType, resourceDescriptor.IdType); @@ -150,7 +158,7 @@ private void AddResourceHookDefinitions(Assembly assembly, ResourceDescriptor id { try { - var resourceDefinition = TypeLocator.GetDerivedGenericTypes(assembly, typeof(ResourceHooksDefinition<>), identifiable.ResourceType) + Type resourceDefinition = TypeLocator.GetDerivedGenericTypes(assembly, typeof(ResourceHooksDefinition<>), identifiable.ResourceType) .SingleOrDefault(); if (resourceDefinition != null) @@ -160,13 +168,14 @@ private void AddResourceHookDefinitions(Assembly assembly, ResourceDescriptor id } catch (InvalidOperationException e) { - throw new InvalidConfigurationException($"Cannot define multiple ResourceHooksDefinition<> implementations for '{identifiable.ResourceType}'", e); + throw new InvalidConfigurationException($"Cannot define multiple ResourceHooksDefinition<> implementations for '{identifiable.ResourceType}'", + e); } } private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor) { - foreach (var serviceInterface in ServiceInterfaces) + foreach (Type serviceInterface in ServiceInterfaces) { RegisterImplementations(assembly, serviceInterface, resourceDescriptor); } @@ -174,15 +183,15 @@ private void AddServices(Assembly assembly, ResourceDescriptor resourceDescripto private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescriptor) { - foreach (var repositoryInterface in RepositoryInterfaces) + foreach (Type repositoryInterface in RepositoryInterfaces) { RegisterImplementations(assembly, repositoryInterface, resourceDescriptor); } } - + private void AddResourceDefinitions(Assembly assembly, ResourceDescriptor resourceDescriptor) { - foreach (var resourceDefinitionInterface in ResourceDefinitionInterfaces) + foreach (Type resourceDefinitionInterface in ResourceDefinitionInterfaces) { RegisterImplementations(assembly, resourceDefinitionInterface, resourceDescriptor); } @@ -190,14 +199,16 @@ private void AddResourceDefinitions(Assembly assembly, ResourceDescriptor resour private void RegisterImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor) { - var genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2 + Type[] genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2 ? ArrayFactory.Create(resourceDescriptor.ResourceType, resourceDescriptor.IdType) : ArrayFactory.Create(resourceDescriptor.ResourceType); - var result = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); + (Type implementation, Type registrationInterface)? + result = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); + if (result != null) { - var (implementation, registrationInterface) = result.Value; + (Type implementation, Type registrationInterface) = result.Value; _services.AddScoped(registrationInterface, implementation); } } diff --git a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs index a3684b77ca..303cb32c43 100644 --- a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs +++ b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs @@ -12,11 +12,13 @@ namespace JsonApiDotNetCore.Configuration internal static class TypeLocator { /// - /// Attempts to lookup the ID type of the specified resource type. Returns null if it does not implement . + /// Attempts to lookup the ID type of the specified resource type. Returns null if it does not implement . /// public static Type TryGetIdType(Type resourceType) { - var identifiableInterface = resourceType.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IIdentifiable<>)); + Type identifiableInterface = resourceType.GetInterfaces() + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IIdentifiable<>)); + return identifiableInterface?.GetGenericArguments()[0]; } @@ -27,7 +29,8 @@ public static ResourceDescriptor TryGetResourceDescriptor(Type type) { if (TypeHelper.IsOrImplementsInterface(type, typeof(IIdentifiable))) { - var idType = TryGetIdType(type); + Type idType = TryGetIdType(type); + if (idType != null) { return new ResourceDescriptor(type, idType); @@ -40,15 +43,22 @@ public static ResourceDescriptor TryGetResourceDescriptor(Type type) /// /// Gets all implementations of a generic interface. /// - /// The assembly to search in. - /// The open generic interface. - /// Generic type parameters to construct the generic interface. + /// + /// The assembly to search in. + /// + /// + /// The open generic interface. + /// + /// + /// Generic type parameters to construct the generic interface. + /// /// /// ), typeof(Article), typeof(Guid)); /// ]]> /// - public static (Type implementation, Type registrationInterface)? GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterface, params Type[] interfaceGenericTypeArguments) + public static (Type implementation, Type registrationInterface)? GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterface, + params Type[] interfaceGenericTypeArguments) { ArgumentGuard.NotNull(assembly, nameof(assembly)); ArgumentGuard.NotNull(openGenericInterface, nameof(openGenericInterface)); @@ -57,16 +67,14 @@ public static (Type implementation, Type registrationInterface)? GetGenericInter if (!openGenericInterface.IsInterface || !openGenericInterface.IsGenericType || openGenericInterface != openGenericInterface.GetGenericTypeDefinition()) { - throw new ArgumentException( - $"Specified type '{openGenericInterface.FullName}' " + "is not an open generic interface.", + throw new ArgumentException($"Specified type '{openGenericInterface.FullName}' " + "is not an open generic interface.", nameof(openGenericInterface)); } if (interfaceGenericTypeArguments.Length != openGenericInterface.GetGenericArguments().Length) { throw new ArgumentException( - $"Interface '{openGenericInterface.FullName}' " + - $"requires {openGenericInterface.GetGenericArguments().Length} type parameters " + + $"Interface '{openGenericInterface.FullName}' " + $"requires {openGenericInterface.GetGenericArguments().Length} type parameters " + $"instead of {interfaceGenericTypeArguments.Length}.", nameof(interfaceGenericTypeArguments)); } @@ -74,15 +82,19 @@ public static (Type implementation, Type registrationInterface)? GetGenericInter .FirstOrDefault(result => result != null); } - private static (Type implementation, Type registrationInterface)? FindGenericInterfaceImplementationForType(Type nextType, Type openGenericInterface, Type[] interfaceGenericTypeArguments) + private static (Type implementation, Type registrationInterface)? FindGenericInterfaceImplementationForType(Type nextType, Type openGenericInterface, + Type[] interfaceGenericTypeArguments) { - foreach (var nextGenericInterface in nextType.GetInterfaces().Where(type => type.IsGenericType)) + foreach (Type nextGenericInterface in nextType.GetInterfaces().Where(type => type.IsGenericType)) { - var nextOpenGenericInterface = nextGenericInterface.GetGenericTypeDefinition(); + Type nextOpenGenericInterface = nextGenericInterface.GetGenericTypeDefinition(); + if (nextOpenGenericInterface == openGenericInterface) { - var nextGenericArguments = nextGenericInterface.GetGenericArguments(); - if (nextGenericArguments.Length == interfaceGenericTypeArguments.Length && nextGenericArguments.SequenceEqual(interfaceGenericTypeArguments)) + Type[] nextGenericArguments = nextGenericInterface.GetGenericArguments(); + + if (nextGenericArguments.Length == interfaceGenericTypeArguments.Length && + nextGenericArguments.SequenceEqual(interfaceGenericTypeArguments)) { return (nextType, nextOpenGenericInterface.MakeGenericType(interfaceGenericTypeArguments)); } @@ -95,9 +107,15 @@ private static (Type implementation, Type registrationInterface)? FindGenericInt /// /// Gets all derivatives of the concrete, generic type. /// - /// The assembly to search. - /// The open generic type, e.g. `typeof(ResourceDefinition<>)`. - /// Parameters to the generic type. + /// + /// The assembly to search. + /// + /// + /// The open generic type, e.g. `typeof(ResourceDefinition<>)`. + /// + /// + /// Parameters to the generic type. + /// /// /// ), typeof(Article)) @@ -105,15 +123,19 @@ private static (Type implementation, Type registrationInterface)? FindGenericInt /// public static IReadOnlyCollection GetDerivedGenericTypes(Assembly assembly, Type openGenericType, params Type[] genericArguments) { - var genericType = openGenericType.MakeGenericType(genericArguments); + Type genericType = openGenericType.MakeGenericType(genericArguments); return GetDerivedTypes(assembly, genericType).ToArray(); } /// /// Gets all derivatives of the specified type. /// - /// The assembly to search. - /// The inherited type. + /// + /// The assembly to search. + /// + /// + /// The inherited type. + /// /// /// /// GetDerivedGenericTypes(assembly, typeof(DbContext)) @@ -121,7 +143,7 @@ public static IReadOnlyCollection GetDerivedGenericTypes(Assembly assembly /// public static IEnumerable GetDerivedTypes(Assembly assembly, Type inheritedType) { - foreach (var type in assembly.GetTypes()) + foreach (Type type in assembly.GetTypes()) { if (inheritedType.IsAssignableFrom(type)) { diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs index c325d96d47..e363b1aabb 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs @@ -21,9 +21,8 @@ namespace JsonApiDotNetCore.Controllers.Annotations [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface)] public sealed class DisableQueryStringAttribute : Attribute { - public IReadOnlyCollection ParameterNames { get; } - public static readonly DisableQueryStringAttribute Empty = new DisableQueryStringAttribute(StandardQueryStringParameters.None); + public IReadOnlyCollection ParameterNames { get; } /// /// Disables one or more of the builtin query parameters for a controller. @@ -34,8 +33,7 @@ public DisableQueryStringAttribute(StandardQueryStringParameters parameters) foreach (StandardQueryStringParameters value in Enum.GetValues(typeof(StandardQueryStringParameters))) { - if (value != StandardQueryStringParameters.None && value != StandardQueryStringParameters.All && - parameters.HasFlag(value)) + if (value != StandardQueryStringParameters.None && value != StandardQueryStringParameters.All && parameters.HasFlag(value)) { parameterNames.Add(value.ToString()); } @@ -45,9 +43,8 @@ public DisableQueryStringAttribute(StandardQueryStringParameters parameters) } /// - /// It is allowed to use a comma-separated list of strings to indicate which query parameters - /// should be disabled, because the user may have defined custom query parameters that are - /// not included in the enum. + /// It is allowed to use a comma-separated list of strings to indicate which query parameters should be disabled, because the user may have defined + /// custom query parameters that are not included in the enum. /// public DisableQueryStringAttribute(string parameterNames) { @@ -58,7 +55,7 @@ public DisableQueryStringAttribute(string parameterNames) public bool ContainsParameter(StandardQueryStringParameters parameter) { - var name = parameter.ToString(); + string name = parameter.ToString(); return ParameterNames.Contains(name); } } diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/DisableRoutingConventionAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/DisableRoutingConventionAttribute.cs index 4450aca1ce..5cd68b2e6f 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/DisableRoutingConventionAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/DisableRoutingConventionAttribute.cs @@ -13,5 +13,6 @@ namespace JsonApiDotNetCore.Controllers.Annotations [PublicAPI] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface)] public sealed class DisableRoutingConventionAttribute : Attribute - { } + { + } } diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/HttpReadOnlyAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/HttpReadOnlyAttribute.cs index 269086a586..b4c0fb7675 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/HttpReadOnlyAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/HttpReadOnlyAttribute.cs @@ -14,6 +14,11 @@ namespace JsonApiDotNetCore.Controllers.Annotations [PublicAPI] public sealed class HttpReadOnlyAttribute : HttpRestrictAttribute { - protected override string[] Methods { get; } = { "POST", "PATCH", "DELETE" }; + protected override string[] Methods { get; } = + { + "POST", + "PATCH", + "DELETE" + }; } } diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/HttpRestrictAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/HttpRestrictAttribute.cs index 00f4dbc195..c2534471f9 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/HttpRestrictAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/HttpRestrictAttribute.cs @@ -10,14 +10,12 @@ public abstract class HttpRestrictAttribute : ActionFilterAttribute { protected abstract string[] Methods { get; } - public override async Task OnActionExecutionAsync( - ActionExecutingContext context, - ActionExecutionDelegate next) + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { ArgumentGuard.NotNull(context, nameof(context)); ArgumentGuard.NotNull(next, nameof(next)); - var method = context.HttpContext.Request.Method; + string method = context.HttpContext.Request.Method; if (!CanExecuteAction(method)) { diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpDeleteAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpDeleteAttribute.cs index e898dc0118..93733d6885 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpDeleteAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpDeleteAttribute.cs @@ -14,6 +14,9 @@ namespace JsonApiDotNetCore.Controllers.Annotations [PublicAPI] public sealed class NoHttpDeleteAttribute : HttpRestrictAttribute { - protected override string[] Methods { get; } = { "DELETE" }; + protected override string[] Methods { get; } = + { + "DELETE" + }; } } diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpPatchAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpPatchAttribute.cs index 01dc0e31ba..29a84b386a 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpPatchAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpPatchAttribute.cs @@ -14,6 +14,9 @@ namespace JsonApiDotNetCore.Controllers.Annotations [PublicAPI] public sealed class NoHttpPatchAttribute : HttpRestrictAttribute { - protected override string[] Methods { get; } = { "PATCH" }; + protected override string[] Methods { get; } = + { + "PATCH" + }; } } diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpPostAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpPostAttribute.cs index 34a0c8a83c..1d47890739 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpPostAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/NoHttpPostAttribute.cs @@ -14,6 +14,9 @@ namespace JsonApiDotNetCore.Controllers.Annotations [PublicAPI] public sealed class NoHttpPostAttribute : HttpRestrictAttribute { - protected override string[] Methods { get; } = { "POST" }; + protected override string[] Methods { get; } = + { + "POST" + }; } } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 1d5640db00..d625abbdec 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -15,9 +15,14 @@ namespace JsonApiDotNetCore.Controllers /// /// Implements the foundational ASP.NET Core controller layer in the JsonApiDotNetCore architecture that delegates to a Resource Service. /// - /// The resource type. - /// The resource identifier type. - public abstract class BaseJsonApiController : CoreJsonApiController where TResource : class, IIdentifiable + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// + public abstract class BaseJsonApiController : CoreJsonApiController + where TResource : class, IIdentifiable { private readonly IJsonApiOptions _options; private readonly IGetAllService _getAll; @@ -35,40 +40,29 @@ public abstract class BaseJsonApiController : CoreJsonApiControl /// /// Creates an instance from a read/write service. /// - protected BaseJsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + protected BaseJsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : this(options, loggerFactory, resourceService, resourceService) - { } + { + } /// /// Creates an instance from separate services for reading and writing. /// - protected BaseJsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceQueryService queryService = null, + protected BaseJsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceQueryService queryService = null, IResourceCommandService commandService = null) - : this(options, loggerFactory, queryService, queryService, queryService, queryService, commandService, - commandService, commandService, commandService, commandService, commandService) - { } + : this(options, loggerFactory, queryService, queryService, queryService, queryService, commandService, commandService, commandService, + commandService, commandService, commandService) + { + } /// /// Creates an instance from separate services for the various individual read and write methods. /// - protected BaseJsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IGetAllService getAll = null, - IGetByIdService getById = null, - IGetSecondaryService getSecondary = null, - IGetRelationshipService getRelationship = null, - ICreateService create = null, - IAddToRelationshipService addToRelationship = null, - IUpdateService update = null, - ISetRelationshipService setRelationship = null, - IDeleteService delete = null, + protected BaseJsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IGetAllService getAll = null, + IGetByIdService getById = null, IGetSecondaryService getSecondary = null, + IGetRelationshipService getRelationship = null, ICreateService create = null, + IAddToRelationshipService addToRelationship = null, IUpdateService update = null, + ISetRelationshipService setRelationship = null, IDeleteService delete = null, IRemoveFromRelationshipService removeFromRelationship = null) { ArgumentGuard.NotNull(options, nameof(options)); @@ -89,8 +83,7 @@ protected BaseJsonApiController( } /// - /// Gets a collection of top-level (non-nested) resources. - /// Example: GET /articles HTTP/1.1 + /// Gets a collection of top-level (non-nested) resources. Example: GET /articles HTTP/1.1 /// public virtual async Task GetAsync(CancellationToken cancellationToken) { @@ -101,38 +94,41 @@ public virtual async Task GetAsync(CancellationToken cancellation throw new RequestMethodNotAllowedException(HttpMethod.Get); } - var resources = await _getAll.GetAsync(cancellationToken); + IReadOnlyCollection resources = await _getAll.GetAsync(cancellationToken); return Ok(resources); } /// - /// Gets a single top-level (non-nested) resource by ID. - /// Example: /articles/1 + /// Gets a single top-level (non-nested) resource by ID. Example: /articles/1 /// public virtual async Task GetAsync(TId id, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id}); + _traceWriter.LogMethodStart(new + { + id + }); if (_getById == null) { throw new RequestMethodNotAllowedException(HttpMethod.Get); } - var resource = await _getById.GetAsync(id, cancellationToken); + TResource resource = await _getById.GetAsync(id, cancellationToken); return Ok(resource); } /// - /// Gets a single resource or multiple resources at a nested endpoint. - /// Examples: - /// GET /articles/1/author HTTP/1.1 - /// GET /articles/1/revisions HTTP/1.1 + /// Gets a single resource or multiple resources at a nested endpoint. Examples: GET /articles/1/author HTTP/1.1 GET /articles/1/revisions HTTP/1.1 /// public virtual async Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id, relationshipName}); + _traceWriter.LogMethodStart(new + { + id, + relationshipName + }); ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); @@ -141,19 +137,21 @@ public virtual async Task GetSecondaryAsync(TId id, string relati throw new RequestMethodNotAllowedException(HttpMethod.Get); } - var relationship = await _getSecondary.GetSecondaryAsync(id, relationshipName, cancellationToken); + object relationship = await _getSecondary.GetSecondaryAsync(id, relationshipName, cancellationToken); return Ok(relationship); } /// - /// Gets a single resource relationship. - /// Example: GET /articles/1/relationships/author HTTP/1.1 - /// Example: GET /articles/1/relationships/revisions HTTP/1.1 + /// Gets a single resource relationship. Example: GET /articles/1/relationships/author HTTP/1.1 Example: GET /articles/1/relationships/revisions HTTP/1.1 /// public virtual async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id, relationshipName}); + _traceWriter.LogMethodStart(new + { + id, + relationshipName + }); ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); @@ -162,18 +160,20 @@ public virtual async Task GetRelationshipAsync(TId id, string rel throw new RequestMethodNotAllowedException(HttpMethod.Get); } - var rightResources = await _getRelationship.GetRelationshipAsync(id, relationshipName, cancellationToken); + object rightResources = await _getRelationship.GetRelationshipAsync(id, relationshipName, cancellationToken); return Ok(rightResources); } /// - /// Creates a new resource with attributes, relationships or both. - /// Example: POST /articles HTTP/1.1 + /// Creates a new resource with attributes, relationships or both. Example: POST /articles HTTP/1.1 /// public virtual async Task PostAsync([FromBody] TResource resource, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {resource}); + _traceWriter.LogMethodStart(new + { + resource + }); ArgumentGuard.NotNull(resource, nameof(resource)); @@ -189,12 +189,13 @@ public virtual async Task PostAsync([FromBody] TResource resource if (_options.ValidateModelState && !ModelState.IsValid) { - throw new InvalidModelStateException(ModelState, typeof(TResource), _options.IncludeExceptionStackTraceInErrors, _options.SerializerNamingStrategy); + throw new InvalidModelStateException(ModelState, typeof(TResource), _options.IncludeExceptionStackTraceInErrors, + _options.SerializerNamingStrategy); } - var newResource = await _create.CreateAsync(resource, cancellationToken); + TResource newResource = await _create.CreateAsync(resource, cancellationToken); - var resourceId = (newResource ?? resource).StringId; + string resourceId = (newResource ?? resource).StringId; string locationUrl = $"{HttpContext.Request.Path}/{resourceId}"; if (newResource == null) @@ -207,16 +208,29 @@ public virtual async Task PostAsync([FromBody] TResource resource } /// - /// Adds resources to a to-many relationship. - /// Example: POST /articles/1/revisions HTTP/1.1 + /// Adds resources to a to-many relationship. Example: POST /articles/1/revisions HTTP/1.1 /// - /// The identifier of the primary resource. - /// The relationship to add resources to. - /// The set of resources to add to the relationship. - /// Propagates notification that request handling should be canceled. - public virtual async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + /// + /// The identifier of the primary resource. + /// + /// + /// The relationship to add resources to. + /// + /// + /// The set of resources to add to the relationship. + /// + /// + /// Propagates notification that request handling should be canceled. + /// + public virtual async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id, relationshipName, secondaryResourceIds}); + _traceWriter.LogMethodStart(new + { + id, + relationshipName, + secondaryResourceIds + }); ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); @@ -232,13 +246,16 @@ public virtual async Task PostRelationshipAsync(TId id, string re } /// - /// Updates the attributes and/or relationships of an existing resource. - /// Only the values of sent attributes are replaced. And only the values of sent relationships are replaced. - /// Example: PATCH /articles/1 HTTP/1.1 + /// Updates the attributes and/or relationships of an existing resource. Only the values of sent attributes are replaced. And only the values of sent + /// relationships are replaced. Example: PATCH /articles/1 HTTP/1.1 /// public virtual async Task PatchAsync(TId id, [FromBody] TResource resource, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id, resource}); + _traceWriter.LogMethodStart(new + { + id, + resource + }); ArgumentGuard.NotNull(resource, nameof(resource)); @@ -249,25 +266,39 @@ public virtual async Task PatchAsync(TId id, [FromBody] TResource if (_options.ValidateModelState && !ModelState.IsValid) { - throw new InvalidModelStateException(ModelState, typeof(TResource), _options.IncludeExceptionStackTraceInErrors, _options.SerializerNamingStrategy); + throw new InvalidModelStateException(ModelState, typeof(TResource), _options.IncludeExceptionStackTraceInErrors, + _options.SerializerNamingStrategy); } - var updated = await _update.UpdateAsync(id, resource, cancellationToken); - return updated == null ? (IActionResult) NoContent() : Ok(updated); + TResource updated = await _update.UpdateAsync(id, resource, cancellationToken); + return updated == null ? (IActionResult)NoContent() : Ok(updated); } /// - /// Performs a complete replacement of a relationship on an existing resource. - /// Example: PATCH /articles/1/relationships/author HTTP/1.1 - /// Example: PATCH /articles/1/relationships/revisions HTTP/1.1 + /// Performs a complete replacement of a relationship on an existing resource. Example: PATCH /articles/1/relationships/author HTTP/1.1 Example: PATCH + /// /articles/1/relationships/revisions HTTP/1.1 /// - /// The identifier of the primary resource. - /// The relationship for which to perform a complete replacement. - /// The resource or set of resources to assign to the relationship. - /// Propagates notification that request handling should be canceled. - public virtual async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) + /// + /// The identifier of the primary resource. + /// + /// + /// The relationship for which to perform a complete replacement. + /// + /// + /// The resource or set of resources to assign to the relationship. + /// + /// + /// Propagates notification that request handling should be canceled. + /// + public virtual async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, + CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id, relationshipName, secondaryResourceIds}); + _traceWriter.LogMethodStart(new + { + id, + relationshipName, + secondaryResourceIds + }); ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); @@ -282,12 +313,14 @@ public virtual async Task PatchRelationshipAsync(TId id, string r } /// - /// Deletes an existing resource. - /// Example: DELETE /articles/1 HTTP/1.1 + /// Deletes an existing resource. Example: DELETE /articles/1 HTTP/1.1 /// public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id}); + _traceWriter.LogMethodStart(new + { + id + }); if (_delete == null) { @@ -300,16 +333,29 @@ public virtual async Task DeleteAsync(TId id, CancellationToken c } /// - /// Removes resources from a to-many relationship. - /// Example: DELETE /articles/1/relationships/revisions HTTP/1.1 + /// Removes resources from a to-many relationship. Example: DELETE /articles/1/relationships/revisions HTTP/1.1 /// - /// The identifier of the primary resource. - /// The relationship to remove resources from. - /// The set of resources to remove from the relationship. - /// Propagates notification that request handling should be canceled. - public virtual async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + /// + /// The identifier of the primary resource. + /// + /// + /// The relationship to remove resources from. + /// + /// + /// The set of resources to remove from the relationship. + /// + /// + /// Propagates notification that request handling should be canceled. + /// + public virtual async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id, relationshipName, secondaryResourceIds}); + _traceWriter.LogMethodStart(new + { + id, + relationshipName, + secondaryResourceIds + }); ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); @@ -326,41 +372,32 @@ public virtual async Task DeleteRelationshipAsync(TId id, string } /// - public abstract class BaseJsonApiController : BaseJsonApiController where TResource : class, IIdentifiable + public abstract class BaseJsonApiController : BaseJsonApiController + where TResource : class, IIdentifiable { /// - protected BaseJsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + protected BaseJsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService, resourceService) - { } + { + } /// - protected BaseJsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceQueryService queryService = null, + protected BaseJsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceQueryService queryService = null, IResourceCommandService commandService = null) : base(options, loggerFactory, queryService, commandService) - { } + { + } /// - protected BaseJsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IGetAllService getAll = null, - IGetByIdService getById = null, - IGetSecondaryService getSecondary = null, - IGetRelationshipService getRelationship = null, - ICreateService create = null, - IAddToRelationshipService addToRelationship = null, - IUpdateService update = null, - ISetRelationshipService setRelationship = null, - IDeleteService delete = null, + protected BaseJsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IGetAllService getAll = null, + IGetByIdService getById = null, IGetSecondaryService getSecondary = null, + IGetRelationshipService getRelationship = null, ICreateService create = null, + IAddToRelationshipService addToRelationship = null, IUpdateService update = null, + ISetRelationshipService setRelationship = null, IDeleteService delete = null, IRemoveFromRelationshipService removeFromRelationship = null) - : base(options, loggerFactory, getAll, getById, getSecondary, getRelationship, create, addToRelationship, update, - setRelationship, delete, removeFromRelationship) - { } + : base(options, loggerFactory, getAll, getById, getSecondary, getRelationship, create, addToRelationship, update, setRelationship, delete, + removeFromRelationship) + { + } } } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs index 719b281db6..5da8b2b4f5 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs @@ -16,8 +16,8 @@ namespace JsonApiDotNetCore.Controllers { /// - /// Implements the foundational ASP.NET Core controller layer in the JsonApiDotNetCore architecture for handling atomic:operations requests. - /// See https://jsonapi.org/ext/atomic/ for details. Delegates work to . + /// Implements the foundational ASP.NET Core controller layer in the JsonApiDotNetCore architecture for handling atomic:operations requests. See + /// https://jsonapi.org/ext/atomic/ for details. Delegates work to . /// [PublicAPI] public abstract class BaseJsonApiOperationsController : CoreJsonApiController @@ -28,8 +28,8 @@ public abstract class BaseJsonApiOperationsController : CoreJsonApiController private readonly ITargetedFields _targetedFields; private readonly TraceLogWriter _traceWriter; - protected BaseJsonApiOperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IOperationsProcessor processor, IJsonApiRequest request, ITargetedFields targetedFields) + protected BaseJsonApiOperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, + IJsonApiRequest request, ITargetedFields targetedFields) { ArgumentGuard.NotNull(options, nameof(options)); ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); @@ -45,9 +45,8 @@ protected BaseJsonApiOperationsController(IJsonApiOptions options, ILoggerFactor } /// - /// Atomically processes a list of operations and returns a list of results. - /// All changes are reverted if processing fails. - /// If processing succeeds but none of the operations returns any data, then HTTP 201 is returned instead of 200. + /// Atomically processes a list of operations and returns a list of results. All changes are reverted if processing fails. If processing succeeds but + /// none of the operations returns any data, then HTTP 201 is returned instead of 200. /// /// /// The next example creates a new resource. @@ -66,7 +65,8 @@ protected BaseJsonApiOperationsController(IJsonApiOptions options, ILoggerFactor /// } /// }] /// } - /// ]]> + /// ]]> + /// /// /// The next example updates an existing resource. /// + /// ]]> + /// /// /// The next example deletes an existing resource. /// - public virtual async Task PostOperationsAsync([FromBody] IList operations, - CancellationToken cancellationToken) + /// ]]> + /// + public virtual async Task PostOperationsAsync([FromBody] IList operations, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {operations}); + _traceWriter.LogMethodStart(new + { + operations + }); ArgumentGuard.NotNull(operations, nameof(operations)); @@ -116,8 +120,8 @@ public virtual async Task PostOperationsAsync([FromBody] IList result != null) ? (IActionResult) Ok(results) : NoContent(); + IList results = await _processor.ProcessAsync(operations, cancellationToken); + return results.Any(result => result != null) ? (IActionResult)Ok(results) : NoContent(); } protected virtual void ValidateClientGeneratedIds(IEnumerable operations) @@ -125,7 +129,8 @@ protected virtual void ValidateClientGeneratedIds(IEnumerable operat var violations = new List(); int index = 0; - foreach (var operation in operations) + + foreach (OperationContainer operation in operations) { if (operation.Kind == OperationKind.CreateResource || operation.Kind == OperationKind.UpdateResource) { @@ -174,17 +180,18 @@ protected virtual void ValidateModelState(IEnumerable operat private static void AddValidationErrors(ModelStateDictionary modelState, Type resourceType, int operationIndex, List violations) { - foreach (var (propertyName, entry) in modelState) + foreach ((string propertyName, ModelStateEntry entry) in modelState) { AddValidationErrors(entry, propertyName, resourceType, operationIndex, violations); } } - private static void AddValidationErrors(ModelStateEntry entry, string propertyName, Type resourceType, int operationIndex, List violations) + private static void AddValidationErrors(ModelStateEntry entry, string propertyName, Type resourceType, int operationIndex, + List violations) { - foreach (var error in entry.Errors) + foreach (ModelError error in entry.Errors) { - var prefix = $"/atomic:operations[{operationIndex}]/data/attributes/"; + string prefix = $"/atomic:operations[{operationIndex}]/data/attributes/"; var violation = new ModelStateViolation(prefix, propertyName, resourceType, error); violations.Add(violation); diff --git a/src/JsonApiDotNetCore/Controllers/CoreJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/CoreJsonApiController.cs index 8406bdb55a..17db572550 100644 --- a/src/JsonApiDotNetCore/Controllers/CoreJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/CoreJsonApiController.cs @@ -24,7 +24,7 @@ protected IActionResult Error(IEnumerable errors) return new ObjectResult(document) { - StatusCode = (int) document.GetErrorStatusCode() + StatusCode = (int)document.GetErrorStatusCode() }; } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs index 1d6f801b06..cfc9399957 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs @@ -10,23 +10,26 @@ namespace JsonApiDotNetCore.Controllers { /// - /// The base class to derive resource-specific write-only controllers from. - /// This class delegates all work to but adds attributes for routing templates. - /// If you want to provide routing templates yourself, you should derive from BaseJsonApiController directly. + /// The base class to derive resource-specific write-only controllers from. This class delegates all work to + /// but adds attributes for routing templates. If you want to provide routing templates yourself, + /// you should derive from BaseJsonApiController directly. /// - /// The resource type. - /// The resource identifier type. - public abstract class JsonApiCommandController : BaseJsonApiController where TResource : class, IIdentifiable + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// + public abstract class JsonApiCommandController : BaseJsonApiController + where TResource : class, IIdentifiable { /// /// Creates an instance from a write-only service. /// - protected JsonApiCommandController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceCommandService commandService) + protected JsonApiCommandController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceCommandService commandService) : base(options, loggerFactory, null, commandService) - { } + { + } /// [HttpPost] @@ -37,8 +40,8 @@ public override async Task PostAsync([FromBody] TResource resourc /// [HttpPost("{id}/relationships/{relationshipName}")] - public override async Task PostRelationshipAsync( - TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + public override async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + CancellationToken cancellationToken) { return await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); } @@ -52,8 +55,8 @@ public override async Task PatchAsync(TId id, [FromBody] TResourc /// [HttpPatch("{id}/relationships/{relationshipName}")] - public override async Task PatchRelationshipAsync( - TId id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) + public override async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, + CancellationToken cancellationToken) { return await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); } @@ -67,21 +70,21 @@ public override async Task DeleteAsync(TId id, CancellationToken /// [HttpDelete("{id}/relationships/{relationshipName}")] - public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + CancellationToken cancellationToken) { return await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); } } /// - public abstract class JsonApiCommandController : JsonApiCommandController where TResource : class, IIdentifiable + public abstract class JsonApiCommandController : JsonApiCommandController + where TResource : class, IIdentifiable { /// - protected JsonApiCommandController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceCommandService commandService) + protected JsonApiCommandController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceCommandService commandService) : base(options, loggerFactory, commandService) - { } + { + } } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 793ba9cef2..8872e5447d 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -10,39 +10,35 @@ namespace JsonApiDotNetCore.Controllers { /// - /// The base class to derive resource-specific controllers from. - /// This class delegates all work to but adds attributes for routing templates. - /// If you want to provide routing templates yourself, you should derive from BaseJsonApiController directly. + /// The base class to derive resource-specific controllers from. This class delegates all work to + /// but adds attributes for routing templates. If you want to provide routing templates yourself, you should derive from BaseJsonApiController directly. /// - /// The resource type. - /// The resource identifier type. - public abstract class JsonApiController : BaseJsonApiController where TResource : class, IIdentifiable + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// + public abstract class JsonApiController : BaseJsonApiController + where TResource : class, IIdentifiable { /// - protected JsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + protected JsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) - { } + { + } /// - protected JsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IGetAllService getAll = null, - IGetByIdService getById = null, - IGetSecondaryService getSecondary = null, - IGetRelationshipService getRelationship = null, - ICreateService create = null, - IAddToRelationshipService addToRelationship = null, - IUpdateService update = null, - ISetRelationshipService setRelationship = null, - IDeleteService delete = null, + protected JsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IGetAllService getAll = null, + IGetByIdService getById = null, IGetSecondaryService getSecondary = null, + IGetRelationshipService getRelationship = null, ICreateService create = null, + IAddToRelationshipService addToRelationship = null, IUpdateService update = null, + ISetRelationshipService setRelationship = null, IDeleteService delete = null, IRemoveFromRelationshipService removeFromRelationship = null) - : base(options, loggerFactory,getAll, getById, getSecondary, getRelationship, create, addToRelationship, update, - setRelationship, delete, removeFromRelationship) - { } + : base(options, loggerFactory, getAll, getById, getSecondary, getRelationship, create, addToRelationship, update, setRelationship, delete, + removeFromRelationship) + { + } /// [HttpGet] @@ -81,8 +77,8 @@ public override async Task PostAsync([FromBody] TResource resourc /// [HttpPost("{id}/relationships/{relationshipName}")] - public override async Task PostRelationshipAsync( - TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + public override async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + CancellationToken cancellationToken) { return await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); } @@ -96,8 +92,8 @@ public override async Task PatchAsync(TId id, [FromBody] TResourc /// [HttpPatch("{id}/relationships/{relationshipName}")] - public override async Task PatchRelationshipAsync( - TId id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) + public override async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, + CancellationToken cancellationToken) { return await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); } @@ -111,39 +107,33 @@ public override async Task DeleteAsync(TId id, CancellationToken /// [HttpDelete("{id}/relationships/{relationshipName}")] - public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + CancellationToken cancellationToken) { return await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); } } /// - public abstract class JsonApiController : JsonApiController where TResource : class, IIdentifiable + public abstract class JsonApiController : JsonApiController + where TResource : class, IIdentifiable { /// - protected JsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) + protected JsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) - { } + { + } /// - protected JsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IGetAllService getAll = null, - IGetByIdService getById = null, - IGetSecondaryService getSecondary = null, - IGetRelationshipService getRelationship = null, - ICreateService create = null, - IAddToRelationshipService addToRelationship = null, - IUpdateService update = null, - ISetRelationshipService setRelationship = null, - IDeleteService delete = null, + protected JsonApiController(IJsonApiOptions options, ILoggerFactory loggerFactory, IGetAllService getAll = null, + IGetByIdService getById = null, IGetSecondaryService getSecondary = null, + IGetRelationshipService getRelationship = null, ICreateService create = null, + IAddToRelationshipService addToRelationship = null, IUpdateService update = null, + ISetRelationshipService setRelationship = null, IDeleteService delete = null, IRemoveFromRelationshipService removeFromRelationship = null) - : base(options, loggerFactory, getAll, getById, getSecondary, getRelationship, create, addToRelationship, update, - setRelationship, delete, removeFromRelationship) - { } + : base(options, loggerFactory, getAll, getById, getSecondary, getRelationship, create, addToRelationship, update, setRelationship, delete, + removeFromRelationship) + { + } } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs index a2aed59e8f..7e8e4956ac 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs @@ -11,22 +11,20 @@ namespace JsonApiDotNetCore.Controllers { /// - /// The base class to derive atomic:operations controllers from. - /// This class delegates all work to but adds attributes for routing templates. - /// If you want to provide routing templates yourself, you should derive from BaseJsonApiOperationsController directly. + /// The base class to derive atomic:operations controllers from. This class delegates all work to but adds + /// attributes for routing templates. If you want to provide routing templates yourself, you should derive from BaseJsonApiOperationsController directly. /// public abstract class JsonApiOperationsController : BaseJsonApiOperationsController { - protected JsonApiOperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IOperationsProcessor processor, IJsonApiRequest request, ITargetedFields targetedFields) + protected JsonApiOperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) : base(options, loggerFactory, processor, request, targetedFields) { } /// [HttpPost] - public override async Task PostOperationsAsync([FromBody] IList operations, - CancellationToken cancellationToken) + public override async Task PostOperationsAsync([FromBody] IList operations, CancellationToken cancellationToken) { return await base.PostOperationsAsync(operations, cancellationToken); } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs index e6f222db50..7ab85612c8 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs @@ -9,23 +9,26 @@ namespace JsonApiDotNetCore.Controllers { /// - /// The base class to derive resource-specific read-only controllers from. - /// This class delegates all work to but adds attributes for routing templates. - /// If you want to provide routing templates yourself, you should derive from BaseJsonApiController directly. + /// The base class to derive resource-specific read-only controllers from. This class delegates all work to + /// but adds attributes for routing templates. If you want to provide routing templates yourself, + /// you should derive from BaseJsonApiController directly. /// - /// The resource type. - /// The resource identifier type. - public abstract class JsonApiQueryController : BaseJsonApiController where TResource : class, IIdentifiable + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// + public abstract class JsonApiQueryController : BaseJsonApiController + where TResource : class, IIdentifiable { /// /// Creates an instance from a read-only service. /// - protected JsonApiQueryController( - IJsonApiOptions context, - ILoggerFactory loggerFactory, - IResourceQueryService queryService) + protected JsonApiQueryController(IJsonApiOptions context, ILoggerFactory loggerFactory, IResourceQueryService queryService) : base(context, loggerFactory, queryService) - { } + { + } /// [HttpGet] @@ -57,14 +60,13 @@ public override async Task GetRelationshipAsync(TId id, string re } /// - public abstract class JsonApiQueryController : JsonApiQueryController where TResource : class, IIdentifiable + public abstract class JsonApiQueryController : JsonApiQueryController + where TResource : class, IIdentifiable { /// - protected JsonApiQueryController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceQueryService queryService) + protected JsonApiQueryController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceQueryService queryService) : base(options, loggerFactory, queryService) - { } + { + } } } diff --git a/src/JsonApiDotNetCore/Errors/CannotClearRequiredRelationshipException.cs b/src/JsonApiDotNetCore/Errors/CannotClearRequiredRelationshipException.cs index e0a647a8b2..f528ed6c96 100644 --- a/src/JsonApiDotNetCore/Errors/CannotClearRequiredRelationshipException.cs +++ b/src/JsonApiDotNetCore/Errors/CannotClearRequiredRelationshipException.cs @@ -10,13 +10,13 @@ namespace JsonApiDotNetCore.Errors [PublicAPI] public sealed class CannotClearRequiredRelationshipException : JsonApiException { - public CannotClearRequiredRelationshipException(string relationshipName, string resourceId, - string resourceType) : base(new Error(HttpStatusCode.BadRequest) - { - Title = "Failed to clear a required relationship.", - Detail = $"The relationship '{relationshipName}' of resource type '{resourceType}' " + - $"with ID '{resourceId}' cannot be cleared because it is a required relationship." - }) + public CannotClearRequiredRelationshipException(string relationshipName, string resourceId, string resourceType) + : base(new Error(HttpStatusCode.BadRequest) + { + Title = "Failed to clear a required relationship.", + Detail = $"The relationship '{relationshipName}' of resource type '{resourceType}' " + + $"with ID '{resourceId}' cannot be cleared because it is a required relationship." + }) { } } diff --git a/src/JsonApiDotNetCore/Errors/InvalidConfigurationException.cs b/src/JsonApiDotNetCore/Errors/InvalidConfigurationException.cs index 884ed046a7..ae46c9bad5 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidConfigurationException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidConfigurationException.cs @@ -9,7 +9,9 @@ namespace JsonApiDotNetCore.Errors [PublicAPI] public sealed class InvalidConfigurationException : Exception { - public InvalidConfigurationException(string message, Exception innerException = null) - : base(message, innerException) { } + public InvalidConfigurationException(string message, Exception innerException = null) + : base(message, innerException) + { + } } } diff --git a/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs b/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs index a3db7f492b..de2e9deecb 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs @@ -19,12 +19,17 @@ namespace JsonApiDotNetCore.Errors [PublicAPI] public class InvalidModelStateException : JsonApiException { - public InvalidModelStateException(ModelStateDictionary modelState, Type resourceType, - bool includeExceptionStackTraceInErrors, NamingStrategy namingStrategy) + public InvalidModelStateException(ModelStateDictionary modelState, Type resourceType, bool includeExceptionStackTraceInErrors, + NamingStrategy namingStrategy) : this(FromModelStateDictionary(modelState, resourceType), includeExceptionStackTraceInErrors, namingStrategy) { } + public InvalidModelStateException(IEnumerable violations, bool includeExceptionStackTraceInErrors, NamingStrategy namingStrategy) + : base(FromModelStateViolations(violations, includeExceptionStackTraceInErrors, namingStrategy)) + { + } + private static IEnumerable FromModelStateDictionary(ModelStateDictionary modelState, Type resourceType) { ArgumentGuard.NotNull(modelState, nameof(modelState)); @@ -32,7 +37,7 @@ private static IEnumerable FromModelStateDictionary(ModelSt var violations = new List(); - foreach (var (propertyName, entry) in modelState) + foreach ((string propertyName, ModelStateEntry entry) in modelState) { AddValidationErrors(entry, propertyName, resourceType, violations); } @@ -40,8 +45,7 @@ private static IEnumerable FromModelStateDictionary(ModelSt return violations; } - private static void AddValidationErrors(ModelStateEntry entry, string propertyName, Type resourceType, - List violations) + private static void AddValidationErrors(ModelStateEntry entry, string propertyName, Type resourceType, List violations) { foreach (ModelError error in entry.Errors) { @@ -50,14 +54,8 @@ private static void AddValidationErrors(ModelStateEntry entry, string propertyNa } } - public InvalidModelStateException(IEnumerable violations, - bool includeExceptionStackTraceInErrors, NamingStrategy namingStrategy) - : base(FromModelStateViolations(violations, includeExceptionStackTraceInErrors, namingStrategy)) - { - } - - private static IEnumerable FromModelStateViolations(IEnumerable violations, - bool includeExceptionStackTraceInErrors, NamingStrategy namingStrategy) + private static IEnumerable FromModelStateViolations(IEnumerable violations, bool includeExceptionStackTraceInErrors, + NamingStrategy namingStrategy) { ArgumentGuard.NotNull(violations, nameof(violations)); ArgumentGuard.NotNull(namingStrategy, nameof(namingStrategy)); @@ -70,7 +68,7 @@ private static IEnumerable FromModelStateViolation(ModelStateViolation vi { if (violation.Error.Exception is JsonApiException jsonApiException) { - foreach (var error in jsonApiException.Errors) + foreach (Error error in jsonApiException.Errors) { yield return error; } @@ -78,16 +76,16 @@ private static IEnumerable FromModelStateViolation(ModelStateViolation vi else { string attributeName = GetDisplayNameForProperty(violation.PropertyName, violation.ResourceType, namingStrategy); - var attributePath = violation.Prefix + attributeName; + string attributePath = violation.Prefix + attributeName; yield return FromModelError(violation.Error, attributePath, includeExceptionStackTraceInErrors); } } - private static string GetDisplayNameForProperty(string propertyName, Type resourceType, - NamingStrategy namingStrategy) + private static string GetDisplayNameForProperty(string propertyName, Type resourceType, NamingStrategy namingStrategy) { PropertyInfo property = resourceType.GetProperty(propertyName); + if (property != null) { var attrAttribute = property.GetCustomAttribute(); @@ -97,8 +95,7 @@ private static string GetDisplayNameForProperty(string propertyName, Type resour return propertyName; } - private static Error FromModelError(ModelError modelError, string attributePath, - bool includeExceptionStackTraceInErrors) + private static Error FromModelError(ModelError modelError, string attributePath, bool includeExceptionStackTraceInErrors) { var error = new Error(HttpStatusCode.UnprocessableEntity) { diff --git a/src/JsonApiDotNetCore/Errors/InvalidQueryException.cs b/src/JsonApiDotNetCore/Errors/InvalidQueryException.cs index b776d2c683..5e704b8e3c 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidQueryException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidQueryException.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.Errors { /// - /// The error that is thrown when translating a to Entity Framework Core fails. + /// The error that is thrown when translating a to Entity Framework Core fails. /// [PublicAPI] public sealed class InvalidQueryException : JsonApiException diff --git a/src/JsonApiDotNetCore/Errors/InvalidQueryStringParameterException.cs b/src/JsonApiDotNetCore/Errors/InvalidQueryStringParameterException.cs index f0e2b146d6..99a4eb381e 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidQueryStringParameterException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidQueryStringParameterException.cs @@ -13,8 +13,7 @@ public sealed class InvalidQueryStringParameterException : JsonApiException { public string QueryParameterName { get; } - public InvalidQueryStringParameterException(string queryParameterName, string genericMessage, - string specificMessage, Exception innerException = null) + public InvalidQueryStringParameterException(string queryParameterName, string genericMessage, string specificMessage, Exception innerException = null) : base(new Error(HttpStatusCode.BadRequest) { Title = genericMessage, diff --git a/src/JsonApiDotNetCore/Errors/InvalidRequestBodyException.cs b/src/JsonApiDotNetCore/Errors/InvalidRequestBodyException.cs index b50101f6dc..fe860ac0fd 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidRequestBodyException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidRequestBodyException.cs @@ -15,9 +15,7 @@ public sealed class InvalidRequestBodyException : JsonApiException public InvalidRequestBodyException(string reason, string details, string requestBody, Exception innerException = null) : base(new Error(HttpStatusCode.UnprocessableEntity) { - Title = reason != null - ? "Failed to deserialize request body: " + reason - : "Failed to deserialize request body.", + Title = reason != null ? "Failed to deserialize request body: " + reason : "Failed to deserialize request body.", Detail = FormatErrorDetail(details, requestBody, innerException) }, innerException) { diff --git a/src/JsonApiDotNetCore/Errors/JsonApiException.cs b/src/JsonApiDotNetCore/Errors/JsonApiException.cs index 278aa1f4ab..55a88e287a 100644 --- a/src/JsonApiDotNetCore/Errors/JsonApiException.cs +++ b/src/JsonApiDotNetCore/Errors/JsonApiException.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCore.Errors { /// - /// The base class for an that represents one or more JSON:API error objects in an unsuccessful response. + /// The base class for an that represents one or more JSON:API error objects in an unsuccessful response. /// [PublicAPI] public class JsonApiException : Exception @@ -21,6 +21,8 @@ public class JsonApiException : Exception public IReadOnlyList Errors { get; } + public override string Message => "Errors = " + JsonConvert.SerializeObject(Errors, ErrorSerializerSettings); + public JsonApiException(Error error, Exception innerException = null) : base(null, innerException) { @@ -41,7 +43,5 @@ public JsonApiException(IEnumerable errors, Exception innerException = nu throw new ArgumentException("At least one error is required.", nameof(errors)); } } - - public override string Message => "Errors = " + JsonConvert.SerializeObject(Errors, ErrorSerializerSettings); } } diff --git a/src/JsonApiDotNetCore/Errors/MissingTransactionSupportException.cs b/src/JsonApiDotNetCore/Errors/MissingTransactionSupportException.cs index 980f6457fb..e092150ba5 100644 --- a/src/JsonApiDotNetCore/Errors/MissingTransactionSupportException.cs +++ b/src/JsonApiDotNetCore/Errors/MissingTransactionSupportException.cs @@ -5,8 +5,7 @@ namespace JsonApiDotNetCore.Errors { /// - /// The error that is thrown when accessing a repository that does not support transactions - /// during an atomic:operations request. + /// The error that is thrown when accessing a repository that does not support transactions during an atomic:operations request. /// [PublicAPI] public sealed class MissingTransactionSupportException : JsonApiException @@ -15,8 +14,7 @@ public MissingTransactionSupportException(string resourceType) : base(new Error(HttpStatusCode.UnprocessableEntity) { Title = "Unsupported resource type in atomic:operations request.", - Detail = $"Operations on resources of type '{resourceType}' " + - "cannot be used because transaction support is unavailable." + Detail = $"Operations on resources of type '{resourceType}' " + "cannot be used because transaction support is unavailable." }) { } diff --git a/src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs b/src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs index dbf699c301..5245c4344f 100644 --- a/src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs +++ b/src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs @@ -5,8 +5,7 @@ namespace JsonApiDotNetCore.Errors { /// - /// The error that is thrown when a repository does not participate in the overarching transaction - /// during an atomic:operations request. + /// The error that is thrown when a repository does not participate in the overarching transaction during an atomic:operations request. /// [PublicAPI] public sealed class NonSharedTransactionException : JsonApiException @@ -15,8 +14,7 @@ public NonSharedTransactionException() : base(new Error(HttpStatusCode.UnprocessableEntity) { Title = "Unsupported combination of resource types in atomic:operations request.", - Detail = "All operations need to participate in a single shared transaction, " + - "which is not the case for this request." + Detail = "All operations need to participate in a single shared transaction, " + "which is not the case for this request." }) { } diff --git a/src/JsonApiDotNetCore/Errors/RelationshipNotFoundException.cs b/src/JsonApiDotNetCore/Errors/RelationshipNotFoundException.cs index 091ff99586..864a0c7606 100644 --- a/src/JsonApiDotNetCore/Errors/RelationshipNotFoundException.cs +++ b/src/JsonApiDotNetCore/Errors/RelationshipNotFoundException.cs @@ -10,11 +10,12 @@ namespace JsonApiDotNetCore.Errors [PublicAPI] public sealed class RelationshipNotFoundException : JsonApiException { - public RelationshipNotFoundException(string relationshipName, string resourceType) : base(new Error(HttpStatusCode.NotFound) - { - Title = "The requested relationship does not exist.", - Detail = $"Resource of type '{resourceType}' does not contain a relationship named '{relationshipName}'." - }) + public RelationshipNotFoundException(string relationshipName, string resourceType) + : base(new Error(HttpStatusCode.NotFound) + { + Title = "The requested relationship does not exist.", + Detail = $"Resource of type '{resourceType}' does not contain a relationship named '{relationshipName}'." + }) { } } diff --git a/src/JsonApiDotNetCore/Errors/ResourceIdInCreateResourceNotAllowedException.cs b/src/JsonApiDotNetCore/Errors/ResourceIdInCreateResourceNotAllowedException.cs index efe5d80c72..4266f987b3 100644 --- a/src/JsonApiDotNetCore/Errors/ResourceIdInCreateResourceNotAllowedException.cs +++ b/src/JsonApiDotNetCore/Errors/ResourceIdInCreateResourceNotAllowedException.cs @@ -18,9 +18,7 @@ public ResourceIdInCreateResourceNotAllowedException(int? atomicOperationIndex = : "Specifying the resource ID in operations that create a resource is not allowed.", Source = { - Pointer = atomicOperationIndex != null - ? $"/atomic:operations[{atomicOperationIndex}]/data/id" - : "/data/id" + Pointer = atomicOperationIndex != null ? $"/atomic:operations[{atomicOperationIndex}]/data/id" : "/data/id" } }) { diff --git a/src/JsonApiDotNetCore/Errors/ResourceIdMismatchException.cs b/src/JsonApiDotNetCore/Errors/ResourceIdMismatchException.cs index b75366f102..721c17e7cd 100644 --- a/src/JsonApiDotNetCore/Errors/ResourceIdMismatchException.cs +++ b/src/JsonApiDotNetCore/Errors/ResourceIdMismatchException.cs @@ -14,8 +14,7 @@ public ResourceIdMismatchException(string bodyId, string endpointId, string requ : base(new Error(HttpStatusCode.Conflict) { Title = "Resource ID mismatch between request body and endpoint URL.", - Detail = $"Expected resource ID '{endpointId}' in PATCH request body " + - $"at endpoint '{requestPath}', instead of '{bodyId}'." + Detail = $"Expected resource ID '{endpointId}' in PATCH request body " + $"at endpoint '{requestPath}', instead of '{bodyId}'." }) { } diff --git a/src/JsonApiDotNetCore/Errors/ResourceTypeMismatchException.cs b/src/JsonApiDotNetCore/Errors/ResourceTypeMismatchException.cs index 78f490c593..83f28a14f9 100644 --- a/src/JsonApiDotNetCore/Errors/ResourceTypeMismatchException.cs +++ b/src/JsonApiDotNetCore/Errors/ResourceTypeMismatchException.cs @@ -12,13 +12,13 @@ namespace JsonApiDotNetCore.Errors [PublicAPI] public sealed class ResourceTypeMismatchException : JsonApiException { - public ResourceTypeMismatchException(HttpMethod method, string requestPath, ResourceContext expected, ResourceContext actual) + public ResourceTypeMismatchException(HttpMethod method, string requestPath, ResourceContext expected, ResourceContext actual) : base(new Error(HttpStatusCode.Conflict) - { - Title = "Resource type mismatch between request body and endpoint URL.", - Detail = $"Expected resource of type '{expected.PublicName}' in {method} " + - $"request body at endpoint '{requestPath}', instead of '{actual?.PublicName}'." - }) + { + Title = "Resource type mismatch between request body and endpoint URL.", + Detail = $"Expected resource of type '{expected.PublicName}' in {method} " + + $"request body at endpoint '{requestPath}', instead of '{actual?.PublicName}'." + }) { } } diff --git a/src/JsonApiDotNetCore/Errors/ResourcesInRelationshipsNotFoundException.cs b/src/JsonApiDotNetCore/Errors/ResourcesInRelationshipsNotFoundException.cs index 450ae4cdd5..94503ab343 100644 --- a/src/JsonApiDotNetCore/Errors/ResourcesInRelationshipsNotFoundException.cs +++ b/src/JsonApiDotNetCore/Errors/ResourcesInRelationshipsNotFoundException.cs @@ -22,8 +22,7 @@ private static Error CreateError(MissingResourceInRelationship missingResourceIn return new Error(HttpStatusCode.NotFound) { Title = "A related resource does not exist.", - Detail = - $"Related resource of type '{missingResourceInRelationship.ResourceType}' with ID '{missingResourceInRelationship.ResourceId}' " + + Detail = $"Related resource of type '{missingResourceInRelationship.ResourceType}' with ID '{missingResourceInRelationship.ResourceId}' " + $"in relationship '{missingResourceInRelationship.RelationshipName}' does not exist." }; } diff --git a/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs b/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs index 58c0219bf6..87e2a7856b 100644 --- a/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs +++ b/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs @@ -6,16 +6,16 @@ namespace JsonApiDotNetCore.Errors { /// - /// The error that is thrown when an with non-success status is returned from a controller method. + /// The error that is thrown when an with non-success status is returned from a controller method. /// [PublicAPI] public sealed class UnsuccessfulActionResultException : JsonApiException { - public UnsuccessfulActionResultException(HttpStatusCode status) + public UnsuccessfulActionResultException(HttpStatusCode status) : base(new Error(status) - { - Title = status.ToString() - }) + { + Title = status.ToString() + }) { } @@ -28,9 +28,7 @@ private static Error ToError(ProblemDetails problemDetails) { ArgumentGuard.NotNull(problemDetails, nameof(problemDetails)); - var status = problemDetails.Status != null - ? (HttpStatusCode) problemDetails.Status.Value - : HttpStatusCode.InternalServerError; + HttpStatusCode status = problemDetails.Status != null ? (HttpStatusCode)problemDetails.Status.Value : HttpStatusCode.InternalServerError; var error = new Error(status) { diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs b/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs index 720363cc82..f912de7d76 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using JetBrains.Annotations; using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Hooks.Internal.Execution; @@ -13,10 +14,12 @@ namespace JsonApiDotNetCore.Hooks.Internal.Discovery /// The default implementation for IHooksDiscovery /// [PublicAPI] - public class HooksDiscovery : IHooksDiscovery where TResource : class, IIdentifiable + public class HooksDiscovery : IHooksDiscovery + where TResource : class, IIdentifiable { private readonly Type _boundResourceDefinitionType = typeof(ResourceHooksDefinition); private readonly ResourceHook[] _allHooks; + private readonly ResourceHook[] _databaseValuesAttributeAllowed = { ResourceHook.BeforeUpdate, @@ -26,18 +29,17 @@ public class HooksDiscovery : IHooksDiscovery where TResou /// public ResourceHook[] ImplementedHooks { get; private set; } + public ResourceHook[] DatabaseValuesEnabledHooks { get; private set; } public ResourceHook[] DatabaseValuesDisabledHooks { get; private set; } public HooksDiscovery(IServiceProvider provider) { - _allHooks = Enum.GetValues(typeof(ResourceHook)) - .Cast() - .Where(h => h != ResourceHook.None) - .ToArray(); + _allHooks = Enum.GetValues(typeof(ResourceHook)).Cast().Where(h => h != ResourceHook.None).ToArray(); Type containerType; - using (var scope = provider.CreateScope()) + + using (IServiceScope scope = provider.CreateScope()) { containerType = scope.ServiceProvider.GetService(_boundResourceDefinitionType)?.GetType(); } @@ -48,7 +50,9 @@ public HooksDiscovery(IServiceProvider provider) /// /// Discovers the implemented hooks for a model. /// - /// The implemented hooks for model. + /// + /// The implemented hooks for model. + /// private void DiscoverImplementedHooks(Type containerType) { if (containerType == null || containerType == _boundResourceDefinitionType) @@ -58,18 +62,21 @@ private void DiscoverImplementedHooks(Type containerType) var implementedHooks = new List(); // this hook can only be used with enabled database values - var databaseValuesEnabledHooks = ResourceHook.BeforeImplicitUpdateRelationship.AsList(); + List databaseValuesEnabledHooks = ResourceHook.BeforeImplicitUpdateRelationship.AsList(); var databaseValuesDisabledHooks = new List(); - foreach (var hook in _allHooks) + + foreach (ResourceHook hook in _allHooks) { - var method = containerType.GetMethod(hook.ToString("G")); + MethodInfo? method = containerType.GetMethod(hook.ToString("G")); + if (method == null || method.DeclaringType == _boundResourceDefinitionType) { continue; } implementedHooks.Add(hook); - var attr = method.GetCustomAttributes(true).OfType().SingleOrDefault(); + LoadDatabaseValuesAttribute attr = method.GetCustomAttributes(true).OfType().SingleOrDefault(); + if (attr != null) { if (!_databaseValuesAttributeAllowed.Contains(hook)) @@ -77,7 +84,8 @@ private void DiscoverImplementedHooks(Type containerType) throw new InvalidConfigurationException($"{nameof(LoadDatabaseValuesAttribute)} cannot be used on hook" + $"{hook:G} in resource definition {containerType.Name}"); } - var targetList = attr.Value ? databaseValuesEnabledHooks : databaseValuesDisabledHooks; + + List targetList = attr.Value ? databaseValuesEnabledHooks : databaseValuesDisabledHooks; targetList.Add(hook); } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Discovery/IHooksDiscovery.cs b/src/JsonApiDotNetCore/Hooks/Internal/Discovery/IHooksDiscovery.cs index 7fede4e57b..c45244b491 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Discovery/IHooksDiscovery.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Discovery/IHooksDiscovery.cs @@ -6,11 +6,11 @@ namespace JsonApiDotNetCore.Hooks.Internal.Discovery { /// - /// A singleton service for a particular TResource that stores a field of - /// enums that represents which resource hooks have been implemented for that + /// A singleton service for a particular TResource that stores a field of enums that represents which resource hooks have been implemented for that /// particular resource. /// - public interface IHooksDiscovery : IHooksDiscovery where TResource : class, IIdentifiable + public interface IHooksDiscovery : IHooksDiscovery + where TResource : class, IIdentifiable { } @@ -19,8 +19,11 @@ public interface IHooksDiscovery /// /// A list of the implemented hooks for resource TResource /// - /// The implemented hooks. + /// + /// The implemented hooks. + /// ResourceHook[] ImplementedHooks { get; } + ResourceHook[] DatabaseValuesEnabledHooks { get; } ResourceHook[] DatabaseValuesDisabledHooks { get; } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs index 574966af47..a61fb4b6d5 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs @@ -12,16 +12,15 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution { [PublicAPI] - public sealed class DiffableResourceHashSet : ResourceHashSet, IDiffableResourceHashSet where TResource : class, IIdentifiable + public sealed class DiffableResourceHashSet : ResourceHashSet, IDiffableResourceHashSet + where TResource : class, IIdentifiable { private readonly HashSet _databaseValues; private readonly bool _databaseValuesLoaded; private readonly Dictionary> _updatedAttributes; - public DiffableResourceHashSet(HashSet requestResources, - HashSet databaseResources, - Dictionary> relationships, - Dictionary> updatedAttributes) + public DiffableResourceHashSet(HashSet requestResources, HashSet databaseResources, + Dictionary> relationships, Dictionary> updatedAttributes) : base(requestResources, relationships) { _databaseValues = databaseResources; @@ -32,14 +31,15 @@ public DiffableResourceHashSet(HashSet requestResources, /// /// Used internally by the ResourceHookExecutor to make live a bit easier with generics /// - internal DiffableResourceHashSet(IEnumerable requestResources, - IEnumerable databaseResources, - Dictionary relationships, - ITargetedFields targetedFields) - : this((HashSet)requestResources, (HashSet)databaseResources, TypeHelper.ConvertRelationshipDictionary(relationships), - targetedFields.Attributes == null ? null : TypeHelper.ConvertAttributeDictionary(targetedFields.Attributes, (HashSet)requestResources)) - { } - + internal DiffableResourceHashSet(IEnumerable requestResources, IEnumerable databaseResources, + Dictionary relationships, ITargetedFields targetedFields) + : this((HashSet)requestResources, (HashSet)databaseResources, + TypeHelper.ConvertRelationshipDictionary(relationships), + targetedFields.Attributes == null + ? null + : TypeHelper.ConvertAttributeDictionary(targetedFields.Attributes, (HashSet)requestResources)) + { + } /// public IEnumerable> GetDiffs() @@ -49,7 +49,7 @@ public IEnumerable> GetDiffs() ThrowNoDbValuesError(); } - foreach (var resource in this) + foreach (TResource resource in this) { TResource currentValueInDatabase = _databaseValues.Single(e => resource.StringId == e.StringId); yield return new ResourceDiffPair(resource, currentValueInDatabase); @@ -61,8 +61,9 @@ public override HashSet GetAffected(Expression GetAffected(Expression resources) - ? resources - : new HashSet(); + return _updatedAttributes.TryGetValue(propertyInfo, out HashSet resources) ? resources : new HashSet(); } private void ThrowNoDbValuesError() diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/HookExecutorHelper.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/HookExecutorHelper.cs index 222c8754e9..1f06736750 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/HookExecutorHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/HookExecutorHelper.cs @@ -49,6 +49,7 @@ public IResourceHookContainer GetResourceHookContainer(RightType targetResource, container = _genericProcessorFactory.Get(typeof(ResourceHooksDefinition<>), targetResource); _hookContainers[targetResource] = container; } + if (container == null) { return null; @@ -57,6 +58,7 @@ public IResourceHookContainer GetResourceHookContainer(RightType targetResource, // if there was a container, first check if it implements the hook we // want to use it for. IEnumerable targetHooks; + if (hook == ResourceHook.None) { CheckForTargetHookExistence(); @@ -74,36 +76,45 @@ public IResourceHookContainer GetResourceHookContainer(RightType targetResource, return container; } } + return null; } /// - public IResourceHookContainer GetResourceHookContainer(ResourceHook hook = ResourceHook.None) where TResource : class, IIdentifiable + public IResourceHookContainer GetResourceHookContainer(ResourceHook hook = ResourceHook.None) + where TResource : class, IIdentifiable { return (IResourceHookContainer)GetResourceHookContainer(typeof(TResource), hook); } - public IEnumerable LoadDbValues(LeftType resourceTypeForRepository, IEnumerable resources, ResourceHook hook, params RelationshipAttribute[] relationshipsToNextLayer) + public IEnumerable LoadDbValues(LeftType resourceTypeForRepository, IEnumerable resources, ResourceHook hook, + params RelationshipAttribute[] relationshipsToNextLayer) { - var idType = TypeHelper.GetIdType(resourceTypeForRepository); - var parameterizedGetWhere = GetType() - .GetMethod(nameof(GetWhereAndInclude), BindingFlags.NonPublic | BindingFlags.Instance)! - .MakeGenericMethod(resourceTypeForRepository, idType); - var cast = ((IEnumerable)resources).Cast(); - var ids = TypeHelper.CopyToList(cast.Select(i => i.GetTypedId()), idType); + LeftType idType = TypeHelper.GetIdType(resourceTypeForRepository); + + MethodInfo parameterizedGetWhere = + GetType().GetMethod(nameof(GetWhereAndInclude), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(resourceTypeForRepository, + idType); + + IEnumerable cast = ((IEnumerable)resources).Cast(); + IList ids = TypeHelper.CopyToList(cast.Select(i => i.GetTypedId()), idType); var values = (IEnumerable)parameterizedGetWhere.Invoke(this, ArrayFactory.Create(ids, relationshipsToNextLayer)); + if (values == null) { return null; } - return (IEnumerable)Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(resourceTypeForRepository), TypeHelper.CopyToList(values, resourceTypeForRepository)); + return (IEnumerable)Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(resourceTypeForRepository), + TypeHelper.CopyToList(values, resourceTypeForRepository)); } - public HashSet LoadDbValues(IEnumerable resources, ResourceHook hook, params RelationshipAttribute[] relationships) where TResource : class, IIdentifiable + public HashSet LoadDbValues(IEnumerable resources, ResourceHook hook, params RelationshipAttribute[] relationships) + where TResource : class, IIdentifiable { - var resourceType = typeof(TResource); - var dbValues = LoadDbValues(resourceType, resources, hook, relationships)?.Cast(); + Type resourceType = typeof(TResource); + IEnumerable dbValues = LoadDbValues(resourceType, resources, hook, relationships)?.Cast(); + if (dbValues == null) { return null; @@ -112,9 +123,10 @@ public HashSet LoadDbValues(IEnumerable resourc return new HashSet(dbValues); } - public bool ShouldLoadDbValues(Type resourceType, ResourceHook hook) + public bool ShouldLoadDbValues(LeftType resourceType, ResourceHook hook) { - var discovery = GetHookDiscovery(resourceType); + IHooksDiscovery discovery = GetHookDiscovery(resourceType); + if (discovery.DatabaseValuesDisabledHooks.Contains(hook)) { return false; @@ -130,7 +142,7 @@ public bool ShouldLoadDbValues(Type resourceType, ResourceHook hook) private bool ShouldExecuteHook(RightType resourceType, ResourceHook hook) { - var discovery = GetHookDiscovery(resourceType); + IHooksDiscovery discovery = GetHookDiscovery(resourceType); return discovery.ImplementedHooks.Contains(hook); } @@ -143,44 +155,48 @@ private void CheckForTargetHookExistence() } } - private IHooksDiscovery GetHookDiscovery(Type resourceType) + private IHooksDiscovery GetHookDiscovery(LeftType resourceType) { if (!_hookDiscoveries.TryGetValue(resourceType, out IHooksDiscovery discovery)) { discovery = _genericProcessorFactory.Get(typeof(IHooksDiscovery<>), resourceType); _hookDiscoveries[resourceType] = discovery; } + return discovery; } - private IEnumerable GetWhereAndInclude(IReadOnlyCollection ids, RelationshipAttribute[] relationshipsToNextLayer) where TResource : class, IIdentifiable + private IEnumerable GetWhereAndInclude(IReadOnlyCollection ids, RelationshipAttribute[] relationshipsToNextLayer) + where TResource : class, IIdentifiable { if (!ids.Any()) { return Array.Empty(); } - var resourceContext = _resourceContextProvider.GetResourceContext(); - var filterExpression = CreateFilterByIds(ids, resourceContext); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); + FilterExpression filterExpression = CreateFilterByIds(ids, resourceContext); var queryLayer = new QueryLayer(resourceContext) { Filter = filterExpression }; - var chains = relationshipsToNextLayer.Select(relationship => new ResourceFieldChainExpression(relationship)).ToList(); + List chains = relationshipsToNextLayer.Select(relationship => new ResourceFieldChainExpression(relationship)) + .ToList(); + if (chains.Any()) { queryLayer.Include = IncludeChainConverter.FromRelationshipChains(chains); } - var repository = GetRepository(); + IResourceReadRepository repository = GetRepository(); return repository.GetAsync(queryLayer, CancellationToken.None).Result; } private static FilterExpression CreateFilterByIds(IReadOnlyCollection ids, ResourceContext resourceContext) { - var idAttribute = resourceContext.Attributes.Single(attr => attr.Property.Name == nameof(Identifiable.Id)); + AttrAttribute idAttribute = resourceContext.Attributes.Single(attr => attr.Property.Name == nameof(Identifiable.Id)); var idChain = new ResourceFieldChainExpression(idAttribute); if (ids.Count == 1) @@ -189,31 +205,32 @@ private static FilterExpression CreateFilterByIds(IReadOnlyCollection return new ComparisonExpression(ComparisonOperator.Equals, idChain, constant); } - var constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToList(); + List constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToList(); return new EqualsAnyOfExpression(idChain, constants); } - private IResourceReadRepository GetRepository() where TResource : class, IIdentifiable + private IResourceReadRepository GetRepository() + where TResource : class, IIdentifiable { return _genericProcessorFactory.Get>(typeof(IResourceReadRepository<,>), typeof(TResource), typeof(TId)); } - public Dictionary LoadImplicitlyAffected( - Dictionary leftResourcesByRelation, + public Dictionary LoadImplicitlyAffected(Dictionary leftResourcesByRelation, IEnumerable existingRightResources = null) { - var existingRightResourceList = existingRightResources?.Cast().ToList(); + List existingRightResourceList = existingRightResources?.Cast().ToList(); var implicitlyAffected = new Dictionary(); - foreach (var kvp in leftResourcesByRelation) + + foreach (KeyValuePair kvp in leftResourcesByRelation) { - if (IsHasManyThrough(kvp, out var lefts, out var relationship)) + if (IsHasManyThrough(kvp, out IEnumerable lefts, out RelationshipAttribute relationship)) { continue; } // note that we don't have to check if BeforeImplicitUpdate hook is implemented. If not, it wont ever get here. - var includedLefts = LoadDbValues(relationship.LeftType, lefts, ResourceHook.BeforeImplicitUpdateRelationship, relationship); + IEnumerable includedLefts = LoadDbValues(relationship.LeftType, lefts, ResourceHook.BeforeImplicitUpdateRelationship, relationship); AddToImplicitlyAffected(includedLefts, relationship, existingRightResourceList, implicitlyAffected); } @@ -227,7 +244,8 @@ private void AddToImplicitlyAffected(IEnumerable includedLefts, RelationshipAttr foreach (IIdentifiable ip in includedLefts) { IList dbRightResourceList = TypeHelper.CreateListFor(relationship.RightType); - var relationshipValue = relationship.GetValue(ip); + object relationshipValue = relationship.GetValue(ip); + if (!(relationshipValue is IEnumerable)) { if (relationshipValue != null) @@ -240,7 +258,8 @@ private void AddToImplicitlyAffected(IEnumerable includedLefts, RelationshipAttr AddToList(dbRightResourceList, (IEnumerable)relationshipValue); } - var dbRightResourceListCast = dbRightResourceList.Cast().ToList(); + List dbRightResourceListCast = dbRightResourceList.Cast().ToList(); + if (existingRightResourceList != null) { dbRightResourceListCast = dbRightResourceListCast.Except(existingRightResourceList, _comparer).ToList(); @@ -261,15 +280,13 @@ private void AddToImplicitlyAffected(IEnumerable includedLefts, RelationshipAttr private static void AddToList(IList list, IEnumerable itemsToAdd) { - foreach (var item in itemsToAdd) + foreach (object? item in itemsToAdd) { list.Add(item); } } - private bool IsHasManyThrough(KeyValuePair kvp, - out IEnumerable resources, - out RelationshipAttribute attr) + private bool IsHasManyThrough(KeyValuePair kvp, out IEnumerable resources, out RelationshipAttribute attr) { attr = kvp.Key; resources = kvp.Value; @@ -277,4 +294,3 @@ private bool IsHasManyThrough(KeyValuePair k } } } - diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IByAffectedRelationships.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IByAffectedRelationships.cs index 689ac13260..2d77f065b0 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IByAffectedRelationships.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IByAffectedRelationships.cs @@ -7,12 +7,12 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution /// /// An interface that is implemented to expose a relationship dictionary on another class. /// - public interface IByAffectedRelationships : - IRelationshipGetters where TRightResource : class, IIdentifiable + public interface IByAffectedRelationships : IRelationshipGetters + where TRightResource : class, IIdentifiable { /// /// Gets a dictionary of affected resources grouped by affected relationships. /// Dictionary> AffectedRelationships { get; } } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IDiffableResourceHashSet.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IDiffableResourceHashSet.cs index 1a7d98fc14..ab712a0bc6 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IDiffableResourceHashSet.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IDiffableResourceHashSet.cs @@ -4,19 +4,16 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution { /// - /// A wrapper class that contains information about the resources that are updated by the request. - /// Contains the resources from the request and the corresponding database values. - /// - /// Also contains information about updated relationships through - /// implementation of IRelationshipsDictionary> + /// A wrapper class that contains information about the resources that are updated by the request. Contains the resources from the request and the + /// corresponding database values. Also contains information about updated relationships through implementation of IRelationshipsDictionary + /// > /// - public interface IDiffableResourceHashSet : IResourceHashSet where TResource : class, IIdentifiable + public interface IDiffableResourceHashSet : IResourceHashSet + where TResource : class, IIdentifiable { /// - /// Iterates over diffs, which is the affected resource from the request - /// with their associated current value from the database. + /// Iterates over diffs, which is the affected resource from the request with their associated current value from the database. /// IEnumerable> GetDiffs(); - } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookExecutorHelper.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookExecutorHelper.cs index 810a3f3dd5..56a7f635f9 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookExecutorHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookExecutorHelper.cs @@ -7,55 +7,60 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution { /// - /// A helper class for retrieving meta data about hooks, - /// fetching database values and performing other recurring internal operations. - /// - /// Used internally by + /// A helper class for retrieving meta data about hooks, fetching database values and performing other recurring internal operations. Used internally by + /// /// internal interface IHookExecutorHelper { /// - /// For a particular ResourceHook and for a given model type, checks if - /// the ResourceHooksDefinition has an implementation for the hook - /// and if so, return it. - /// - /// Also caches the retrieves containers so we don't need to reflectively - /// instantiate them multiple times. + /// For a particular ResourceHook and for a given model type, checks if the ResourceHooksDefinition has an implementation for the hook and if so, return + /// it. Also caches the retrieves containers so we don't need to reflectively instantiate them multiple times. /// IResourceHookContainer GetResourceHookContainer(Type targetResource, ResourceHook hook = ResourceHook.None); /// - /// For a particular ResourceHook and for a given model type, checks if - /// the ResourceHooksDefinition has an implementation for the hook - /// and if so, return it. - /// - /// Also caches the retrieves containers so we don't need to reflectively - /// instantiate them multiple times. + /// For a particular ResourceHook and for a given model type, checks if the ResourceHooksDefinition has an implementation for the hook and if so, return + /// it. Also caches the retrieves containers so we don't need to reflectively instantiate them multiple times. /// - IResourceHookContainer GetResourceHookContainer(ResourceHook hook = ResourceHook.None) where TResource : class, IIdentifiable; + IResourceHookContainer GetResourceHookContainer(ResourceHook hook = ResourceHook.None) + where TResource : class, IIdentifiable; /// /// Load the implicitly affected resources from the database for a given set of target target resources and involved relationships /// - /// The implicitly affected resources by relationship - Dictionary LoadImplicitlyAffected(Dictionary leftResourcesByRelation, IEnumerable existingRightResources = null); + /// + /// The implicitly affected resources by relationship + /// + Dictionary LoadImplicitlyAffected(Dictionary leftResourcesByRelation, + IEnumerable existingRightResources = null); /// /// For a set of resources, loads current values from the database /// - /// type of the resources to be loaded - /// The set of resources to load the db values for - /// The hook in which the db values will be displayed. - /// Relationships that need to be included on resources. + /// + /// type of the resources to be loaded + /// + /// + /// The set of resources to load the db values for + /// + /// + /// The hook in which the db values will be displayed. + /// + /// + /// Relationships that need to be included on resources. + /// IEnumerable LoadDbValues(Type resourceTypeForRepository, IEnumerable resources, ResourceHook hook, params RelationshipAttribute[] relationships); /// - /// Checks if the display database values option is allowed for the targeted hook, and for - /// a given resource of type checks if this hook is implemented and if the - /// database values option is enabled. + /// Checks if the display database values option is allowed for the targeted hook, and for a given resource of type + /// checks if this hook is implemented and if the database values option is enabled. /// - /// true, if should load db values, false otherwise. - /// Container resource type. + /// + /// true, if should load db values, false otherwise. + /// + /// + /// Container resource type. + /// /// Hook. bool ShouldLoadDbValues(Type resourceType, ResourceHook hook); } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipGetters.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipGetters.cs index 7f07fc083c..ab3d80eca9 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipGetters.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipGetters.cs @@ -9,19 +9,22 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution /// /// A helper class that provides insights in which relationships have been updated for which resources. /// - public interface IRelationshipGetters where TLeftResource : class, IIdentifiable + public interface IRelationshipGetters + where TLeftResource : class, IIdentifiable { /// - /// Gets a dictionary of all resources that have an affected relationship to type + /// Gets a dictionary of all resources that have an affected relationship to type /// - Dictionary> GetByRelationship() where TRightResource : class, IIdentifiable; + Dictionary> GetByRelationship() + where TRightResource : class, IIdentifiable; + /// - /// Gets a dictionary of all resources that have an affected relationship to type + /// Gets a dictionary of all resources that have an affected relationship to type /// Dictionary> GetByRelationship(Type resourceType); + /// - /// Gets a collection of all the resources for the property within - /// has been affected by the request + /// Gets a collection of all the resources for the property within has been affected by the request /// HashSet GetAffected(Expression> navigationAction); } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipsDictionary.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipsDictionary.cs index 8de79a01ed..6f1496b01d 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipsDictionary.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipsDictionary.cs @@ -7,14 +7,16 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution /// /// A dummy interface used internally by the hook executor. /// - public interface IRelationshipsDictionary { } + public interface IRelationshipsDictionary + { + } /// /// A helper class that provides insights in which relationships have been updated for which resources. /// - public interface IRelationshipsDictionary : - IRelationshipGetters, - IReadOnlyDictionary>, - IRelationshipsDictionary where TRightResource : class, IIdentifiable - { } + public interface IRelationshipsDictionary + : IRelationshipGetters, IReadOnlyDictionary>, IRelationshipsDictionary + where TRightResource : class, IIdentifiable + { + } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IResourceHashSet.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IResourceHashSet.cs index 8863ac4686..045880be3f 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IResourceHashSet.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IResourceHashSet.cs @@ -4,10 +4,11 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution { /// - /// Basically a enumerable of of resources that were affected by the request. - /// - /// Also contains information about updated relationships through - /// implementation of IAffectedRelationshipsDictionary> + /// Basically a enumerable of of resources that were affected by the request. Also contains information about updated + /// relationships through implementation of IAffectedRelationshipsDictionary> /// - public interface IResourceHashSet : IByAffectedRelationships, IReadOnlyCollection where TResource : class, IIdentifiable { } -} \ No newline at end of file + public interface IResourceHashSet : IByAffectedRelationships, IReadOnlyCollection + where TResource : class, IIdentifiable + { + } +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/RelationshipsDictionary.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/RelationshipsDictionary.cs index 0aa1b9406d..d738a6b162 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/RelationshipsDictionary.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/RelationshipsDictionary.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using JetBrains.Annotations; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; @@ -10,30 +11,35 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution { /// - /// Implementation of IAffectedRelationships{TRightResource} - /// - /// It is practically a ReadOnlyDictionary{RelationshipAttribute, HashSet{TRightResource}} dictionary - /// with the two helper methods defined on IAffectedRelationships{TRightResource}. + /// Implementation of IAffectedRelationships{TRightResource} It is practically a ReadOnlyDictionary{RelationshipAttribute, HashSet{TRightResource}} + /// dictionary with the two helper methods defined on IAffectedRelationships{TRightResource}. /// [PublicAPI] - public class RelationshipsDictionary : - Dictionary>, - IRelationshipsDictionary where TResource : class, IIdentifiable + public class RelationshipsDictionary : Dictionary>, IRelationshipsDictionary + where TResource : class, IIdentifiable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// Relationships. - public RelationshipsDictionary(Dictionary> relationships) : base(relationships) { } + /// + /// Relationships. + /// + public RelationshipsDictionary(Dictionary> relationships) + : base(relationships) + { + } /// /// Used internally by the ResourceHookExecutor to make life a bit easier with generics /// internal RelationshipsDictionary(Dictionary relationships) - : this(TypeHelper.ConvertRelationshipDictionary(relationships)) { } + : this(TypeHelper.ConvertRelationshipDictionary(relationships)) + { + } /// - public Dictionary> GetByRelationship() where TRelatedResource : class, IIdentifiable + public Dictionary> GetByRelationship() + where TRelatedResource : class, IIdentifiable { return GetByRelationship(typeof(TRelatedResource)); } @@ -49,7 +55,7 @@ public HashSet GetAffected(Expression> naviga { ArgumentGuard.NotNull(navigationAction, nameof(navigationAction)); - var property = TypeHelper.ParseNavigationExpression(navigationAction); + PropertyInfo property = TypeHelper.ParseNavigationExpression(navigationAction); return this.Where(p => p.Key.Property.Name == property.Name).Select(p => p.Value).SingleOrDefault(); } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceDiffPair.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceDiffPair.cs index fe52b4f9b3..c338cbe612 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceDiffPair.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceDiffPair.cs @@ -4,25 +4,26 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution { /// - /// A wrapper that contains a resource that is affected by the request, - /// matched to its current database value + /// A wrapper that contains a resource that is affected by the request, matched to its current database value /// [PublicAPI] - public sealed class ResourceDiffPair where TResource : class, IIdentifiable + public sealed class ResourceDiffPair + where TResource : class, IIdentifiable { - public ResourceDiffPair(TResource resource, TResource databaseValue) - { - Resource = resource; - DatabaseValue = databaseValue; - } - /// /// The resource from the request matching the resource from the database. /// public TResource Resource { get; } + /// /// The resource from the database matching the resource from the request. /// public TResource DatabaseValue { get; } + + public ResourceDiffPair(TResource resource, TResource databaseValue) + { + Resource = resource; + DatabaseValue = databaseValue; + } } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHashSet.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHashSet.cs index 192bcdad21..40734b2168 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHashSet.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHashSet.cs @@ -9,23 +9,20 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution { /// - /// Implementation of IResourceHashSet{TResource}. - /// - /// Basically a enumerable of of resources that were affected by the request. - /// - /// Also contains information about updated relationships through - /// implementation of IRelationshipsDictionary> + /// Implementation of IResourceHashSet{TResource}. Basically a enumerable of of resources that were affected by the + /// request. Also contains information about updated relationships through implementation of IRelationshipsDictionary> /// [PublicAPI] - public class ResourceHashSet : HashSet, IResourceHashSet where TResource : class, IIdentifiable + public class ResourceHashSet : HashSet, IResourceHashSet + where TResource : class, IIdentifiable { + private readonly RelationshipsDictionary _relationships; + /// public Dictionary> AffectedRelationships => _relationships; - private readonly RelationshipsDictionary _relationships; - - public ResourceHashSet(HashSet resources, - Dictionary> relationships) : base(resources) + public ResourceHashSet(HashSet resources, Dictionary> relationships) + : base(resources) { _relationships = new RelationshipsDictionary(relationships); } @@ -33,10 +30,10 @@ public ResourceHashSet(HashSet resources, /// /// Used internally by the ResourceHookExecutor to make live a bit easier with generics /// - internal ResourceHashSet(IEnumerable resources, - Dictionary relationships) - : this((HashSet)resources, TypeHelper.ConvertRelationshipDictionary(relationships)) { } - + internal ResourceHashSet(IEnumerable resources, Dictionary relationships) + : this((HashSet)resources, TypeHelper.ConvertRelationshipDictionary(relationships)) + { + } /// public Dictionary> GetByRelationship(Type resourceType) @@ -45,7 +42,8 @@ public Dictionary> GetByRelationship(T } /// - public Dictionary> GetByRelationship() where TRightResource : class, IIdentifiable + public Dictionary> GetByRelationship() + where TRightResource : class, IIdentifiable { return GetByRelationship(typeof(TRightResource)); } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHook.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHook.cs index 310bdb808c..f63b1380ad 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHook.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHook.cs @@ -1,6 +1,5 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution { - /// /// A enum that represent the available resource hooks. /// @@ -20,5 +19,4 @@ public enum ResourceHook AfterDelete, AfterUpdateRelationship } - } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourcePipeline.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourcePipeline.cs index 3c256f1ad4..bdfc01aa78 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourcePipeline.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourcePipeline.cs @@ -5,9 +5,9 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution { /// - /// An enum that represents the initiator of a resource hook. Eg, when BeforeCreate() - /// is called from , it will be called - /// with parameter pipeline = ResourceAction.GetSingle. + /// An enum that represents the initiator of a resource hook. Eg, when BeforeCreate() is called from + /// , it will be called with parameter pipeline = + /// ResourceAction.GetSingle. /// [PublicAPI] public enum ResourcePipeline diff --git a/src/JsonApiDotNetCore/Hooks/Internal/ICreateHookContainer.cs b/src/JsonApiDotNetCore/Hooks/Internal/ICreateHookContainer.cs index 11570d6212..a52e9a716c 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/ICreateHookContainer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/ICreateHookContainer.cs @@ -8,42 +8,50 @@ namespace JsonApiDotNetCore.Hooks.Internal /// /// Create hooks container /// - public interface ICreateHookContainer where TResource : class, IIdentifiable + public interface ICreateHookContainer + where TResource : class, IIdentifiable { /// - /// Implement this hook to run custom logic in the - /// layer just before creation of resources of type . + /// Implement this hook to run custom logic in the layer just before creation of resources of type + /// . /// - /// For the pipeline, - /// will typically contain one entry. + /// For the pipeline, will typically contain one entry. /// - /// The returned may be a subset - /// of , in which case the operation of the - /// pipeline will not be executed for the omitted resources. The returned - /// set may also contain custom changes of the properties on the resources. + /// The returned may be a subset of , in which case the operation of the pipeline will + /// not be executed for the omitted resources. The returned set may also contain custom changes of the properties on the resources. /// - /// If new relationships are to be created with the to-be-created resources, - /// this will be reflected by the corresponding NavigationProperty being set. - /// For each of these relationships, the - /// hook is fired after the execution of this hook. + /// If new relationships are to be created with the to-be-created resources, this will be reflected by the corresponding NavigationProperty being set. + /// For each of these relationships, the hook is fired after the execution of + /// this hook. /// - /// The transformed resource set - /// The unique set of affected resources. - /// An enum indicating from where the hook was triggered. + /// + /// The transformed resource set + /// + /// + /// The unique set of affected resources. + /// + /// + /// An enum indicating from where the hook was triggered. + /// IEnumerable BeforeCreate(IResourceHashSet resources, ResourcePipeline pipeline); /// - /// Implement this hook to run custom logic in the - /// layer just after creation of resources of type . + /// Implement this hook to run custom logic in the layer just after creation of resources of type + /// . /// - /// If relationships were created with the created resources, this will - /// be reflected by the corresponding NavigationProperty being set. - /// For each of these relationships, the + /// If relationships were created with the created resources, this will be reflected by the corresponding NavigationProperty being set. For each of these + /// relationships, the /// hook is fired after the execution of this hook. /// - /// The transformed resource set - /// The unique set of affected resources. - /// An enum indicating from where the hook was triggered. + /// + /// The transformed resource set + /// + /// + /// The unique set of affected resources. + /// + /// + /// An enum indicating from where the hook was triggered. + /// void AfterCreate(HashSet resources, ResourcePipeline pipeline); } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/ICreateHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/ICreateHookExecutor.cs index 470a90d1db..c26c5099d8 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/ICreateHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/ICreateHookExecutor.cs @@ -8,32 +8,47 @@ namespace JsonApiDotNetCore.Hooks.Internal public interface ICreateHookExecutor { /// - /// Executes the Before Cycle by firing the appropriate hooks if they are implemented. - /// The returned set will be used in the actual operation in . + /// Executes the Before Cycle by firing the appropriate hooks if they are implemented. The returned set will be used in the actual operation in + /// . /// - /// Fires the - /// hook for values in parameter . + /// Fires the hook for values in parameter . /// - /// Fires the - /// hook for any secondary (nested) resource for values within parameter + /// Fires the hook for any secondary (nested) resource for values within + /// parameter /// - /// The transformed set - /// Target resources for the Before cycle. - /// An enum indicating from where the hook was triggered. - /// The type of the root resources - IEnumerable BeforeCreate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable; + /// + /// The transformed set + /// + /// + /// Target resources for the Before cycle. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// The type of the root resources + /// + IEnumerable BeforeCreate(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable; + /// - /// Executes the After Cycle by firing the appropriate hooks if they are implemented. + /// Executes the After Cycle by firing the appropriate hooks if they are implemented. /// - /// Fires the - /// hook for values in parameter . + /// Fires the hook for values in parameter . /// - /// Fires the - /// hook for any secondary (nested) resource for values within parameter + /// Fires the hook for any secondary (nested) resource for values within + /// parameter /// - /// Target resources for the Before cycle. - /// An enum indicating from where the hook was triggered. - /// The type of the root resources - void AfterCreate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable; + /// + /// Target resources for the Before cycle. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// The type of the root resources + /// + void AfterCreate(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IDeleteHookContainer.cs b/src/JsonApiDotNetCore/Hooks/Internal/IDeleteHookContainer.cs index 7ab640fd49..595056ce07 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IDeleteHookContainer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IDeleteHookContainer.cs @@ -8,36 +8,45 @@ namespace JsonApiDotNetCore.Hooks.Internal /// /// Delete hooks container /// - public interface IDeleteHookContainer where TResource : class, IIdentifiable + public interface IDeleteHookContainer + where TResource : class, IIdentifiable { /// - /// Implement this hook to run custom logic in the - /// layer just before deleting resources of type . + /// Implement this hook to run custom logic in the layer just before deleting resources of type + /// . /// - /// For the pipeline, - /// will typically contain one resource. + /// For the pipeline, will typically contain one resource. /// - /// The returned may be a subset - /// of , in which case the operation of the - /// pipeline will not be executed for the omitted resources. + /// The returned may be a subset of , in which case the operation of the pipeline will + /// not be executed for the omitted resources. /// - /// If by the deletion of these resources any other resources are affected - /// implicitly by the removal of their relationships (eg - /// in the case of an one-to-one relationship), the - /// hook is fired for these resources. + /// If by the deletion of these resources any other resources are affected implicitly by the removal of their relationships (eg in the case of an + /// one-to-one relationship), the hook is fired for these resources. /// - /// The transformed resource set - /// The unique set of affected resources. - /// An enum indicating from where the hook was triggered. + /// + /// The transformed resource set + /// + /// + /// The unique set of affected resources. + /// + /// + /// An enum indicating from where the hook was triggered. + /// IEnumerable BeforeDelete(IResourceHashSet resources, ResourcePipeline pipeline); /// - /// Implement this hook to run custom logic in the - /// layer just after deletion of resources of type . + /// Implement this hook to run custom logic in the layer just after deletion of resources of type + /// . /// - /// The unique set of affected resources. - /// An enum indicating from where the hook was triggered. - /// If set to true the deletion succeeded in the repository layer. + /// + /// The unique set of affected resources. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// If set to true the deletion succeeded in the repository layer. + /// void AfterDelete(HashSet resources, ResourcePipeline pipeline, bool succeeded); } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IDeleteHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/IDeleteHookExecutor.cs index efab29ac87..706477ad42 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IDeleteHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IDeleteHookExecutor.cs @@ -8,33 +8,48 @@ namespace JsonApiDotNetCore.Hooks.Internal public interface IDeleteHookExecutor { /// - /// Executes the Before Cycle by firing the appropriate hooks if they are implemented. - /// The returned set will be used in the actual operation in . + /// Executes the Before Cycle by firing the appropriate hooks if they are implemented. The returned set will be used in the actual operation in + /// . /// - /// Fires the - /// hook for values in parameter . + /// Fires the hook for values in parameter . /// - /// Fires the - /// hook for any resources that are indirectly (implicitly) affected by this operation. - /// Eg: when deleting a resource that has relationships set to other resources, - /// these other resources are implicitly affected by the delete operation. + /// Fires the hook for any resources that are indirectly (implicitly) + /// affected by this operation. Eg: when deleting a resource that has relationships set to other resources, these other resources are implicitly affected + /// by the delete operation. /// - /// The transformed set - /// Target resources for the Before cycle. - /// An enum indicating from where the hook was triggered. - /// The type of the root resources - IEnumerable BeforeDelete(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable; + /// + /// The transformed set + /// + /// + /// Target resources for the Before cycle. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// The type of the root resources + /// + IEnumerable BeforeDelete(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable; /// - /// Executes the After Cycle by firing the appropriate hooks if they are implemented. + /// Executes the After Cycle by firing the appropriate hooks if they are implemented. /// - /// Fires the - /// hook for values in parameter . + /// Fires the hook for values in parameter . /// - /// Target resources for the Before cycle. - /// An enum indicating from where the hook was triggered. - /// If set to true the deletion succeeded. - /// The type of the root resources - void AfterDelete(IEnumerable resources, ResourcePipeline pipeline, bool succeeded) where TResource : class, IIdentifiable; + /// + /// Target resources for the Before cycle. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// If set to true the deletion succeeded. + /// + /// + /// The type of the root resources + /// + void AfterDelete(IEnumerable resources, ResourcePipeline pipeline, bool succeeded) + where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IOnReturnHookContainer.cs b/src/JsonApiDotNetCore/Hooks/Internal/IOnReturnHookContainer.cs index 61db0db85f..94b60ead58 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IOnReturnHookContainer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IOnReturnHookContainer.cs @@ -8,21 +8,26 @@ namespace JsonApiDotNetCore.Hooks.Internal /// /// On return hook container /// - public interface IOnReturnHookContainer where TResource : class, IIdentifiable + public interface IOnReturnHookContainer + where TResource : class, IIdentifiable { /// - /// Implement this hook to transform the result data just before returning - /// the resources of type from the - /// layer + /// Implement this hook to transform the result data just before returning the resources of type from the + /// layer /// - /// The returned may be a subset - /// of and may contain changes in properties - /// of the encapsulated resources. + /// The returned may be a subset of and may contain changes in properties of the + /// encapsulated resources. /// /// - /// The transformed resource set - /// The unique set of affected resources - /// An enum indicating from where the hook was triggered. + /// + /// The transformed resource set + /// + /// + /// The unique set of affected resources + /// + /// + /// An enum indicating from where the hook was triggered. + /// IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline); } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IOnReturnHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/IOnReturnHookExecutor.cs index 30d10479f3..9dca66a068 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IOnReturnHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IOnReturnHookExecutor.cs @@ -10,15 +10,24 @@ namespace JsonApiDotNetCore.Hooks.Internal public interface IOnReturnHookExecutor { /// - /// Executes the On Cycle by firing the appropriate hooks if they are implemented. + /// Executes the On Cycle by firing the appropriate hooks if they are implemented. /// - /// Fires the for every unique - /// resource type occurring in parameter . + /// Fires the for every unique resource type occurring in parameter + /// . /// - /// The transformed set - /// Target resources for the Before cycle. - /// An enum indicating from where the hook was triggered. - /// The type of the root resources - IEnumerable OnReturn(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable; + /// + /// The transformed set + /// + /// + /// Target resources for the Before cycle. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// The type of the root resources + /// + IEnumerable OnReturn(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IReadHookContainer.cs b/src/JsonApiDotNetCore/Hooks/Internal/IReadHookContainer.cs index 9913f8f2d4..d7ad9117a6 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IReadHookContainer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IReadHookContainer.cs @@ -8,24 +8,38 @@ namespace JsonApiDotNetCore.Hooks.Internal /// /// Read hooks container /// - public interface IReadHookContainer where TResource : class, IIdentifiable + public interface IReadHookContainer + where TResource : class, IIdentifiable { /// - /// Implement this hook to run custom logic in the - /// layer just before reading resources of type . + /// Implement this hook to run custom logic in the layer just before reading resources of type + /// . /// - /// An enum indicating from where the hook was triggered. - /// Indicates whether the to be queried resources are the primary request resources or if they were included - /// The string ID of the requested resource, in the case of + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// Indicates whether the to be queried resources are the primary request resources or if they were included + /// + /// + /// The string ID of the requested resource, in the case of + /// void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null); + /// - /// Implement this hook to run custom logic in the - /// layer just after reading resources of type . + /// Implement this hook to run custom logic in the layer just after reading resources of type + /// . /// - /// The unique set of affected resources. - /// An enum indicating from where the hook was triggered. - /// A boolean to indicate whether the resources in this hook execution are the primary resources of the request, - /// or if they were included as a relationship + /// + /// The unique set of affected resources. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// A boolean to indicate whether the resources in this hook execution are the primary resources of the request, or if they were included as a + /// relationship + /// void AfterRead(HashSet resources, ResourcePipeline pipeline, bool isIncluded = false); } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IReadHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/IReadHookExecutor.cs index 3ac67c0a93..a752d81e2d 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IReadHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IReadHookExecutor.cs @@ -12,25 +12,38 @@ namespace JsonApiDotNetCore.Hooks.Internal public interface IReadHookExecutor { /// - /// Executes the Before Cycle by firing the appropriate hooks if they are implemented. + /// Executes the Before Cycle by firing the appropriate hooks if they are implemented. /// - /// Fires the - /// hook for the requested resources as well as any related relationship. + /// Fires the hook for the requested resources as well as any related relationship. /// - /// An enum indicating from where the hook was triggered. - /// StringId of the requested resource in the case of - /// . - /// The type of the request resource - void BeforeRead(ResourcePipeline pipeline, string stringId = null) where TResource : class, IIdentifiable; + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// StringId of the requested resource in the case of . + /// + /// + /// The type of the request resource + /// + void BeforeRead(ResourcePipeline pipeline, string stringId = null) + where TResource : class, IIdentifiable; + /// - /// Executes the After Cycle by firing the appropriate hooks if they are implemented. + /// Executes the After Cycle by firing the appropriate hooks if they are implemented. /// - /// Fires the for every unique - /// resource type occurring in parameter . + /// Fires the for every unique resource type occurring in parameter + /// . /// - /// Target resources for the Before cycle. - /// An enum indicating from where the hook was triggered. - /// The type of the root resources - void AfterRead(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable; + /// + /// Target resources for the Before cycle. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// The type of the root resources + /// + void AfterRead(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookContainer.cs b/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookContainer.cs index b9cf89cd4d..f3bbc0697b 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookContainer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookContainer.cs @@ -3,15 +3,19 @@ namespace JsonApiDotNetCore.Hooks.Internal { /// - /// Not meant for public usage. Used internally in the + /// Not meant for public usage. Used internally in the /// - public interface IResourceHookContainer { } + public interface IResourceHookContainer + { + } /// - /// Implement this interface to implement business logic hooks on . + /// Implement this interface to implement business logic hooks on . /// public interface IResourceHookContainer - : IReadHookContainer, IDeleteHookContainer, ICreateHookContainer, - IUpdateHookContainer, IOnReturnHookContainer, IResourceHookContainer - where TResource : class, IIdentifiable { } + : IReadHookContainer, IDeleteHookContainer, ICreateHookContainer, IUpdateHookContainer, + IOnReturnHookContainer, IResourceHookContainer + where TResource : class, IIdentifiable + { + } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookExecutor.cs index 56a5cc975f..e3a62558f3 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookExecutor.cs @@ -5,14 +5,12 @@ namespace JsonApiDotNetCore.Hooks.Internal { /// - /// Transient service responsible for executing Resource Hooks as defined - /// in . See methods in - /// , and - /// for more information. - /// - /// Uses for traversal of nested resource data structures. - /// Uses for retrieving meta data about hooks, - /// fetching database values and performing other recurring internal operations. + /// Transient service responsible for executing Resource Hooks as defined in . See methods in + /// , and for more information. Uses + /// for traversal of nested resource data structures. Uses for retrieving meta data + /// about hooks, fetching database values and performing other recurring internal operations. /// - public interface IResourceHookExecutor : IReadHookExecutor, IUpdateHookExecutor, ICreateHookExecutor, IDeleteHookExecutor, IOnReturnHookExecutor { } + public interface IResourceHookExecutor : IReadHookExecutor, IUpdateHookExecutor, ICreateHookExecutor, IDeleteHookExecutor, IOnReturnHookExecutor + { + } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IUpdateHookContainer.cs b/src/JsonApiDotNetCore/Hooks/Internal/IUpdateHookContainer.cs index dcf26d3fd0..bcae4b5f9b 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IUpdateHookContainer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IUpdateHookContainer.cs @@ -8,96 +8,113 @@ namespace JsonApiDotNetCore.Hooks.Internal /// /// update hooks container /// - public interface IUpdateHookContainer where TResource : class, IIdentifiable + public interface IUpdateHookContainer + where TResource : class, IIdentifiable { /// - /// Implement this hook to run custom logic in the - /// layer just before updating resources of type . + /// Implement this hook to run custom logic in the layer just before updating resources of type + /// . /// - /// For the pipeline, the - /// will typically contain one resource. + /// For the pipeline, the will typically contain one resource. /// - /// The returned may be a subset - /// of the property in parameter , - /// in which case the operation of the pipeline will not be executed - /// for the omitted resources. The returned set may also contain custom - /// changes of the properties on the resources. + /// The returned may be a subset of the property in parameter + /// , in which case the operation of the pipeline will not be executed for the omitted resources. The returned set may also + /// contain custom changes of the properties on the resources. /// - /// If new relationships are to be created with the to-be-updated resources, - /// this will be reflected by the corresponding NavigationProperty being set. - /// For each of these relationships, the - /// hook is fired after the execution of this hook. + /// If new relationships are to be created with the to-be-updated resources, this will be reflected by the corresponding NavigationProperty being set. + /// For each of these relationships, the hook is fired after the execution of + /// this hook. /// - /// If by the creation of these relationships, any other relationships (eg - /// in the case of an already populated one-to-one relationship) are implicitly - /// affected, the - /// hook is fired for these. + /// If by the creation of these relationships, any other relationships (eg in the case of an already populated one-to-one relationship) are implicitly + /// affected, the hook is fired for these. /// - /// The transformed resource set - /// The affected resources. - /// An enum indicating from where the hook was triggered. + /// + /// The transformed resource set + /// + /// + /// The affected resources. + /// + /// + /// An enum indicating from where the hook was triggered. + /// IEnumerable BeforeUpdate(IDiffableResourceHashSet resources, ResourcePipeline pipeline); /// - /// Implement this hook to run custom logic in the - /// layer just before updating relationships to resources of type . + /// Implement this hook to run custom logic in the layer just before updating relationships to + /// resources of type . /// - /// This hook is fired when a relationship is created to resources of type - /// from a dependent pipeline ( - /// or ). For example, If an Article was created - /// and its author relationship was set to an existing Person, this hook will be fired - /// for that particular Person. + /// This hook is fired when a relationship is created to resources of type from a dependent pipeline ( + /// or ). For example, If an Article was created and its author relationship + /// was set to an existing Person, this hook will be fired for that particular Person. /// - /// The returned may be a subset - /// of , in which case the operation of the - /// pipeline will not be executed for any resource whose ID was omitted + /// The returned may be a subset of , in which case the operation of the pipeline will not + /// be executed for any resource whose ID was omitted /// /// - /// The transformed set of ids - /// The unique set of ids - /// An enum indicating from where the hook was triggered. - /// A helper that groups the resources by the affected relationship - IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline); + /// + /// The transformed set of ids + /// + /// + /// The unique set of ids + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// A helper that groups the resources by the affected relationship + /// + IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary resourcesByRelationship, + ResourcePipeline pipeline); /// - /// Implement this hook to run custom logic in the - /// layer just after updating resources of type . + /// Implement this hook to run custom logic in the layer just after updating resources of type + /// . /// - /// If relationships were updated with the updated resources, this will - /// be reflected by the corresponding NavigationProperty being set. - /// For each of these relationships, the + /// If relationships were updated with the updated resources, this will be reflected by the corresponding NavigationProperty being set. For each of these + /// relationships, the /// hook is fired after the execution of this hook. /// - /// The unique set of affected resources. - /// An enum indicating from where the hook was triggered. + /// + /// The unique set of affected resources. + /// + /// + /// An enum indicating from where the hook was triggered. + /// void AfterUpdate(HashSet resources, ResourcePipeline pipeline); /// - /// Implement this hook to run custom logic in the layer - /// just after a relationship was updated. + /// Implement this hook to run custom logic in the layer just after a relationship was updated. /// - /// Relationship helper. - /// An enum indicating from where the hook was triggered. + /// + /// Relationship helper. + /// + /// + /// An enum indicating from where the hook was triggered. + /// void AfterUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline); /// - /// Implement this hook to run custom logic in the - /// layer just before implicitly updating relationships to resources of type . + /// Implement this hook to run custom logic in the layer just before implicitly updating relationships + /// to resources of type . /// - /// This hook is fired when a relationship to resources of type - /// is implicitly affected from a dependent pipeline ( - /// or ). For example, if an Article was updated - /// by setting its author relationship (one-to-one) to an existing Person, - /// and by this the relationship to a different Person was implicitly removed, - /// this hook will be fired for the latter Person. + /// This hook is fired when a relationship to resources of type is implicitly affected from a dependent pipeline ( + /// or ). For example, if an Article was updated by setting its author + /// relationship (one-to-one) to an existing Person, and by this the relationship to a different Person was implicitly removed, this hook will be fired + /// for the latter Person. /// - /// See for information about - /// when this hook is fired. + /// See for information about when + /// this hook is fired. /// /// - /// The transformed set of ids - /// A helper that groups the resources by the affected relationship - /// An enum indicating from where the hook was triggered. + /// + /// The transformed set of ids + /// + /// + /// A helper that groups the resources by the affected relationship + /// + /// + /// An enum indicating from where the hook was triggered. + /// void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline); } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IUpdateHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/IUpdateHookExecutor.cs index 9884e88260..67323ca1ea 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IUpdateHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IUpdateHookExecutor.cs @@ -11,38 +11,52 @@ namespace JsonApiDotNetCore.Hooks.Internal public interface IUpdateHookExecutor { /// - /// Executes the Before Cycle by firing the appropriate hooks if they are implemented. - /// The returned set will be used in the actual operation in . + /// Executes the Before Cycle by firing the appropriate hooks if they are implemented. The returned set will be used in the actual operation in + /// . /// - /// Fires the - /// hook for values in parameter . + /// Fires the hook for values in + /// parameter . /// - /// Fires the - /// hook for any secondary (nested) resource for values within parameter + /// Fires the hook for any secondary (nested) resource for values within + /// parameter /// - /// Fires the - /// hook for any resources that are indirectly (implicitly) affected by this operation. - /// Eg: when updating a one-to-one relationship of a resource which already - /// had this relationship populated, then this update will indirectly affect - /// the existing relationship value. + /// Fires the hook for any resources that are indirectly (implicitly) + /// affected by this operation. Eg: when updating a one-to-one relationship of a resource which already had this relationship populated, then this update + /// will indirectly affect the existing relationship value. /// - /// The transformed set - /// Target resources for the Before cycle. - /// An enum indicating from where the hook was triggered. - /// The type of the root resources - IEnumerable BeforeUpdate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable; + /// + /// The transformed set + /// + /// + /// Target resources for the Before cycle. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// The type of the root resources + /// + IEnumerable BeforeUpdate(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable; + /// - /// Executes the After Cycle by firing the appropriate hooks if they are implemented. + /// Executes the After Cycle by firing the appropriate hooks if they are implemented. /// - /// Fires the - /// hook for values in parameter . + /// Fires the hook for values in parameter . /// - /// Fires the - /// hook for any secondary (nested) resource for values within parameter + /// Fires the hook for any secondary (nested) resource for values within + /// parameter /// - /// Target resources for the Before cycle. - /// An enum indicating from where the hook was triggered. - /// The type of the root resources - void AfterUpdate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable; + /// + /// Target resources for the Before cycle. + /// + /// + /// An enum indicating from where the hook was triggered. + /// + /// + /// The type of the root resources + /// + void AfterUpdate(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/NeverResourceHookExecutorFacade.cs b/src/JsonApiDotNetCore/Hooks/Internal/NeverResourceHookExecutorFacade.cs index 4de491aa53..aabff919c1 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/NeverResourceHookExecutorFacade.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/NeverResourceHookExecutorFacade.cs @@ -6,7 +6,7 @@ namespace JsonApiDotNetCore.Hooks.Internal { /// - /// Facade for hooks that never executes any callbacks, which is used when is false. + /// Facade for hooks that never executes any callbacks, which is used when is false. /// public sealed class NeverResourceHookExecutorFacade : IResourceHookExecutorFacade { diff --git a/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutor.cs index d2bfcb39a8..38cd16d1cc 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutor.cs @@ -27,12 +27,8 @@ internal sealed class ResourceHookExecutor : IResourceHookExecutor private readonly ITargetedFields _targetedFields; private readonly IResourceGraph _resourceGraph; - public ResourceHookExecutor( - IHookExecutorHelper executorHelper, - ITraversalHelper traversalHelper, - ITargetedFields targetedFields, - IEnumerable constraintProviders, - IResourceGraph resourceGraph) + public ResourceHookExecutor(IHookExecutorHelper executorHelper, ITraversalHelper traversalHelper, ITargetedFields targetedFields, + IEnumerable constraintProviders, IResourceGraph resourceGraph) { _executorHelper = executorHelper; _traversalHelper = traversalHelper; @@ -42,16 +38,17 @@ public ResourceHookExecutor( } /// - public void BeforeRead(ResourcePipeline pipeline, string stringId = null) where TResource : class, IIdentifiable + public void BeforeRead(ResourcePipeline pipeline, string stringId = null) + where TResource : class, IIdentifiable { - var hookContainer = _executorHelper.GetResourceHookContainer(ResourceHook.BeforeRead); + IResourceHookContainer hookContainer = _executorHelper.GetResourceHookContainer(ResourceHook.BeforeRead); hookContainer?.BeforeRead(pipeline, false, stringId); - var calledContainers = typeof(TResource).AsList(); + List calledContainers = typeof(TResource).AsList(); // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var includes = _constraintProviders + IncludeExpression[] includes = _constraintProviders .SelectMany(provider => provider.GetConstraints()) .Select(expressionInScope => expressionInScope.Expression) .OfType() @@ -60,19 +57,20 @@ public void BeforeRead(ResourcePipeline pipeline, string stringId = n // @formatter:keep_existing_linebreaks restore // @formatter:wrap_chained_method_calls restore - foreach (var chain in includes.SelectMany(IncludeChainConverter.GetRelationshipChains)) + foreach (ResourceFieldChainExpression chain in includes.SelectMany(IncludeChainConverter.GetRelationshipChains)) { RecursiveBeforeRead(chain.Fields.Cast().ToList(), pipeline, calledContainers); } } /// - public IEnumerable BeforeUpdate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable + public IEnumerable BeforeUpdate(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.BeforeUpdate, resources, out var container, out var node)) + if (GetHook(ResourceHook.BeforeUpdate, resources, out IResourceHookContainer container, out RootNode node)) { - var relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); - var dbValues = LoadDbValues(typeof(TResource), (IEnumerable)node.UniqueResources, ResourceHook.BeforeUpdate, relationships); + RelationshipAttribute[] relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); + IEnumerable dbValues = LoadDbValues(typeof(TResource), (IEnumerable)node.UniqueResources, ResourceHook.BeforeUpdate, relationships); var diff = new DiffableResourceHashSet(node.UniqueResources, dbValues, node.LeftsToNextLayer(), _targetedFields); IEnumerable updated = container.BeforeUpdate(diff, pipeline); node.UpdateUnique(updated); @@ -84,26 +82,33 @@ public IEnumerable BeforeUpdate(IEnumerable res } /// - public IEnumerable BeforeCreate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable + public IEnumerable BeforeCreate(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.BeforeCreate, resources, out var container, out var node)) + if (GetHook(ResourceHook.BeforeCreate, resources, out IResourceHookContainer container, out RootNode node)) { var affected = new ResourceHashSet((HashSet)node.UniqueResources, node.LeftsToNextLayer()); IEnumerable updated = container.BeforeCreate(affected, pipeline); node.UpdateUnique(updated); node.Reassign(resources); } + FireNestedBeforeUpdateHooks(pipeline, _traversalHelper.CreateNextLayer(node)); return resources; } /// - public IEnumerable BeforeDelete(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable + public IEnumerable BeforeDelete(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.BeforeDelete, resources, out var container, out var node)) + if (GetHook(ResourceHook.BeforeDelete, resources, out IResourceHookContainer container, out RootNode node)) { - var relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); - var targetResources = LoadDbValues(typeof(TResource), (IEnumerable)node.UniqueResources, ResourceHook.BeforeDelete, relationships) ?? node.UniqueResources; + RelationshipAttribute[] relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); + + IEnumerable targetResources = + LoadDbValues(typeof(TResource), (IEnumerable)node.UniqueResources, ResourceHook.BeforeDelete, relationships) ?? + node.UniqueResources; + var affected = new ResourceHashSet(targetResources, node.LeftsToNextLayer()); IEnumerable updated = container.BeforeDelete(affected, pipeline); @@ -115,19 +120,21 @@ public IEnumerable BeforeDelete(IEnumerable res // Here we're loading all relations onto the to-be-deleted article // if for that relation the BeforeImplicitUpdateHook is implemented, // and this hook is then executed - foreach (var entry in node.LeftsToNextLayerByRelationships()) + foreach (KeyValuePair> entry in node.LeftsToNextLayerByRelationships()) { - var rightType = entry.Key; - var implicitTargets = entry.Value; + Type rightType = entry.Key; + Dictionary implicitTargets = entry.Value; FireForAffectedImplicits(rightType, implicitTargets, pipeline); } + return resources; } /// - public IEnumerable OnReturn(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable + public IEnumerable OnReturn(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.OnReturn, resources, out var container, out var node)) + if (GetHook(ResourceHook.OnReturn, resources, out IResourceHookContainer container, out RootNode node)) { IEnumerable updated = container.OnReturn((HashSet)node.UniqueResources, pipeline); ValidateHookResponse(updated); @@ -137,17 +144,19 @@ public IEnumerable OnReturn(IEnumerable resourc Traverse(_traversalHelper.CreateNextLayer(node), ResourceHook.OnReturn, (nextContainer, nextNode) => { - var filteredUniqueSet = CallHook(nextContainer, ResourceHook.OnReturn, ArrayFactory.Create(nextNode.UniqueResources, pipeline)); + IEnumerable filteredUniqueSet = CallHook(nextContainer, ResourceHook.OnReturn, ArrayFactory.Create(nextNode.UniqueResources, pipeline)); nextNode.UpdateUnique(filteredUniqueSet); nextNode.Reassign(); }); + return resources; } /// - public void AfterRead(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable + public void AfterRead(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.AfterRead, resources, out var container, out var node)) + if (GetHook(ResourceHook.AfterRead, resources, out IResourceHookContainer container, out RootNode node)) { container.AfterRead((HashSet)node.UniqueResources, pipeline); } @@ -159,51 +168,53 @@ public void AfterRead(IEnumerable resources, ResourcePipel } /// - public void AfterCreate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable + public void AfterCreate(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.AfterCreate, resources, out var container, out var node)) + if (GetHook(ResourceHook.AfterCreate, resources, out IResourceHookContainer container, out RootNode node)) { container.AfterCreate((HashSet)node.UniqueResources, pipeline); } - Traverse(_traversalHelper.CreateNextLayer(node), - ResourceHook.AfterUpdateRelationship, + Traverse(_traversalHelper.CreateNextLayer(node), ResourceHook.AfterUpdateRelationship, (nextContainer, nextNode) => FireAfterUpdateRelationship(nextContainer, nextNode, pipeline)); } /// - public void AfterUpdate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable + public void AfterUpdate(IEnumerable resources, ResourcePipeline pipeline) + where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.AfterUpdate, resources, out var container, out var node)) + if (GetHook(ResourceHook.AfterUpdate, resources, out IResourceHookContainer container, out RootNode node)) { container.AfterUpdate((HashSet)node.UniqueResources, pipeline); } - Traverse(_traversalHelper.CreateNextLayer(node), - ResourceHook.AfterUpdateRelationship, + Traverse(_traversalHelper.CreateNextLayer(node), ResourceHook.AfterUpdateRelationship, (nextContainer, nextNode) => FireAfterUpdateRelationship(nextContainer, nextNode, pipeline)); } /// - public void AfterDelete(IEnumerable resources, ResourcePipeline pipeline, bool succeeded) where TResource : class, IIdentifiable + public void AfterDelete(IEnumerable resources, ResourcePipeline pipeline, bool succeeded) + where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.AfterDelete, resources, out var container, out var node)) + if (GetHook(ResourceHook.AfterDelete, resources, out IResourceHookContainer container, out RootNode node)) { container.AfterDelete((HashSet)node.UniqueResources, pipeline, succeeded); } } /// - /// For a given target and for a given type - /// , gets the hook container if the target - /// hook was implemented and should be executed. + /// For a given target and for a given type , gets the hook container if the target hook was + /// implemented and should be executed. /// /// Along the way, creates a traversable node from the root resource set. /// - /// true, if hook was implemented, false otherwise. - private bool GetHook(ResourceHook target, IEnumerable resources, - out IResourceHookContainer container, - out RootNode node) where TResource : class, IIdentifiable + /// + /// true, if hook was implemented, false otherwise. + /// + private bool GetHook(ResourceHook target, IEnumerable resources, out IResourceHookContainer container, + out RootNode node) + where TResource : class, IIdentifiable { node = _traversalHelper.CreateRootNode(resources); container = _executorHelper.GetResourceHookContainer(target); @@ -211,11 +222,11 @@ private bool GetHook(ResourceHook target, IEnumerable reso } /// - /// Traverses the nodes in a . + /// Traverses the nodes in a . /// private void Traverse(NodeLayer currentLayer, ResourceHook target, Action action) { - var nextLayer = currentLayer; + NodeLayer nextLayer = currentLayer; while (true) { @@ -234,7 +245,7 @@ private void TraverseNextLayer(NodeLayer nextLayer, Action - /// Recursively goes through the included relationships from JsonApiContext, - /// translates them to the corresponding hook containers and fires the + /// Recursively goes through the included relationships from JsonApiContext, translates them to the corresponding hook containers and fires the /// BeforeRead hook (if implemented) /// private void RecursiveBeforeRead(List relationshipChain, ResourcePipeline pipeline, List calledContainers) { while (true) { - var relationship = relationshipChain.First(); + RelationshipAttribute relationship = relationshipChain.First(); if (!calledContainers.Contains(relationship.RightType)) { calledContainers.Add(relationship.RightType); - var container = _executorHelper.GetResourceHookContainer(relationship.RightType, ResourceHook.BeforeRead); + IResourceHookContainer container = _executorHelper.GetResourceHookContainer(relationship.RightType, ResourceHook.BeforeRead); if (container != null) { @@ -280,20 +290,18 @@ private void RecursiveBeforeRead(List relationshipChain, } /// - /// Fires the nested before hooks for resources in the current + /// Fires the nested before hooks for resources in the current /// /// - /// For example: consider the case when the owner of article1 (one-to-one) - /// is being updated from owner_old to owner_new, where owner_new is currently already - /// related to article2. Then, the following nested hooks need to be fired in the following order. - /// First the BeforeUpdateRelationship should be for owner1, then the - /// BeforeImplicitUpdateRelationship hook should be fired for - /// owner2, and lastly the BeforeImplicitUpdateRelationship for article2. + /// For example: consider the case when the owner of article1 (one-to-one) is being updated from owner_old to owner_new, where owner_new is currently + /// already related to article2. Then, the following nested hooks need to be fired in the following order. First the BeforeUpdateRelationship should be + /// for owner1, then the BeforeImplicitUpdateRelationship hook should be fired for owner2, and lastly the BeforeImplicitUpdateRelationship for article2. + /// private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer layer) { foreach (IResourceNode node in layer) { - var nestedHookContainer = _executorHelper.GetResourceHookContainer(node.ResourceType, ResourceHook.BeforeUpdateRelationship); + IResourceHookContainer nestedHookContainer = _executorHelper.GetResourceHookContainer(node.ResourceType, ResourceHook.BeforeUpdateRelationship); IEnumerable uniqueResources = node.UniqueResources; RightType resourceType = node.ResourceType; Dictionary currentResourcesGrouped; @@ -304,8 +312,8 @@ private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer la { if (uniqueResources.Cast().Any()) { - var relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); - var dbValues = LoadDbValues(resourceType, uniqueResources, ResourceHook.BeforeUpdateRelationship, relationships); + RelationshipAttribute[] relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); + IEnumerable dbValues = LoadDbValues(resourceType, uniqueResources, ResourceHook.BeforeUpdateRelationship, relationships); // these are the resources of the current node grouped by // RelationshipAttributes that occurred in the previous layer @@ -317,9 +325,12 @@ private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer la currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); currentResourcesGroupedInverse = ReplaceKeysWithInverseRelationships(currentResourcesGrouped); - var resourcesByRelationship = CreateRelationshipHelper(resourceType, currentResourcesGroupedInverse, dbValues); - var allowedIds = CallHook(nestedHookContainer, ResourceHook.BeforeUpdateRelationship, ArrayFactory.Create(GetIds(uniqueResources), resourcesByRelationship, pipeline)).Cast(); - var updated = GetAllowedResources(uniqueResources, allowedIds); + IRelationshipsDictionary resourcesByRelationship = CreateRelationshipHelper(resourceType, currentResourcesGroupedInverse, dbValues); + + IEnumerable allowedIds = CallHook(nestedHookContainer, ResourceHook.BeforeUpdateRelationship, + ArrayFactory.Create(GetIds(uniqueResources), resourcesByRelationship, pipeline)).Cast(); + + HashSet updated = GetAllowedResources(uniqueResources, allowedIds); node.UpdateUnique(updated); node.Reassign(); } @@ -334,7 +345,8 @@ private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer la // For this, we need to query the database for the HasOneAttribute:owner // relationship of article1, which is referred to as the // left side of the HasOneAttribute:owner relationship. - var leftResources = node.RelationshipsFromPreviousLayer.GetLeftResources(); + Dictionary leftResources = node.RelationshipsFromPreviousLayer.GetLeftResources(); + if (leftResources.Any()) { // owner_old is loaded, which is an "implicitly affected resource" @@ -346,6 +358,7 @@ private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer la // For this, we need to query the database for the current owner // relationship value of owner_new. currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); + if (currentResourcesGrouped.Any()) { // rightResources is grouped by relationships from previous @@ -365,49 +378,61 @@ private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer la } /// - /// replaces the keys of the dictionary - /// with its inverse relationship attribute. + /// replaces the keys of the dictionary with its inverse relationship attribute. /// - /// Resources grouped by relationship attribute - private Dictionary ReplaceKeysWithInverseRelationships(Dictionary resourcesByRelationship) + /// + /// Resources grouped by relationship attribute + /// + private Dictionary ReplaceKeysWithInverseRelationships( + Dictionary resourcesByRelationship) { // when Article has one Owner (HasOneAttribute:owner) is set, there is no guarantee // that the inverse attribute was also set (Owner has one Article: HasOneAttr:article). // If it isn't, JsonApiDotNetCore currently knows nothing about this relationship pointing back, and it // currently cannot fire hooks for resources resolved through inverse relationships. - var inversableRelationshipAttributes = resourcesByRelationship.Where(kvp => kvp.Key.InverseNavigationProperty != null); + IEnumerable> inversableRelationshipAttributes = + resourcesByRelationship.Where(kvp => kvp.Key.InverseNavigationProperty != null); + return inversableRelationshipAttributes.ToDictionary(kvp => _resourceGraph.GetInverseRelationship(kvp.Key), kvp => kvp.Value); } /// - /// Given a source of resources, gets the implicitly affected resources - /// from the database and calls the BeforeImplicitUpdateRelationship hook. + /// Given a source of resources, gets the implicitly affected resources from the database and calls the BeforeImplicitUpdateRelationship hook. /// - private void FireForAffectedImplicits(Type resourceTypeToInclude, Dictionary implicitsTarget, ResourcePipeline pipeline, IEnumerable existingImplicitResources = null) + private void FireForAffectedImplicits(Type resourceTypeToInclude, Dictionary implicitsTarget, + ResourcePipeline pipeline, IEnumerable existingImplicitResources = null) { - var container = _executorHelper.GetResourceHookContainer(resourceTypeToInclude, ResourceHook.BeforeImplicitUpdateRelationship); + IResourceHookContainer container = _executorHelper.GetResourceHookContainer(resourceTypeToInclude, ResourceHook.BeforeImplicitUpdateRelationship); + if (container == null) { return; } - var implicitAffected = _executorHelper.LoadImplicitlyAffected(implicitsTarget, existingImplicitResources); + Dictionary + implicitAffected = _executorHelper.LoadImplicitlyAffected(implicitsTarget, existingImplicitResources); + if (!implicitAffected.Any()) { return; } - var inverse = implicitAffected.ToDictionary(kvp => _resourceGraph.GetInverseRelationship(kvp.Key), kvp => kvp.Value); - var resourcesByRelationship = CreateRelationshipHelper(resourceTypeToInclude, inverse); + Dictionary inverse = + implicitAffected.ToDictionary(kvp => _resourceGraph.GetInverseRelationship(kvp.Key), kvp => kvp.Value); + + IRelationshipsDictionary resourcesByRelationship = CreateRelationshipHelper(resourceTypeToInclude, inverse); CallHook(container, ResourceHook.BeforeImplicitUpdateRelationship, ArrayFactory.Create(resourcesByRelationship, pipeline)); } /// - /// checks that the collection does not contain more than one item when - /// relevant (eg AfterRead from GetSingle pipeline). + /// checks that the collection does not contain more than one item when relevant (eg AfterRead from GetSingle pipeline). /// - /// The collection returned from the hook - /// The pipeline from which the hook was fired + /// + /// The collection returned from the hook + /// + /// + /// The pipeline from which the hook was fired + /// [AssertionMethod] private void ValidateHookResponse(IEnumerable returnedList, ResourcePipeline pipeline = 0) { @@ -419,11 +444,11 @@ private void ValidateHookResponse(IEnumerable returnedList, ResourcePipeli } /// - /// A helper method to call a hook on reflectively. + /// A helper method to call a hook on reflectively. /// private IEnumerable CallHook(IResourceHookContainer container, ResourceHook hook, object[] arguments) { - var method = container.GetType().GetMethod(hook.ToString("G")); + MethodInfo? method = container.GetType().GetMethod(hook.ToString("G")); // note that some of the hooks return "void". When these hooks, the // are called reflectively with Invoke like here, the return value // is just null, so we don't have to worry about casting issues here. @@ -431,7 +456,7 @@ private IEnumerable CallHook(IResourceHookContainer container, ResourceHook hook } /// - /// If the method, unwrap and throw the actual exception. + /// If the method, unwrap and throw the actual exception. /// private object ThrowJsonApiExceptionOnError(Func action) { @@ -446,42 +471,46 @@ private object ThrowJsonApiExceptionOnError(Func action) } /// - /// Helper method to instantiate AffectedRelationships for a given - /// If are included, the values of the entries in need to be replaced with these values. + /// Helper method to instantiate AffectedRelationships for a given If are included, the + /// values of the entries in need to be replaced with these values. /// - /// The relationship helper. - private IRelationshipsDictionary CreateRelationshipHelper(RightType resourceType, Dictionary prevLayerRelationships, IEnumerable dbValues = null) + /// + /// The relationship helper. + /// + private IRelationshipsDictionary CreateRelationshipHelper(RightType resourceType, Dictionary prevLayerRelationships, + IEnumerable dbValues = null) { - var prevLayerRelationshipsWithDbValues = prevLayerRelationships; + Dictionary prevLayerRelationshipsWithDbValues = prevLayerRelationships; if (dbValues != null) { prevLayerRelationshipsWithDbValues = ReplaceWithDbValues(prevLayerRelationshipsWithDbValues, dbValues.Cast()); } - return (IRelationshipsDictionary)TypeHelper.CreateInstanceOfOpenType(typeof(RelationshipsDictionary<>), resourceType, true, prevLayerRelationshipsWithDbValues); + return (IRelationshipsDictionary)TypeHelper.CreateInstanceOfOpenType(typeof(RelationshipsDictionary<>), resourceType, true, + prevLayerRelationshipsWithDbValues); } /// - /// Replaces the resources in the values of the prevLayerRelationships dictionary - /// with the corresponding resources loaded from the db. + /// Replaces the resources in the values of the prevLayerRelationships dictionary with the corresponding resources loaded from the db. /// - private Dictionary ReplaceWithDbValues(Dictionary prevLayerRelationships, IEnumerable dbValues) + private Dictionary ReplaceWithDbValues(Dictionary prevLayerRelationships, + IEnumerable dbValues) { - foreach (var key in prevLayerRelationships.Keys.ToList()) + foreach (RelationshipAttribute key in prevLayerRelationships.Keys.ToList()) { - var source = prevLayerRelationships[key].Cast().Select(resource => + IEnumerable source = prevLayerRelationships[key].Cast().Select(resource => dbValues.Single(dbResource => dbResource.StringId == resource.StringId)); - var replaced = TypeHelper.CopyToList(source, key.LeftType); + IList replaced = TypeHelper.CopyToList(source, key.LeftType); prevLayerRelationships[key] = TypeHelper.CreateHashSetFor(key.LeftType, replaced); } + return prevLayerRelationships; } /// - /// Filter the source set by removing the resources with ID that are not - /// in . + /// Filter the source set by removing the resources with ID that are not in . /// private HashSet GetAllowedResources(IEnumerable source, IEnumerable allowedIds) { @@ -489,16 +518,26 @@ private HashSet GetAllowedResources(IEnumerable source, IEnumerab } /// - /// given the set of , it will load all the - /// values from the database of these resources. + /// given the set of , it will load all the values from the database of these resources. /// - /// The db values. - /// type of the resources to be loaded - /// The set of resources to load the db values for - /// The hook in which the db values will be displayed. - /// Relationships from to the next layer: - /// this indicates which relationships will be included on . - private IEnumerable LoadDbValues(Type resourceType, IEnumerable uniqueResources, ResourceHook targetHook, RelationshipAttribute[] relationshipsToNextLayer) + /// + /// The db values. + /// + /// + /// type of the resources to be loaded + /// + /// + /// The set of resources to load the db values for + /// + /// + /// The hook in which the db values will be displayed. + /// + /// + /// Relationships from to the next layer: this indicates which relationships will be included on + /// . + /// + private IEnumerable LoadDbValues(Type resourceType, IEnumerable uniqueResources, ResourceHook targetHook, + RelationshipAttribute[] relationshipsToNextLayer) { // We only need to load database values if the target hook of this hook execution // cycle is compatible with displaying database values and has this option enabled. @@ -515,21 +554,25 @@ private IEnumerable LoadDbValues(Type resourceType, IEnumerable uniqueResources, /// private void FireAfterUpdateRelationship(IResourceHookContainer container, IResourceNode node, ResourcePipeline pipeline) { - Dictionary currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); + // the relationships attributes in currentResourcesGrouped will be pointing from a // resource in the previous layer to a resource in the current (nested) layer. // For the nested hook we need to replace these attributes with their inverse. // See the FireNestedBeforeUpdateHooks method for a more detailed example. - var resourcesByRelationship = CreateRelationshipHelper(node.ResourceType, ReplaceKeysWithInverseRelationships(currentResourcesGrouped)); + IRelationshipsDictionary resourcesByRelationship = + CreateRelationshipHelper(node.ResourceType, ReplaceKeysWithInverseRelationships(currentResourcesGrouped)); + CallHook(container, ResourceHook.AfterUpdateRelationship, ArrayFactory.Create(resourcesByRelationship, pipeline)); } /// - /// Returns a list of StringIds from a list of IIdentifiable resources (). + /// Returns a list of StringIds from a list of IIdentifiable resources (). /// /// The ids. - /// IIdentifiable resources. + /// + /// IIdentifiable resources. + /// private HashSet GetIds(IEnumerable resources) { return new HashSet(resources.Cast().Select(e => e.StringId)); diff --git a/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutorFacade.cs b/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutorFacade.cs index 136666500f..21c03ab33f 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutorFacade.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutorFacade.cs @@ -8,8 +8,8 @@ namespace JsonApiDotNetCore.Hooks.Internal { /// - /// Facade for hooks that invokes callbacks on , - /// which is used when is true. + /// Facade for hooks that invokes callbacks on , which is used when + /// is true. /// internal sealed class ResourceHookExecutorFacade : IResourceHookExecutorFacade { @@ -128,7 +128,7 @@ public object OnReturnRelationship(object resourceOrResources) if (resourceOrResources is IIdentifiable) { - var resources = ObjectExtensions.AsList((dynamic)resourceOrResources); + dynamic resources = ObjectExtensions.AsList((dynamic)resourceOrResources); return Enumerable.SingleOrDefault(_resourceHookExecutor.OnReturn(resources, ResourcePipeline.GetRelationship)); } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ChildNode.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ChildNode.cs index 8e0ee92706..7d9bbbe8a2 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ChildNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ChildNode.cs @@ -10,7 +10,8 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal /// Child node in the tree /// /// - internal sealed class ChildNode : IResourceNode where TResource : class, IIdentifiable + internal sealed class ChildNode : IResourceNode + where TResource : class, IIdentifiable { private readonly IdentifiableComparer _comparer = IdentifiableComparer.Instance; private readonly RelationshipsFromPreviousLayer _relationshipsFromPreviousLayer; @@ -44,7 +45,8 @@ public ChildNode(RelationshipProxy[] nextLayerRelationships, RelationshipsFromPr public void UpdateUnique(IEnumerable updated) { List cast = updated.Cast().ToList(); - foreach (var group in _relationshipsFromPreviousLayer) + + foreach (RelationshipGroup group in _relationshipsFromPreviousLayer) { group.RightResources = new HashSet(group.RightResources.Intersect(cast, _comparer).Cast()); } @@ -56,10 +58,11 @@ public void UpdateUnique(IEnumerable updated) public void Reassign(IEnumerable updated = null) { var unique = (HashSet)UniqueResources; - foreach (var group in _relationshipsFromPreviousLayer) + + foreach (RelationshipGroup group in _relationshipsFromPreviousLayer) { - var proxy = group.Proxy; - var leftResources = group.LeftResources; + RelationshipProxy proxy = group.Proxy; + HashSet leftResources = group.LeftResources; Reassign(leftResources, proxy, unique); } @@ -69,18 +72,20 @@ private void Reassign(IEnumerable leftResources, RelationshipProx { foreach (IIdentifiable left in leftResources) { - var currentValue = proxy.GetValue(left); + object currentValue = proxy.GetValue(left); if (currentValue is IEnumerable relationshipCollection) { - var intersection = relationshipCollection.Intersect(unique, _comparer); - IEnumerable typedCollection = - TypeHelper.CopyToTypedCollection(intersection, relationshipCollection.GetType()); + IEnumerable intersection = relationshipCollection.Intersect(unique, _comparer); + IEnumerable typedCollection = TypeHelper.CopyToTypedCollection(intersection, relationshipCollection.GetType()); proxy.SetValue(left, typedCollection); } else if (currentValue is IIdentifiable relationshipSingle) { - if (!unique.Intersect(new HashSet {relationshipSingle}, _comparer).Any()) + if (!unique.Intersect(new HashSet + { + relationshipSingle + }, _comparer).Any()) { proxy.SetValue(left, null); } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipGroup.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipGroup.cs index 42b20ced84..54d93c7905 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipGroup.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipGroup.cs @@ -8,4 +8,4 @@ internal interface IRelationshipGroup RelationshipProxy Proxy { get; } HashSet LeftResources { get; } } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipsFromPreviousLayer.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipsFromPreviousLayer.cs index ed7b6fdf00..98886e1b10 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipsFromPreviousLayer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipsFromPreviousLayer.cs @@ -12,12 +12,17 @@ internal interface IRelationshipsFromPreviousLayer /// /// Grouped by relationship to the previous layer, gets all the resources of the current layer /// - /// The right side resources. + /// + /// The right side resources. + /// Dictionary GetRightResources(); + /// /// Grouped by relationship to the previous layer, gets all the resources of the previous layer /// - /// The right side resources. + /// + /// The right side resources. + /// Dictionary GetLeftResources(); } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IResourceNode.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IResourceNode.cs index de364e8ccc..dcd350666c 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IResourceNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IResourceNode.cs @@ -12,28 +12,33 @@ internal interface IResourceNode /// Each node represents the resources of a given type throughout a particular layer. /// RightType ResourceType { get; } + /// /// The unique set of resources in this node. Note that these are all of the same type. /// IEnumerable UniqueResources { get; } + /// /// Relationships to the next layer /// - /// The relationships to next layer. + /// + /// The relationships to next layer. + /// RelationshipProxy[] RelationshipsToNextLayer { get; } + /// /// Relationships to the previous layer /// IRelationshipsFromPreviousLayer RelationshipsFromPreviousLayer { get; } /// - /// A helper method to assign relationships to the previous layer after firing hooks. - /// Or, in case of the root node, to update the original source enumerable. + /// A helper method to assign relationships to the previous layer after firing hooks. Or, in case of the root node, to update the original source + /// enumerable. /// void Reassign(IEnumerable source = null); + /// - /// A helper method to internally update the unique set of resources as a result of - /// a filter action in a hook. + /// A helper method to internally update the unique set of resources as a result of a filter action in a hook. /// /// Updated. void UpdateUnique(IEnumerable updated); diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ITraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ITraversalHelper.cs index 4ca6ddfac0..a4db2e83f3 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ITraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ITraversalHelper.cs @@ -9,18 +9,26 @@ internal interface ITraversalHelper /// Crates the next layer /// NodeLayer CreateNextLayer(IResourceNode node); + /// /// Creates the next layer based on the nodes provided /// NodeLayer CreateNextLayer(IEnumerable nodes); + /// - /// Creates a root node for breadth-first-traversal (BFS). Note that typically, in - /// JsonApiDotNetCore, the root layer will be homogeneous. Also, because it is the first layer, - /// there can be no relationships to previous layers, only to next layers. + /// Creates a root node for breadth-first-traversal (BFS). Note that typically, in JsonApiDotNetCore, the root layer will be homogeneous. Also, because + /// it is the first layer, there can be no relationships to previous layers, only to next layers. /// - /// The root node. - /// Root resources. - /// The 1st type parameter. - RootNode CreateRootNode(IEnumerable rootResources) where TResource : class, IIdentifiable; + /// + /// The root node. + /// + /// + /// Root resources. + /// + /// + /// The 1st type parameter. + /// + RootNode CreateRootNode(IEnumerable rootResources) + where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeLayer.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeLayer.cs index 711ddd28fb..bad0938a20 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeLayer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeLayer.cs @@ -6,21 +6,21 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal { /// - /// A helper class that represents all resources in the current layer that - /// are being traversed for which hooks will be executed (see IResourceHookExecutor) + /// A helper class that represents all resources in the current layer that are being traversed for which hooks will be executed (see + /// IResourceHookExecutor) /// internal sealed class NodeLayer : IEnumerable { private readonly List _collection; - public bool AnyResources() + public NodeLayer(List nodes) { - return _collection.Any(n => n.UniqueResources.Cast().Any()); + _collection = nodes; } - public NodeLayer(List nodes) + public bool AnyResources() { - _collection = nodes; + return _collection.Any(n => n.UniqueResources.Cast().Any()); } public IEnumerator GetEnumerator() @@ -33,4 +33,4 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipGroup.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipGroup.cs index 12ac8382cd..69a947f9f9 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipGroup.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipGroup.cs @@ -3,11 +3,13 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal { - internal sealed class RelationshipGroup : IRelationshipGroup where TRight : class, IIdentifiable + internal sealed class RelationshipGroup : IRelationshipGroup + where TRight : class, IIdentifiable { public RelationshipProxy Proxy { get; } public HashSet LeftResources { get; } public HashSet RightResources { get; internal set; } + public RelationshipGroup(RelationshipProxy proxy, HashSet leftResources, HashSet rightResources) { Proxy = proxy; diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipProxy.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipProxy.cs index 86677d1907..a18f68ce6e 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipProxy.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipProxy.cs @@ -7,15 +7,10 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal { /// - /// A class used internally for resource hook execution. Not intended for developer use. - /// - /// A wrapper for RelationshipAttribute with an abstraction layer that works on the - /// getters and setters of relationships. These are different in the case of - /// HasMany vs HasManyThrough, and HasManyThrough. - /// It also depends on if the through type (eg ArticleTags) - /// is identifiable (in which case we will traverse through - /// it and fire hooks for it, if defined) or not (in which case we skip - /// ArticleTags and go directly to Tags. + /// A class used internally for resource hook execution. Not intended for developer use. A wrapper for RelationshipAttribute with an abstraction layer + /// that works on the getters and setters of relationships. These are different in the case of HasMany vs HasManyThrough, and HasManyThrough. It also + /// depends on if the through type (eg ArticleTags) is identifiable (in which case we will traverse through it and fire hooks for it, if defined) or not + /// (in which case we skip ArticleTags and go directly to Tags. /// internal sealed class RelationshipProxy { @@ -24,10 +19,8 @@ internal sealed class RelationshipProxy public Type LeftType => Attribute.LeftType; /// - /// The target type for this relationship attribute. - /// For HasOne has HasMany this is trivial: just the right-hand side. - /// For HasManyThrough it is either the ThroughProperty (when the through resource is - /// Identifiable) or it is the right-hand side (when the through resource is not identifiable) + /// The target type for this relationship attribute. For HasOne has HasMany this is trivial: just the right-hand side. For HasManyThrough it is either + /// the ThroughProperty (when the through resource is Identifiable) or it is the right-hand side (when the through resource is not identifiable) /// public Type RightType { get; } @@ -40,6 +33,7 @@ public RelationshipProxy(RelationshipAttribute attr, Type relatedType, bool isCo RightType = relatedType; Attribute = attr; IsContextRelation = isContextRelation; + if (attr is HasManyThroughAttribute throughAttr) { _skipThroughType |= RightType != throughAttr.ThroughType; @@ -47,12 +41,15 @@ public RelationshipProxy(RelationshipAttribute attr, Type relatedType, bool isCo } /// - /// Gets the relationship value for a given parent resource. - /// Internally knows how to do this depending on the type of RelationshipAttribute - /// that this RelationshipProxy encapsulates. + /// Gets the relationship value for a given parent resource. Internally knows how to do this depending on the type of RelationshipAttribute that this + /// RelationshipProxy encapsulates. /// - /// The relationship value. - /// Parent resource. + /// + /// The relationship value. + /// + /// + /// Parent resource. + /// public object GetValue(IIdentifiable resource) { if (Attribute is HasManyThroughAttribute hasManyThrough) @@ -61,16 +58,19 @@ public object GetValue(IIdentifiable resource) { return hasManyThrough.ThroughProperty.GetValue(resource); } + var collection = new List(); var throughResources = (IEnumerable)hasManyThrough.ThroughProperty.GetValue(resource); + if (throughResources == null) { return null; } - foreach (var throughResource in throughResources) + foreach (object? throughResource in throughResources) { var rightResource = (IIdentifiable)hasManyThrough.RightProperty.GetValue(throughResource); + if (rightResource == null) { continue; @@ -81,16 +81,20 @@ public object GetValue(IIdentifiable resource) return collection; } + return Attribute.GetValue(resource); } /// - /// Set the relationship value for a given parent resource. - /// Internally knows how to do this depending on the type of RelationshipAttribute - /// that this RelationshipProxy encapsulates. + /// Set the relationship value for a given parent resource. Internally knows how to do this depending on the type of RelationshipAttribute that this + /// RelationshipProxy encapsulates. /// - /// Parent resource. - /// The relationship value. + /// + /// Parent resource. + /// + /// + /// The relationship value. + /// public void SetValue(IIdentifiable resource, object value) { if (Attribute is HasManyThroughAttribute hasManyThrough) @@ -104,8 +108,9 @@ public void SetValue(IIdentifiable resource, object value) var throughResources = (IEnumerable)hasManyThrough.ThroughProperty.GetValue(resource); var filteredList = new List(); - var rightResources = TypeHelper.CopyToList((IEnumerable)value, RightType); - foreach (var throughResource in throughResources ?? Array.Empty()) + IList rightResources = TypeHelper.CopyToList((IEnumerable)value, RightType); + + foreach (object? throughResource in throughResources ?? Array.Empty()) { if (rightResources.Contains(hasManyThrough.RightProperty.GetValue(throughResource))) { @@ -113,7 +118,7 @@ public void SetValue(IIdentifiable resource, object value) } } - var collectionValue = TypeHelper.CopyToTypedCollection(filteredList, hasManyThrough.ThroughProperty.PropertyType); + IEnumerable collectionValue = TypeHelper.CopyToTypedCollection(filteredList, hasManyThrough.ThroughProperty.PropertyType); hasManyThrough.ThroughProperty.SetValue(resource, collectionValue); return; } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipsFromPreviousLayer.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipsFromPreviousLayer.cs index d5bd5e416a..add2107d7e 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipsFromPreviousLayer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipsFromPreviousLayer.cs @@ -6,7 +6,8 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal { - internal sealed class RelationshipsFromPreviousLayer : IRelationshipsFromPreviousLayer, IEnumerable> where TRightResource : class, IIdentifiable + internal sealed class RelationshipsFromPreviousLayer : IRelationshipsFromPreviousLayer, IEnumerable> + where TRightResource : class, IIdentifiable { private readonly IEnumerable> _collection; diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RootNode.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RootNode.cs index 166b98f0a2..14b6296f6f 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RootNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RootNode.cs @@ -8,10 +8,10 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal { /// - /// The root node class of the breadth-first-traversal of resource data structures - /// as performed by the + /// The root node class of the breadth-first-traversal of resource data structures as performed by the /// - internal sealed class RootNode : IResourceNode where TResource : class, IIdentifiable + internal sealed class RootNode : IResourceNode + where TResource : class, IIdentifiable { private readonly IdentifiableComparer _comparer = IdentifiableComparer.Instance; private readonly RelationshipProxy[] _allRelationshipsToNextLayer; @@ -20,21 +20,6 @@ internal sealed class RootNode : IResourceNode where TResource : clas public IEnumerable UniqueResources => _uniqueResources; public RelationshipProxy[] RelationshipsToNextLayer { get; } - public Dictionary> LeftsToNextLayerByRelationships() - { - return _allRelationshipsToNextLayer - .GroupBy(proxy => proxy.RightType) - .ToDictionary(gdc => gdc.Key, gdc => gdc.ToDictionary(p => p.Attribute, _ => UniqueResources)); - } - - /// - /// The current layer resources grouped by affected relationship to the next layer - /// - public Dictionary LeftsToNextLayer() - { - return RelationshipsToNextLayer.ToDictionary(p => p.Attribute, _ => UniqueResources); - } - /// /// The root node does not have a parent layer and therefore does not have any relationships to any previous layer /// @@ -48,20 +33,34 @@ public RootNode(IEnumerable uniqueResources, RelationshipProxy[] popu _allRelationshipsToNextLayer = allRelationships; } + public Dictionary> LeftsToNextLayerByRelationships() + { + return _allRelationshipsToNextLayer.GroupBy(proxy => proxy.RightType) + .ToDictionary(gdc => gdc.Key, gdc => gdc.ToDictionary(p => p.Attribute, _ => UniqueResources)); + } + + /// + /// The current layer resources grouped by affected relationship to the next layer + /// + public Dictionary LeftsToNextLayer() + { + return RelationshipsToNextLayer.ToDictionary(p => p.Attribute, _ => UniqueResources); + } + /// - /// Update the internal list of affected resources. + /// Update the internal list of affected resources. /// /// Updated. public void UpdateUnique(IEnumerable updated) { - var cast = updated.Cast().ToList(); - var intersected = _uniqueResources.Intersect(cast, _comparer).Cast(); + List cast = updated.Cast().ToList(); + IEnumerable intersected = _uniqueResources.Intersect(cast, _comparer).Cast(); _uniqueResources = new HashSet(intersected); } public void Reassign(IEnumerable source = null) { - var ids = _uniqueResources.Select(ue => ue.StringId); + IEnumerable ids = _uniqueResources.Select(ue => ue.StringId); if (source is HashSet hashSet) { diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/TraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/TraversalHelper.cs index b27c0762ea..20283fd33a 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/TraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/TraversalHelper.cs @@ -1,4 +1,3 @@ -using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -12,59 +11,68 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal { /// - /// A helper class used by the to traverse through - /// resource data structures (trees), allowing for a breadth-first-traversal - /// - /// It creates nodes for each layer. - /// Typically, the first layer is homogeneous (all resources have the same type), - /// and further nodes can be mixed. + /// A helper class used by the to traverse through resource data structures (trees), allowing for a + /// breadth-first-traversal It creates nodes for each layer. Typically, the first layer is homogeneous (all resources have the same type), and further + /// nodes can be mixed. /// internal sealed class TraversalHelper : ITraversalHelper { private readonly IdentifiableComparer _comparer = IdentifiableComparer.Instance; private readonly IResourceGraph _resourceGraph; private readonly ITargetedFields _targetedFields; + /// - /// Keeps track of which resources has already been traversed through, to prevent - /// infinite loops in eg cyclic data structures. + /// A mapper from to . See the latter for more details. /// - private Dictionary> _processedResources; + private readonly Dictionary _relationshipProxies = new Dictionary(); + /// - /// A mapper from to . - /// See the latter for more details. + /// Keeps track of which resources has already been traversed through, to prevent infinite loops in eg cyclic data structures. /// - private readonly Dictionary _relationshipProxies = new Dictionary(); - public TraversalHelper( - IResourceGraph resourceGraph, - ITargetedFields targetedFields) + private Dictionary> _processedResources; + + public TraversalHelper(IResourceGraph resourceGraph, ITargetedFields targetedFields) { _targetedFields = targetedFields; _resourceGraph = resourceGraph; } /// - /// Creates a root node for breadth-first-traversal. Note that typically, in - /// JsonApiDotNetCore, the root layer will be homogeneous. Also, because it is the first layer, - /// there can be no relationships to previous layers, only to next layers. + /// Creates a root node for breadth-first-traversal. Note that typically, in JsonApiDotNetCore, the root layer will be homogeneous. Also, because it is + /// the first layer, there can be no relationships to previous layers, only to next layers. /// - /// The root node. - /// Root resources. - /// The 1st type parameter. - public RootNode CreateRootNode(IEnumerable rootResources) where TResource : class, IIdentifiable + /// + /// The root node. + /// + /// + /// Root resources. + /// + /// + /// The 1st type parameter. + /// + public RootNode CreateRootNode(IEnumerable rootResources) + where TResource : class, IIdentifiable { _processedResources = new Dictionary>(); RegisterRelationshipProxies(typeof(TResource)); - var uniqueResources = ProcessResources(rootResources); - var populatedRelationshipsToNextLayer = GetPopulatedRelationships(typeof(TResource), uniqueResources); - var allRelationshipsFromType = _relationshipProxies.Select(entry => entry.Value).Where(proxy => proxy.LeftType == typeof(TResource)).ToArray(); + HashSet uniqueResources = ProcessResources(rootResources); + RelationshipProxy[] populatedRelationshipsToNextLayer = GetPopulatedRelationships(typeof(TResource), uniqueResources); + + RelationshipProxy[] allRelationshipsFromType = + _relationshipProxies.Select(entry => entry.Value).Where(proxy => proxy.LeftType == typeof(TResource)).ToArray(); + return new RootNode(uniqueResources, populatedRelationshipsToNextLayer, allRelationshipsFromType); } /// /// Create the first layer after the root layer (based on the root node) /// - /// The next layer. - /// Root node. + /// + /// The next layer. + /// + /// + /// Root node. + /// public NodeLayer CreateNextLayer(IResourceNode rootNode) { return CreateNextLayer(rootNode.AsEnumerable()); @@ -73,29 +81,32 @@ public NodeLayer CreateNextLayer(IResourceNode rootNode) /// /// Create a next layer from any previous layer /// - /// The next layer. + /// + /// The next layer. + /// /// Nodes. public NodeLayer CreateNextLayer(IEnumerable nodes) { // first extract resources by parsing populated relationships in the resources // of previous layer - var (lefts, rights) = ExtractResources(nodes); + (Dictionary> lefts, Dictionary> rights) = ExtractResources(nodes); // group them conveniently so we can make ChildNodes of them: // there might be several relationship attributes in rights dictionary // that point to the same right type. - var leftsGrouped = GroupByRightTypeOfRelationship(lefts); + Dictionary>>> leftsGrouped = GroupByRightTypeOfRelationship(lefts); // convert the groups into child nodes - var nextNodes = leftsGrouped.Select(entry => + List nextNodes = leftsGrouped.Select(entry => { - var nextNodeType = entry.Key; + RightType nextNodeType = entry.Key; RegisterRelationshipProxies(nextNodeType); var populatedRelationships = new List(); - var relationshipsToPreviousLayer = entry.Value.Select(grouped => + + List relationshipsToPreviousLayer = entry.Value.Select(grouped => { - var proxy = grouped.Key; + RelationshipProxy proxy = grouped.Key; populatedRelationships.AddRange(GetPopulatedRelationships(nextNodeType, rights[proxy])); return CreateRelationshipGroupInstance(nextNodeType, proxy, grouped.Value, rights[proxy]); }).ToList(); @@ -107,19 +118,20 @@ public NodeLayer CreateNextLayer(IEnumerable nodes) } /// - /// iterates through the dictionary and groups the values - /// by matching right type of the keys (which are relationship attributes) + /// iterates through the dictionary and groups the values by matching right type of the keys (which are relationship + /// attributes) /// - private Dictionary>>> GroupByRightTypeOfRelationship(Dictionary> relationships) + private Dictionary>>> GroupByRightTypeOfRelationship( + Dictionary> relationships) { return relationships.GroupBy(kvp => kvp.Key.RightType).ToDictionary(gdc => gdc.Key, gdc => gdc.ToList()); } /// - /// Extracts the resources for the current layer by going through all populated relationships - /// of the (left resources of the previous layer. + /// Extracts the resources for the current layer by going through all populated relationships of the (left resources of the previous layer. /// - private (Dictionary>, Dictionary>) ExtractResources(IEnumerable leftNodes) + private (Dictionary>, Dictionary>) ExtractResources( + IEnumerable leftNodes) { // RelationshipAttr_prevLayer->currentLayer => prevLayerResources var leftResourcesGrouped = new Dictionary>(); @@ -127,27 +139,28 @@ private DictionarycurrentLayer => currentLayerResources var rightResourcesGrouped = new Dictionary>(); - foreach (var node in leftNodes) + foreach (IResourceNode node in leftNodes) { - var leftResources = node.UniqueResources; - var relationships = node.RelationshipsToNextLayer; + IEnumerable leftResources = node.UniqueResources; + RelationshipProxy[] relationships = node.RelationshipsToNextLayer; ExtractLeftResources(leftResources, relationships, rightResourcesGrouped, leftResourcesGrouped); } - var processResourcesMethod = GetType().GetMethod(nameof(ProcessResources), BindingFlags.NonPublic | BindingFlags.Instance); - foreach (var kvp in rightResourcesGrouped) + MethodInfo? processResourcesMethod = GetType().GetMethod(nameof(ProcessResources), BindingFlags.NonPublic | BindingFlags.Instance); + + foreach (KeyValuePair> kvp in rightResourcesGrouped) { - var type = kvp.Key.RightType; - var list = TypeHelper.CopyToList(kvp.Value, type); + RightType type = kvp.Key.RightType; + IList list = TypeHelper.CopyToList(kvp.Value, type); processResourcesMethod!.MakeGenericMethod(type).Invoke(this, ArrayFactory.Create(list)); } return (leftResourcesGrouped, rightResourcesGrouped); } - private void ExtractLeftResources(IEnumerable leftResources, RelationshipProxy[] relationships, Dictionary> rightResourcesGrouped, - Dictionary> leftResourcesGrouped) + private void ExtractLeftResources(IEnumerable leftResources, RelationshipProxy[] relationships, + Dictionary> rightResourcesGrouped, Dictionary> leftResourcesGrouped) { foreach (IIdentifiable leftResource in leftResources) { @@ -156,12 +169,12 @@ private void ExtractLeftResources(IEnumerable leftResources, RelationshipProxy[] } private void ExtractLeftResource(IIdentifiable leftResource, RelationshipProxy[] relationships, - Dictionary> rightResourcesGrouped, - Dictionary> leftResourcesGrouped) + Dictionary> rightResourcesGrouped, Dictionary> leftResourcesGrouped) { - foreach (var proxy in relationships) + foreach (RelationshipProxy proxy in relationships) { - var relationshipValue = proxy.GetValue(leftResource); + object relationshipValue = proxy.GetValue(leftResource); + // skip this relationship if it's not populated if (!proxy.IsContextRelation && relationshipValue == null) { @@ -172,7 +185,8 @@ private void ExtractLeftResource(IIdentifiable leftResource, RelationshipProxy[] { // in the case of a to-one relationship, the assigned value // will not be a list. We therefore first wrap it in a list. - var list = TypeHelper.CreateListFor(proxy.RightType); + IList list = TypeHelper.CreateListFor(proxy.RightType); + if (relationshipValue != null) { list.Add(relationshipValue); @@ -181,7 +195,8 @@ private void ExtractLeftResource(IIdentifiable leftResource, RelationshipProxy[] rightResources = list; } - var uniqueRightResources = UniqueInTree(rightResources.Cast(), proxy.RightType); + HashSet uniqueRightResources = UniqueInTree(rightResources.Cast(), proxy.RightType); + if (proxy.IsContextRelation || uniqueRightResources.Any()) { AddToRelationshipGroup(rightResourcesGrouped, proxy, uniqueRightResources); @@ -191,35 +206,46 @@ private void ExtractLeftResource(IIdentifiable leftResource, RelationshipProxy[] } /// - /// Get all populated relationships known in the current tree traversal from a - /// left type to any right type + /// Get all populated relationships known in the current tree traversal from a left type to any right type /// - /// The relationships. + /// + /// The relationships. + /// private RelationshipProxy[] GetPopulatedRelationships(LeftType leftType, IEnumerable lefts) { - var relationshipsFromLeftToRight = _relationshipProxies.Select(entry => entry.Value).Where(proxy => proxy.LeftType == leftType); + IEnumerable relationshipsFromLeftToRight = + _relationshipProxies.Select(entry => entry.Value).Where(proxy => proxy.LeftType == leftType); + return relationshipsFromLeftToRight.Where(proxy => proxy.IsContextRelation || lefts.Any(p => proxy.GetValue(p) != null)).ToArray(); } /// - /// Registers the resources as "seen" in the tree traversal, extracts any new s from it. + /// Registers the resources as "seen" in the tree traversal, extracts any new s from it. /// - /// The resources. - /// Incoming resources. - /// The 1st type parameter. - private HashSet ProcessResources(IEnumerable incomingResources) where TResource : class, IIdentifiable + /// + /// The resources. + /// + /// + /// Incoming resources. + /// + /// + /// The 1st type parameter. + /// + private HashSet ProcessResources(IEnumerable incomingResources) + where TResource : class, IIdentifiable { - Type type = typeof(TResource); - var newResources = UniqueInTree(incomingResources, type); + RightType type = typeof(TResource); + HashSet newResources = UniqueInTree(incomingResources, type); RegisterProcessedResources(newResources, type); return newResources; } /// - /// Parses all relationships from to - /// other models in the resource resourceGraphs by constructing RelationshipProxies . + /// Parses all relationships from to other models in the resource resourceGraphs by constructing RelationshipProxies . /// - /// The type to parse + /// + /// The type to parse + /// private void RegisterRelationshipProxies(RightType type) { foreach (RelationshipAttribute attr in _resourceGraph.GetRelationships(type)) @@ -233,7 +259,8 @@ private void RegisterRelationshipProxies(RightType type) { RightType rightType = GetRightTypeFromRelationship(attr); bool isContextRelation = false; - var relationshipsToUpdate = _targetedFields.Relationships; + ISet relationshipsToUpdate = _targetedFields.Relationships; + if (relationshipsToUpdate != null) { isContextRelation = relationshipsToUpdate.Contains(attr); @@ -248,94 +275,114 @@ private void RegisterRelationshipProxies(RightType type) /// /// Registers the processed resources in the dictionary grouped by type /// - /// Resources to register - /// Resource type. - private void RegisterProcessedResources(IEnumerable resources, Type resourceType) + /// + /// Resources to register + /// + /// + /// Resource type. + /// + private void RegisterProcessedResources(IEnumerable resources, RightType resourceType) { - var processedResources = GetProcessedResources(resourceType); + HashSet processedResources = GetProcessedResources(resourceType); processedResources.UnionWith(new HashSet(resources)); } /// /// Gets the processed resources for a given type, instantiates the collection if new. /// - /// The processed resources. - /// Resource type. - private HashSet GetProcessedResources(Type resourceType) + /// + /// The processed resources. + /// + /// + /// Resource type. + /// + private HashSet GetProcessedResources(RightType resourceType) { if (!_processedResources.TryGetValue(resourceType, out HashSet processedResources)) { processedResources = new HashSet(); _processedResources[resourceType] = processedResources; } + return processedResources; } /// - /// Using the register of processed resources, determines the unique and new - /// resources with respect to previous iterations. + /// Using the register of processed resources, determines the unique and new resources with respect to previous iterations. /// - /// The in tree. - private HashSet UniqueInTree(IEnumerable resources, Type resourceType) where TResource : class, IIdentifiable + /// + /// The in tree. + /// + private HashSet UniqueInTree(IEnumerable resources, RightType resourceType) + where TResource : class, IIdentifiable { - var newResources = resources.Except(GetProcessedResources(resourceType), _comparer).Cast(); + IEnumerable newResources = resources.Except(GetProcessedResources(resourceType), _comparer).Cast(); return new HashSet(newResources); } /// - /// Gets the type from relationship attribute. If the attribute is - /// HasManyThrough, and the through type is identifiable, then the target - /// type is the through type instead of the right type, because hooks might be - /// implemented for the through resource. + /// Gets the type from relationship attribute. If the attribute is HasManyThrough, and the through type is identifiable, then the target type is the + /// through type instead of the right type, because hooks might be implemented for the through resource. /// - /// The target type for traversal - /// Relationship attribute + /// + /// The target type for traversal + /// + /// + /// Relationship attribute + /// private RightType GetRightTypeFromRelationship(RelationshipAttribute attr) { if (attr is HasManyThroughAttribute throughAttr && TypeHelper.IsOrImplementsInterface(throughAttr.ThroughType, typeof(IIdentifiable))) { return throughAttr.ThroughType; } + return attr.RightType; } - private void AddToRelationshipGroup(Dictionary> target, RelationshipProxy proxy, IEnumerable newResources) + private void AddToRelationshipGroup(Dictionary> target, RelationshipProxy proxy, + IEnumerable newResources) { if (!target.TryGetValue(proxy, out List resources)) { resources = new List(); target[proxy] = resources; } + resources.AddRange(newResources); } /// - /// Reflective helper method to create an instance of ; + /// Reflective helper method to create an instance of ; /// - private IResourceNode CreateNodeInstance(RightType nodeType, RelationshipProxy[] relationshipsToNext, IEnumerable relationshipsFromPrev) + private IResourceNode CreateNodeInstance(RightType nodeType, RelationshipProxy[] relationshipsToNext, + IEnumerable relationshipsFromPrev) { IRelationshipsFromPreviousLayer prev = CreateRelationshipsFromInstance(nodeType, relationshipsFromPrev); return (IResourceNode)TypeHelper.CreateInstanceOfOpenType(typeof(ChildNode<>), nodeType, relationshipsToNext, prev); } /// - /// Reflective helper method to create an instance of ; + /// Reflective helper method to create an instance of ; /// private IRelationshipsFromPreviousLayer CreateRelationshipsFromInstance(RightType nodeType, IEnumerable relationshipsFromPrev) { - var relationshipsFromPrevList = relationshipsFromPrev.ToList(); - var cast = TypeHelper.CopyToList(relationshipsFromPrevList, relationshipsFromPrevList.First().GetType()); + List relationshipsFromPrevList = relationshipsFromPrev.ToList(); + IList cast = TypeHelper.CopyToList(relationshipsFromPrevList, relationshipsFromPrevList.First().GetType()); return (IRelationshipsFromPreviousLayer)TypeHelper.CreateInstanceOfOpenType(typeof(RelationshipsFromPreviousLayer<>), nodeType, cast); } /// - /// Reflective helper method to create an instance of ; + /// Reflective helper method to create an instance of ; /// - private IRelationshipGroup CreateRelationshipGroupInstance(Type thisLayerType, RelationshipProxy proxy, List leftResources, List rightResources) + private IRelationshipGroup CreateRelationshipGroupInstance(RightType thisLayerType, RelationshipProxy proxy, List leftResources, + List rightResources) { - var rightResourcesHashed = TypeHelper.CreateInstanceOfOpenType(typeof(HashSet<>), thisLayerType, TypeHelper.CopyToList(rightResources, thisLayerType)); - return (IRelationshipGroup)TypeHelper.CreateInstanceOfOpenType(typeof(RelationshipGroup<>), - thisLayerType, proxy, new HashSet(leftResources), rightResourcesHashed); + object rightResourcesHashed = + TypeHelper.CreateInstanceOfOpenType(typeof(HashSet<>), thisLayerType, TypeHelper.CopyToList(rightResources, thisLayerType)); + + return (IRelationshipGroup)TypeHelper.CreateInstanceOfOpenType(typeof(RelationshipGroup<>), thisLayerType, proxy, + new HashSet(leftResources), rightResourcesHashed); } } } diff --git a/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs index 63d74d18c0..f21dcad2b4 100644 --- a/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs @@ -20,7 +20,10 @@ public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultE { if (context.Result is IStatusCodeActionResult statusCodeResult) { - context.Result = new ObjectResult(null) {StatusCode = statusCodeResult.StatusCode}; + context.Result = new ObjectResult(null) + { + StatusCode = statusCodeResult.StatusCode + }; } } } diff --git a/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs index 9fa5df8366..54f9bfaf9f 100644 --- a/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; +using JsonApiDotNetCore.Serialization.Objects; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -25,11 +26,11 @@ public Task OnExceptionAsync(ExceptionContext context) if (context.HttpContext.IsJsonApiRequest()) { - var errorDocument = _exceptionHandler.HandleException(context.Exception); + ErrorDocument errorDocument = _exceptionHandler.HandleException(context.Exception); context.Result = new ObjectResult(errorDocument) { - StatusCode = (int) errorDocument.GetErrorStatusCode() + StatusCode = (int)errorDocument.GetErrorStatusCode() }; } diff --git a/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs index 65a7ac14b1..7a72874e5b 100644 --- a/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs @@ -10,7 +10,7 @@ namespace JsonApiDotNetCore.Middleware public sealed class AsyncQueryStringActionFilter : IAsyncQueryStringActionFilter { private readonly IQueryStringReader _queryStringReader; - + public AsyncQueryStringActionFilter(IQueryStringReader queryStringReader) { ArgumentGuard.NotNull(queryStringReader, nameof(queryStringReader)); diff --git a/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs b/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs index 568cf6fed7..2006ad62de 100644 --- a/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs +++ b/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Net; using JetBrains.Annotations; @@ -38,9 +39,9 @@ public ErrorDocument HandleException(Exception exception) private void LogException(Exception exception) { - var level = GetLogLevel(exception); - var message = GetLogMessage(exception); - + LogLevel level = GetLogLevel(exception); + string message = GetLogMessage(exception); + _logger.Log(level, exception, message); } @@ -72,20 +73,17 @@ protected virtual ErrorDocument CreateErrorDocument(Exception exception) { ArgumentGuard.NotNull(exception, nameof(exception)); - var errors = exception is JsonApiException jsonApiException - ? jsonApiException.Errors - : exception is OperationCanceledException - ? new Error((HttpStatusCode) 499) - { - Title = "Request execution was canceled." - }.AsArray() - : new Error(HttpStatusCode.InternalServerError) - { - Title = "An unhandled error occurred while processing this request.", - Detail = exception.Message - }.AsArray(); - - foreach (var error in errors) + IReadOnlyList errors = exception is JsonApiException jsonApiException ? jsonApiException.Errors : + exception is OperationCanceledException ? new Error((HttpStatusCode)499) + { + Title = "Request execution was canceled." + }.AsArray() : new Error(HttpStatusCode.InternalServerError) + { + Title = "An unhandled error occurred while processing this request.", + Detail = exception.Message + }.AsArray(); + + foreach (Error error in errors) { ApplyOptions(error, exception); } diff --git a/src/JsonApiDotNetCore/Middleware/IAsyncConvertEmptyActionResultFilter.cs b/src/JsonApiDotNetCore/Middleware/IAsyncConvertEmptyActionResultFilter.cs index 9101c97c21..8e0f8b394e 100644 --- a/src/JsonApiDotNetCore/Middleware/IAsyncConvertEmptyActionResultFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/IAsyncConvertEmptyActionResultFilter.cs @@ -10,9 +10,11 @@ namespace JsonApiDotNetCore.Middleware /// return NotFound() -> return NotFound(null) /// ]]> /// - /// This ensures our formatter is invoked, where we'll build a JSON:API compliant response. - /// For details, see: https://github.com/dotnet/aspnetcore/issues/16969 + /// This ensures our formatter is invoked, where we'll build a JSON:API compliant response. For details, see: + /// https://github.com/dotnet/aspnetcore/issues/16969 /// [PublicAPI] - public interface IAsyncConvertEmptyActionResultFilter : IAsyncAlwaysRunResultFilter { } + public interface IAsyncConvertEmptyActionResultFilter : IAsyncAlwaysRunResultFilter + { + } } diff --git a/src/JsonApiDotNetCore/Middleware/IAsyncJsonApiExceptionFilter.cs b/src/JsonApiDotNetCore/Middleware/IAsyncJsonApiExceptionFilter.cs index 67b1985816..7ae6981c77 100644 --- a/src/JsonApiDotNetCore/Middleware/IAsyncJsonApiExceptionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/IAsyncJsonApiExceptionFilter.cs @@ -4,8 +4,10 @@ namespace JsonApiDotNetCore.Middleware { /// - /// Application-wide exception filter that invokes for JSON:API requests. + /// Application-wide exception filter that invokes for JSON:API requests. /// [PublicAPI] - public interface IAsyncJsonApiExceptionFilter : IAsyncExceptionFilter { } + public interface IAsyncJsonApiExceptionFilter : IAsyncExceptionFilter + { + } } diff --git a/src/JsonApiDotNetCore/Middleware/IAsyncQueryStringActionFilter.cs b/src/JsonApiDotNetCore/Middleware/IAsyncQueryStringActionFilter.cs index c2a4effcce..4ad18fcf35 100644 --- a/src/JsonApiDotNetCore/Middleware/IAsyncQueryStringActionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/IAsyncQueryStringActionFilter.cs @@ -7,5 +7,7 @@ namespace JsonApiDotNetCore.Middleware /// Application-wide entry point for processing JSON:API request query strings. /// [PublicAPI] - public interface IAsyncQueryStringActionFilter : IAsyncActionFilter { } + public interface IAsyncQueryStringActionFilter : IAsyncActionFilter + { + } } diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs index 1aeb803be6..268c0a2697 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs @@ -7,5 +7,7 @@ namespace JsonApiDotNetCore.Middleware /// Application-wide entry point for reading JSON:API request bodies. /// [PublicAPI] - public interface IJsonApiInputFormatter : IInputFormatter { } + public interface IJsonApiInputFormatter : IInputFormatter + { + } } diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs index 1afd8683f9..725accb03f 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs @@ -7,5 +7,7 @@ namespace JsonApiDotNetCore.Middleware /// Application-wide entry point for writing JSON:API response bodies. /// [PublicAPI] - public interface IJsonApiOutputFormatter : IOutputFormatter { } + public interface IJsonApiOutputFormatter : IOutputFormatter + { + } } diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs index 84e56e1941..3888fafb74 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs @@ -15,7 +15,7 @@ public interface IJsonApiRequest public EndpointKind Kind { get; } /// - /// The request URL prefix. This may be an absolute or relative path, depending on . + /// The request URL prefix. This may be an absolute or relative path, depending on . /// /// /// - /// The ID of the primary (top-level) resource for this request. - /// This would be null in "/blogs", "123" in "/blogs/123" or "/blogs/123/author". + /// The ID of the primary (top-level) resource for this request. This would be null in "/blogs", "123" in "/blogs/123" or "/blogs/123/author". /// string PrimaryId { get; } /// - /// The primary (top-level) resource for this request. - /// This would be "blogs" in "/blogs", "/blogs/123" or "/blogs/123/author". + /// The primary (top-level) resource for this request. This would be "blogs" in "/blogs", "/blogs/123" or "/blogs/123/author". /// ResourceContext PrimaryResource { get; } /// - /// The secondary (nested) resource for this request. - /// This would be null in "/blogs", "/blogs/123" and "/blogs/123/unknownResource" or - /// "people" in "/blogs/123/author" and "/blogs/123/relationships/author". + /// The secondary (nested) resource for this request. This would be null in "/blogs", "/blogs/123" and "/blogs/123/unknownResource" or "people" in + /// "/blogs/123/author" and "/blogs/123/relationships/author". /// ResourceContext SecondaryResource { get; } /// - /// The relationship for this nested request. - /// This would be null in "/blogs", "/blogs/123" and "/blogs/123/unknownResource" or - /// "author" in "/blogs/123/author" and "/blogs/123/relationships/author". + /// The relationship for this nested request. This would be null in "/blogs", "/blogs/123" and "/blogs/123/unknownResource" or "author" in + /// "/blogs/123/author" and "/blogs/123/relationships/author". /// RelationshipAttribute Relationship { get; } diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiRoutingConvention.cs index e1c941414c..d5db22efa6 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiRoutingConvention.cs @@ -4,9 +4,10 @@ namespace JsonApiDotNetCore.Middleware { /// - /// Service for specifying which routing convention to use. This can be overridden to customize - /// the relation between controllers and mapped routes. + /// Service for specifying which routing convention to use. This can be overridden to customize the relation between controllers and mapped routes. /// [PublicAPI] - public interface IJsonApiRoutingConvention : IApplicationModelConvention, IControllerResourceMapping { } + public interface IJsonApiRoutingConvention : IApplicationModelConvention, IControllerResourceMapping + { + } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 46aded0e1f..27f9a2a46d 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; @@ -20,7 +21,7 @@ namespace JsonApiDotNetCore.Middleware { /// - /// Intercepts HTTP requests to populate injected instance for JSON:API requests. + /// Intercepts HTTP requests to populate injected instance for JSON:API requests. /// [PublicAPI] public sealed class JsonApiMiddleware @@ -35,11 +36,8 @@ public JsonApiMiddleware(RequestDelegate next) _next = next; } - public async Task InvokeAsync(HttpContext httpContext, - IControllerResourceMapping controllerResourceMapping, - IJsonApiOptions options, - IJsonApiRequest request, - IResourceContextProvider resourceContextProvider) + public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMapping controllerResourceMapping, IJsonApiOptions options, + IJsonApiRequest request, IResourceContextProvider resourceContextProvider) { ArgumentGuard.NotNull(httpContext, nameof(httpContext)); ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping)); @@ -47,12 +45,13 @@ public async Task InvokeAsync(HttpContext httpContext, ArgumentGuard.NotNull(request, nameof(request)); ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); - var routeValues = httpContext.GetRouteData().Values; + RouteValueDictionary routeValues = httpContext.GetRouteData().Values; + + ResourceContext primaryResourceContext = CreatePrimaryResourceContext(routeValues, controllerResourceMapping, resourceContextProvider); - var primaryResourceContext = CreatePrimaryResourceContext(routeValues, controllerResourceMapping, resourceContextProvider); if (primaryResourceContext != null) { - if (!await ValidateContentTypeHeaderAsync(HeaderConstants.MediaType, httpContext, options.SerializerSettings) || + if (!await ValidateContentTypeHeaderAsync(HeaderConstants.MediaType, httpContext, options.SerializerSettings) || !await ValidateAcceptHeaderAsync(MediaType, httpContext, options.SerializerSettings)) { return; @@ -64,7 +63,7 @@ public async Task InvokeAsync(HttpContext httpContext, } else if (IsOperationsRequest(routeValues)) { - if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializerSettings) || + if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializerSettings) || !await ValidateAcceptHeaderAsync(AtomicOperationsMediaType, httpContext, options.SerializerSettings)) { return; @@ -78,13 +77,15 @@ public async Task InvokeAsync(HttpContext httpContext, await _next(httpContext); } - private static ResourceContext CreatePrimaryResourceContext(RouteValueDictionary routeValues, - IControllerResourceMapping controllerResourceMapping, IResourceContextProvider resourceContextProvider) + private static ResourceContext CreatePrimaryResourceContext(RouteValueDictionary routeValues, IControllerResourceMapping controllerResourceMapping, + IResourceContextProvider resourceContextProvider) { - var controllerName = (string) routeValues["controller"]; + string controllerName = (string)routeValues["controller"]; + if (controllerName != null) { - var resourceType = controllerResourceMapping.GetResourceTypeForController(controllerName); + Type resourceType = controllerResourceMapping.GetResourceTypeForController(controllerName); + if (resourceType != null) { return resourceContextProvider.GetResourceContext(resourceType); @@ -94,26 +95,30 @@ private static ResourceContext CreatePrimaryResourceContext(RouteValueDictionary return null; } - private static async Task ValidateContentTypeHeaderAsync(string allowedContentType, HttpContext httpContext, JsonSerializerSettings serializerSettings) + private static async Task ValidateContentTypeHeaderAsync(string allowedContentType, HttpContext httpContext, + JsonSerializerSettings serializerSettings) { - var contentType = httpContext.Request.ContentType; + string contentType = httpContext.Request.ContentType; + if (contentType != null && contentType != allowedContentType) { await FlushResponseAsync(httpContext.Response, serializerSettings, new Error(HttpStatusCode.UnsupportedMediaType) { Title = "The specified Content-Type header value is not supported.", - Detail = $"Please specify '{allowedContentType}' instead of '{contentType}' " + - "for the Content-Type header value." + Detail = $"Please specify '{allowedContentType}' instead of '{contentType}' " + "for the Content-Type header value." }); + return false; } return true; } - private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue allowedMediaTypeValue, HttpContext httpContext, JsonSerializerSettings serializerSettings) + private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue allowedMediaTypeValue, HttpContext httpContext, + JsonSerializerSettings serializerSettings) { StringValues acceptHeaders = httpContext.Request.Headers["Accept"]; + if (!acceptHeaders.Any()) { return true; @@ -121,9 +126,9 @@ private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue a bool seenCompatibleMediaType = false; - foreach (var acceptHeader in acceptHeaders) + foreach (string acceptHeader in acceptHeaders) { - if (MediaTypeWithQualityHeaderValue.TryParse(acceptHeader, out var headerValue)) + if (MediaTypeWithQualityHeaderValue.TryParse(acceptHeader, out MediaTypeWithQualityHeaderValue headerValue)) { headerValue.Quality = null; @@ -148,6 +153,7 @@ private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue a Title = "The specified Accept header value does not contain any supported media types.", Detail = $"Please include '{allowedMediaTypeValue}' in the Accept header values." }); + return false; } @@ -157,9 +163,9 @@ private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue a private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonSerializerSettings serializerSettings, Error error) { httpResponse.ContentType = HeaderConstants.MediaType; - httpResponse.StatusCode = (int) error.StatusCode; + httpResponse.StatusCode = (int)error.StatusCode; - JsonSerializer serializer = JsonSerializer.CreateDefault(serializerSettings); + var serializer = JsonSerializer.CreateDefault(serializerSettings); serializer.ApplyErrorSettings(); // https://github.com/JamesNK/Newtonsoft.Json/issues/1193 @@ -178,9 +184,8 @@ private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonSeri await httpResponse.Body.FlushAsync(); } - private static void SetupResourceRequest(JsonApiRequest request, ResourceContext primaryResourceContext, - RouteValueDictionary routeValues, IJsonApiOptions options, IResourceContextProvider resourceContextProvider, - HttpRequest httpRequest) + private static void SetupResourceRequest(JsonApiRequest request, ResourceContext primaryResourceContext, RouteValueDictionary routeValues, + IJsonApiOptions options, IResourceContextProvider resourceContextProvider, HttpRequest httpRequest) { request.IsReadOnly = httpRequest.Method == HttpMethod.Get.Method; request.Kind = EndpointKind.Primary; @@ -188,14 +193,14 @@ private static void SetupResourceRequest(JsonApiRequest request, ResourceContext request.PrimaryId = GetPrimaryRequestId(routeValues); request.BasePath = GetBasePath(primaryResourceContext.PublicName, options, httpRequest); - var relationshipName = GetRelationshipNameForSecondaryRequest(routeValues); + string relationshipName = GetRelationshipNameForSecondaryRequest(routeValues); + if (relationshipName != null) { request.Kind = IsRouteForRelationship(routeValues) ? EndpointKind.Relationship : EndpointKind.Secondary; - var requestRelationship = - primaryResourceContext.Relationships.SingleOrDefault(relationship => - relationship.PublicName == relationshipName); + RelationshipAttribute requestRelationship = + primaryResourceContext.Relationships.SingleOrDefault(relationship => relationship.PublicName == relationshipName); if (requestRelationship != null) { @@ -204,13 +209,13 @@ private static void SetupResourceRequest(JsonApiRequest request, ResourceContext } } - var isGetAll = request.PrimaryId == null && request.IsReadOnly; + bool isGetAll = request.PrimaryId == null && request.IsReadOnly; request.IsCollection = isGetAll || request.Relationship is HasManyAttribute; } private static string GetPrimaryRequestId(RouteValueDictionary routeValues) { - return routeValues.TryGetValue("id", out var id) ? (string) id : null; + return routeValues.TryGetValue("id", out object id) ? (string)id : null; } private static string GetBasePath(string resourceName, IJsonApiOptions options, HttpRequest httpRequest) @@ -230,6 +235,7 @@ private static string GetBasePath(string resourceName, IJsonApiOptions options, } string customRoute = GetCustomRoute(resourceName, options.Namespace, httpRequest.HttpContext); + if (!string.IsNullOrEmpty(customRoute)) { builder.Append('/'); @@ -248,14 +254,15 @@ private static string GetCustomRoute(string resourceName, string apiNamespace, H { if (resourceName != null) { - var endpoint = httpContext.GetEndpoint(); + Endpoint endpoint = httpContext.GetEndpoint(); var routeAttribute = endpoint.Metadata.GetMetadata(); + if (routeAttribute != null) { - var trimmedComponents = httpContext.Request.Path.Value.Trim('/').Split('/').ToList(); - var resourceNameIndex = trimmedComponents.FindIndex(c => c == resourceName); - var newComponents = trimmedComponents.Take(resourceNameIndex).ToArray(); - var customRoute = string.Join('/', newComponents); + List trimmedComponents = httpContext.Request.Path.Value.Trim('/').Split('/').ToList(); + int resourceNameIndex = trimmedComponents.FindIndex(c => c == resourceName); + string[] newComponents = trimmedComponents.Take(resourceNameIndex).ToArray(); + string customRoute = string.Join('/', newComponents); return customRoute == apiNamespace ? null : customRoute; } } @@ -265,18 +272,18 @@ private static string GetCustomRoute(string resourceName, string apiNamespace, H private static string GetRelationshipNameForSecondaryRequest(RouteValueDictionary routeValues) { - return routeValues.TryGetValue("relationshipName", out object routeValue) ? (string) routeValue : null; + return routeValues.TryGetValue("relationshipName", out object routeValue) ? (string)routeValue : null; } private static bool IsRouteForRelationship(RouteValueDictionary routeValues) { - var actionName = (string)routeValues["action"]; + string actionName = (string)routeValues["action"]; return actionName.EndsWith("Relationship", StringComparison.Ordinal); } private static bool IsOperationsRequest(RouteValueDictionary routeValues) { - var actionName = (string)routeValues["action"]; + string actionName = (string)routeValues["action"]; return actionName == "PostOperations"; } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs index ce2704aadf..53c86330b2 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs @@ -11,25 +11,25 @@ public sealed class JsonApiRequest : IJsonApiRequest { /// public EndpointKind Kind { get; set; } - + /// public string BasePath { get; set; } - + /// public string PrimaryId { get; set; } - + /// public ResourceContext PrimaryResource { get; set; } - + /// public ResourceContext SecondaryResource { get; set; } - + /// public RelationshipAttribute Relationship { get; set; } - + /// public bool IsCollection { get; set; } - + /// public bool IsReadOnly { get; set; } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs index 73468d4ef3..045ecb01ab 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs @@ -14,19 +14,18 @@ namespace JsonApiDotNetCore.Middleware { /// - /// The default routing convention registers the name of the resource as the route - /// using the serializer naming convention. The default for this is - /// a camel case formatter. If the controller directly inherits from and there is no - /// resource directly associated, it uses the name of the controller instead of the name of the type. + /// The default routing convention registers the name of the resource as the route using the serializer naming convention. The default for this is a + /// camel case formatter. If the controller directly inherits from and there is no resource directly associated, it + /// uses the name of the controller instead of the name of the type. /// /// { } // => /someResources/relationship/relatedResource - /// + /// /// public class RandomNameController : JsonApiController { } // => /someResources/relationship/relatedResource - /// + /// /// // when using kebab-case naming convention: /// public class SomeResourceController : JsonApiController { } // => /some-resources/relationship/related-resource - /// + /// /// public class SomeVeryCustomController : CoreJsonApiController { } // => /someVeryCustoms/relationship/relatedResource /// ]]> [PublicAPI] @@ -51,11 +50,11 @@ public Type GetResourceTypeForController(string controllerName) { ArgumentGuard.NotNull(controllerName, nameof(controllerName)); - if (_registeredResources.TryGetValue(controllerName, out var resourceContext)) + if (_registeredResources.TryGetValue(controllerName, out ResourceContext resourceContext)) { return resourceContext.ResourceType; } - + return null; } @@ -64,16 +63,17 @@ public void Apply(ApplicationModel application) { ArgumentGuard.NotNull(application, nameof(application)); - foreach (var controller in application.Controllers) + foreach (ControllerModel controller in application.Controllers) { bool isOperationsController = IsOperationsController(controller.ControllerType); + if (!isOperationsController) { - var resourceType = ExtractResourceTypeFromController(controller.ControllerType); + Type resourceType = ExtractResourceTypeFromController(controller.ControllerType); if (resourceType != null) { - var resourceContext = _resourceContextProvider.GetResourceContext(resourceType); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType); if (resourceContext != null) { @@ -87,14 +87,17 @@ public void Apply(ApplicationModel application) continue; } - var template = TemplateFromResource(controller) ?? TemplateFromController(controller); + string template = TemplateFromResource(controller) ?? TemplateFromController(controller); + if (template == null) { - throw new InvalidConfigurationException( - $"Controllers with overlapping route templates detected: {controller.ControllerType.FullName}"); + throw new InvalidConfigurationException($"Controllers with overlapping route templates detected: {controller.ControllerType.FullName}"); } - controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel { Template = template }; + controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel + { + Template = template + }; } } @@ -109,9 +112,10 @@ private bool IsRoutingConventionEnabled(ControllerModel controller) /// private string TemplateFromResource(ControllerModel model) { - if (_registeredResources.TryGetValue(model.ControllerName, out var resourceContext)) + if (_registeredResources.TryGetValue(model.ControllerName, out ResourceContext resourceContext)) { - var template = $"{_options.Namespace}/{resourceContext.PublicName}"; + string template = $"{_options.Namespace}/{resourceContext.PublicName}"; + if (_registeredTemplates.Add(template)) { return template; @@ -127,13 +131,13 @@ private string TemplateFromResource(ControllerModel model) private string TemplateFromController(ControllerModel model) { string controllerName = _options.SerializerNamingStrategy.GetPropertyName(model.ControllerName, false); - var template = $"{_options.Namespace}/{controllerName}"; + string template = $"{_options.Namespace}/{controllerName}"; if (_registeredTemplates.Add(template)) { return template; } - + return null; } @@ -142,19 +146,19 @@ private string TemplateFromController(ControllerModel model) /// private Type ExtractResourceTypeFromController(Type type) { - var aspNetControllerType = typeof(ControllerBase); - var coreControllerType = typeof(CoreJsonApiController); - var baseControllerType = typeof(BaseJsonApiController<,>); - var currentType = type; + Type aspNetControllerType = typeof(ControllerBase); + Type coreControllerType = typeof(CoreJsonApiController); + Type baseControllerType = typeof(BaseJsonApiController<,>); + Type currentType = type; + while (!currentType.IsGenericType || currentType.GetGenericTypeDefinition() != baseControllerType) { - var nextBaseType = currentType.BaseType; + Type? nextBaseType = currentType.BaseType; - if ((nextBaseType == aspNetControllerType || nextBaseType == coreControllerType) && - currentType.IsGenericType) + if ((nextBaseType == aspNetControllerType || nextBaseType == coreControllerType) && currentType.IsGenericType) { - var resourceType = currentType.GetGenericArguments() - .FirstOrDefault(t => TypeHelper.IsOrImplementsInterface(t, typeof(IIdentifiable))); + Type resourceType = currentType.GetGenericArguments().FirstOrDefault(t => TypeHelper.IsOrImplementsInterface(t, typeof(IIdentifiable))); + if (resourceType != null) { return resourceType; @@ -162,6 +166,7 @@ private Type ExtractResourceTypeFromController(Type type) } currentType = nextBaseType; + if (nextBaseType == null) { break; @@ -173,7 +178,7 @@ private Type ExtractResourceTypeFromController(Type type) private static bool IsOperationsController(Type type) { - var baseControllerType = typeof(BaseJsonApiOperationsController); + Type baseControllerType = typeof(BaseJsonApiOperationsController); return baseControllerType.IsAssignableFrom(type); } } diff --git a/src/JsonApiDotNetCore/Middleware/TraceLogWriter.cs b/src/JsonApiDotNetCore/Middleware/TraceLogWriter.cs index 7ba6490547..a3a5452cf1 100644 --- a/src/JsonApiDotNetCore/Middleware/TraceLogWriter.cs +++ b/src/JsonApiDotNetCore/Middleware/TraceLogWriter.cs @@ -54,7 +54,8 @@ private static void WriteProperties(StringBuilder builder, object propertyContai if (propertyContainer != null) { bool isFirstMember = true; - foreach (var property in propertyContainer.GetType().GetProperties()) + + foreach (PropertyInfo property in propertyContainer.GetType().GetProperties()) { if (isFirstMember) { @@ -75,7 +76,8 @@ private static void WriteProperty(StringBuilder builder, PropertyInfo property, builder.Append(property.Name); builder.Append(": "); - var value = property.GetValue(instance); + object? value = property.GetValue(instance); + if (value == null) { builder.Append("null"); @@ -100,7 +102,7 @@ private static void WriteObject(StringBuilder builder, object value) } else { - var text = SerializeObject(value); + string text = SerializeObject(value); builder.Append(text); } } @@ -109,7 +111,8 @@ private static bool HasToStringOverload(Type type) { if (type != null) { - var toStringMethod = type.GetMethod("ToString", Array.Empty()); + MethodInfo? toStringMethod = type.GetMethod("ToString", Array.Empty()); + if (toStringMethod != null && toStringMethod.DeclaringType != typeof(object)) { return true; diff --git a/src/JsonApiDotNetCore/ObjectExtensions.cs b/src/JsonApiDotNetCore/ObjectExtensions.cs index 7dfa76e02c..7df7cf3b02 100644 --- a/src/JsonApiDotNetCore/ObjectExtensions.cs +++ b/src/JsonApiDotNetCore/ObjectExtensions.cs @@ -11,12 +11,18 @@ public static IEnumerable AsEnumerable(this T element) public static T[] AsArray(this T element) { - return new[] {element}; + return new[] + { + element + }; } public static List AsList(this T element) { - return new List {element}; + return new List + { + element + }; } } } diff --git a/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs b/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs index 5d848911dd..6bd2e5a870 100644 --- a/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs +++ b/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -[assembly:InternalsVisibleTo("Benchmarks")] -[assembly:InternalsVisibleTo("JsonApiDotNetCoreExampleTests")] -[assembly:InternalsVisibleTo("UnitTests")] -[assembly:InternalsVisibleTo("DiscoveryTests")] +[assembly: InternalsVisibleTo("Benchmarks")] +[assembly: InternalsVisibleTo("JsonApiDotNetCoreExampleTests")] +[assembly: InternalsVisibleTo("UnitTests")] +[assembly: InternalsVisibleTo("DiscoveryTests")] diff --git a/src/JsonApiDotNetCore/Properties/launchSettings.json b/src/JsonApiDotNetCore/Properties/launchSettings.json index d0f3094262..233fb4a18e 100644 --- a/src/JsonApiDotNetCore/Properties/launchSettings.json +++ b/src/JsonApiDotNetCore/Properties/launchSettings.json @@ -24,4 +24,4 @@ "applicationUrl": "http://localhost:63522/" } } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs b/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs index 56f56468c1..5475baed85 100644 --- a/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs +++ b/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.Queries { /// - /// Represents an expression coming from query string. The scope determines at which depth in the to apply its expression. + /// Represents an expression coming from query string. The scope determines at which depth in the to apply its expression. /// [PublicAPI] public class ExpressionInScope diff --git a/src/JsonApiDotNetCore/Queries/Expressions/CollectionNotEmptyExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/CollectionNotEmptyExpression.cs index 47bdde4bda..1d398192f8 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/CollectionNotEmptyExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/CollectionNotEmptyExpression.cs @@ -40,7 +40,7 @@ public override bool Equals(object obj) return false; } - var other = (CollectionNotEmptyExpression) obj; + var other = (CollectionNotEmptyExpression)obj; return TargetCollection.Equals(other.TargetCollection); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs index 16063c9c1f..ab06961738 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs @@ -46,7 +46,7 @@ public override bool Equals(object obj) return false; } - var other = (ComparisonExpression) obj; + var other = (ComparisonExpression)obj; return Operator == other.Operator && Left.Equals(other.Left) && Right.Equals(other.Right); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs index 26459b943f..57163936f7 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs @@ -40,7 +40,7 @@ public override bool Equals(object obj) return false; } - var other = (CountExpression) obj; + var other = (CountExpression)obj; return TargetCollection.Equals(other.TargetCollection); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/EqualsAnyOfExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/EqualsAnyOfExpression.cs index 69076e2d39..5e723bb5bf 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/EqualsAnyOfExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/EqualsAnyOfExpression.cs @@ -16,8 +16,7 @@ public class EqualsAnyOfExpression : FilterExpression public ResourceFieldChainExpression TargetAttribute { get; } public IReadOnlyCollection Constants { get; } - public EqualsAnyOfExpression(ResourceFieldChainExpression targetAttribute, - IReadOnlyCollection constants) + public EqualsAnyOfExpression(ResourceFieldChainExpression targetAttribute, IReadOnlyCollection constants) { ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute)); ArgumentGuard.NotNull(constants, nameof(constants)); @@ -62,7 +61,7 @@ public override bool Equals(object obj) return false; } - var other = (EqualsAnyOfExpression) obj; + var other = (EqualsAnyOfExpression)obj; return TargetAttribute.Equals(other.TargetAttribute) && Constants.SequenceEqual(other.Constants); } @@ -72,7 +71,7 @@ public override int GetHashCode() var hashCode = new HashCode(); hashCode.Add(TargetAttribute); - foreach (var constant in Constants) + foreach (LiteralConstantExpression constant in Constants) { hashCode.Add(constant); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs index 6814166c4f..57357fa6ec 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs @@ -5,8 +5,7 @@ namespace JsonApiDotNetCore.Queries.Expressions { /// - /// Converts includes between tree and chain formats. - /// Exists for backwards compatibility, subject to be removed in the future. + /// Converts includes between tree and chain formats. Exists for backwards compatibility, subject to be removed in the future. /// internal static class IncludeChainConverter { @@ -14,8 +13,7 @@ internal static class IncludeChainConverter /// Converts a tree of inclusions into a set of relationship chains. /// /// - /// Input tree: - /// - /// Output chains: + /// ]]> Output chains: /// Blog, /// Article -> Revisions -> Author @@ -35,7 +32,7 @@ public static IReadOnlyCollection GetRelationshipC { ArgumentGuard.NotNull(include, nameof(include)); - IncludeToChainsConverter converter = new IncludeToChainsConverter(); + var converter = new IncludeToChainsConverter(); converter.Visit(include, null); return converter.Chains; @@ -45,12 +42,10 @@ public static IReadOnlyCollection GetRelationshipC /// Converts a set of relationship chains into a tree of inclusions. /// /// - /// Input chains: - /// Blog, /// Article -> Revisions -> Author - /// ]]> - /// Output tree: + /// ]]> Output tree: /// elements = ConvertChainsToElements(chains); return elements.Any() ? new IncludeExpression(elements) : IncludeExpression.Empty; } @@ -86,7 +81,7 @@ private static void ConvertChainToElement(ResourceFieldChainExpression chain, Mu { MutableIncludeNode currentNode = rootNode; - foreach (var relationship in chain.Fields.OfType()) + foreach (RelationshipAttribute relationship in chain.Fields.OfType()) { if (!currentNode.Children.ContainsKey(relationship)) { @@ -156,7 +151,7 @@ public MutableIncludeNode(RelationshipAttribute relationship) public IncludeElementExpression ToExpression() { - var elementChildren = Children.Values.Select(child => child.ToExpression()).ToArray(); + IncludeElementExpression[] elementChildren = Children.Values.Select(child => child.ToExpression()).ToArray(); return new IncludeElementExpression(_relationship, elementChildren); } } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs index 2b9be3d95d..f279f4ede4 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCore.Queries.Expressions { /// - /// Represents an element in . + /// Represents an element in . /// [PublicAPI] public class IncludeElementExpression : QueryExpression @@ -46,7 +46,7 @@ public override string ToString() builder.Append(string.Join(",", Children.Select(child => child.ToString()))); builder.Append('}'); } - + return builder.ToString(); } @@ -62,7 +62,7 @@ public override bool Equals(object obj) return false; } - var other = (IncludeElementExpression) obj; + var other = (IncludeElementExpression)obj; return Relationship.Equals(other.Relationship) == Children.SequenceEqual(other.Children); } @@ -72,7 +72,7 @@ public override int GetHashCode() var hashCode = new HashCode(); hashCode.Add(Relationship); - foreach (var child in Children) + foreach (IncludeElementExpression child in Children) { hashCode.Add(child); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs index a96065db56..538d698193 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs @@ -11,14 +11,8 @@ namespace JsonApiDotNetCore.Queries.Expressions [PublicAPI] public class IncludeExpression : QueryExpression { - public IReadOnlyCollection Elements { get; } - public static readonly IncludeExpression Empty = new IncludeExpression(); - - private IncludeExpression() - { - Elements = Array.Empty(); - } + public IReadOnlyCollection Elements { get; } public IncludeExpression(IReadOnlyCollection elements) { @@ -32,6 +26,11 @@ public IncludeExpression(IReadOnlyCollection elements) Elements = elements; } + private IncludeExpression() + { + Elements = Array.Empty(); + } + public override TResult Accept(QueryExpressionVisitor visitor, TArgument argument) { return visitor.VisitInclude(this, argument); @@ -39,7 +38,7 @@ public override TResult Accept(QueryExpressionVisitor chains = IncludeChainConverter.GetRelationshipChains(this); return string.Join(",", chains.Select(child => child.ToString())); } @@ -55,7 +54,7 @@ public override bool Equals(object obj) return false; } - var other = (IncludeExpression) obj; + var other = (IncludeExpression)obj; return Elements.SequenceEqual(other.Elements); } @@ -64,7 +63,7 @@ public override int GetHashCode() { var hashCode = new HashCode(); - foreach (var element in Elements) + foreach (IncludeElementExpression element in Elements) { hashCode.Add(element); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs index e59299ba3d..019301e40c 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs @@ -40,7 +40,7 @@ public override bool Equals(object obj) return false; } - var other = (LiteralConstantExpression) obj; + var other = (LiteralConstantExpression)obj; return Value == other.Value; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs index ef093e204f..ee368235ce 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs @@ -58,7 +58,7 @@ public override bool Equals(object obj) return false; } - var other = (LogicalExpression) obj; + var other = (LogicalExpression)obj; return Operator == other.Operator && Terms.SequenceEqual(other.Terms); } @@ -68,7 +68,7 @@ public override int GetHashCode() var hashCode = new HashCode(); hashCode.Add(Operator); - foreach (var term in Terms) + foreach (QueryExpression term in Terms) { hashCode.Add(term); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs index 6c026d0258..0df64dbb44 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs @@ -15,8 +15,7 @@ public class MatchTextExpression : FilterExpression public LiteralConstantExpression TextValue { get; } public TextMatchKind MatchKind { get; } - public MatchTextExpression(ResourceFieldChainExpression targetAttribute, LiteralConstantExpression textValue, - TextMatchKind matchKind) + public MatchTextExpression(ResourceFieldChainExpression targetAttribute, LiteralConstantExpression textValue, TextMatchKind matchKind) { ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute)); ArgumentGuard.NotNull(textValue, nameof(textValue)); @@ -55,7 +54,7 @@ public override bool Equals(object obj) return false; } - var other = (MatchTextExpression) obj; + var other = (MatchTextExpression)obj; return TargetAttribute.Equals(other.TargetAttribute) && TextValue.Equals(other.TextValue) && MatchKind == other.MatchKind; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs index 23a9637045..da790bcfbe 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs @@ -40,7 +40,7 @@ public override bool Equals(object obj) return false; } - var other = (NotExpression) obj; + var other = (NotExpression)obj; return Child.Equals(other.Child); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationElementQueryStringValueExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationElementQueryStringValueExpression.cs index 29dbf72d99..d62ca621e0 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationElementQueryStringValueExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationElementQueryStringValueExpression.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Queries.Expressions { /// - /// Represents an element in . + /// Represents an element in . /// [PublicAPI] public class PaginationElementQueryStringValueExpression : QueryExpression @@ -40,7 +40,7 @@ public override bool Equals(object obj) return false; } - var other = (PaginationElementQueryStringValueExpression) obj; + var other = (PaginationElementQueryStringValueExpression)obj; return Equals(Scope, other.Scope) && Value == other.Value; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs index 593e2ca1b8..3d8f2c5870 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.Queries.Expressions { /// - /// Represents a pagination, produced from . + /// Represents a pagination, produced from . /// [PublicAPI] public class PaginationExpression : QueryExpression @@ -43,7 +43,7 @@ public override bool Equals(object obj) return false; } - var other = (PaginationExpression) obj; + var other = (PaginationExpression)obj; return PageNumber.Equals(other.PageNumber) && Equals(PageSize, other.PageSize); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs index 5e054d000e..7046c7b1ad 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs @@ -13,8 +13,7 @@ public class PaginationQueryStringValueExpression : QueryExpression { public IReadOnlyCollection Elements { get; } - public PaginationQueryStringValueExpression( - IReadOnlyCollection elements) + public PaginationQueryStringValueExpression(IReadOnlyCollection elements) { ArgumentGuard.NotNull(elements, nameof(elements)); @@ -26,8 +25,7 @@ public PaginationQueryStringValueExpression( Elements = elements; } - public override TResult Accept(QueryExpressionVisitor visitor, - TArgument argument) + public override TResult Accept(QueryExpressionVisitor visitor, TArgument argument) { return visitor.PaginationQueryStringValue(this, argument); } @@ -49,7 +47,7 @@ public override bool Equals(object obj) return false; } - var other = (PaginationQueryStringValueExpression) obj; + var other = (PaginationQueryStringValueExpression)obj; return Elements.SequenceEqual(other.Elements); } @@ -58,7 +56,7 @@ public override int GetHashCode() { var hashCode = new HashCode(); - foreach (var element in Elements) + foreach (PaginationElementQueryStringValueExpression element in Elements) { hashCode.Add(element); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpression.cs index 6fc8467a93..44d5311b19 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpression.cs @@ -3,8 +3,8 @@ namespace JsonApiDotNetCore.Queries.Expressions { /// - /// Represents the base data structure for immutable types that query string parameters are converted into. - /// This intermediate structure is later transformed into system trees that are handled by Entity Framework Core. + /// Represents the base data structure for immutable types that query string parameters are converted into. This intermediate structure is later + /// transformed into system trees that are handled by Entity Framework Core. /// public abstract class QueryExpression { diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs index 6ff2e777aa..e3107db41a 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs @@ -28,8 +28,8 @@ public override QueryExpression VisitComparison(ComparisonExpression expression, return null; } - var newLeft = Visit(expression.Left, argument); - var newRight = Visit(expression.Right, argument); + QueryExpression newLeft = Visit(expression.Left, argument); + QueryExpression newRight = Visit(expression.Right, argument); var newExpression = new ComparisonExpression(expression.Operator, newLeft, newRight); return newExpression.Equals(expression) ? expression : newExpression; @@ -54,7 +54,7 @@ public override QueryExpression VisitLogical(LogicalExpression expression, TArgu { if (expression != null) { - var newTerms = VisitSequence(expression.Terms, argument); + IReadOnlyCollection newTerms = VisitSequence(expression.Terms, argument); if (newTerms.Count == 1) { @@ -75,7 +75,7 @@ public override QueryExpression VisitNot(NotExpression expression, TArgument arg { if (expression != null) { - var newChild = Visit(expression.Child, argument); + QueryExpression newChild = Visit(expression.Child, argument); if (newChild != null) { @@ -135,7 +135,7 @@ public override QueryExpression VisitSort(SortExpression expression, TArgument a { if (expression != null) { - var newElements = VisitSequence(expression.Elements, argument); + IReadOnlyCollection newElements = VisitSequence(expression.Elements, argument); if (newElements.Count != 0) { @@ -185,7 +185,7 @@ public override QueryExpression VisitEqualsAnyOf(EqualsAnyOfExpression expressio if (expression != null) { var newTargetAttribute = Visit(expression.TargetAttribute, argument) as ResourceFieldChainExpression; - var newConstants = VisitSequence(expression.Constants, argument); + IReadOnlyCollection newConstants = VisitSequence(expression.Constants, argument); var newExpression = new EqualsAnyOfExpression(newTargetAttribute, newConstants); return newExpression.Equals(expression) ? expression : newExpression; @@ -200,7 +200,7 @@ public override QueryExpression VisitSparseFieldTable(SparseFieldTableExpression { var newTable = new Dictionary(); - foreach (var (resourceContext, sparseFieldSet) in expression.Table) + foreach ((ResourceContext resourceContext, SparseFieldSetExpression sparseFieldSet) in expression.Table) { if (Visit(sparseFieldSet, argument) is SparseFieldSetExpression newSparseFieldSet) { @@ -229,9 +229,7 @@ public override QueryExpression VisitQueryStringParameterScope(QueryStringParame { var newParameterName = Visit(expression.ParameterName, argument) as LiteralConstantExpression; - var newScope = expression.Scope != null - ? Visit(expression.Scope, argument) as ResourceFieldChainExpression - : null; + ResourceFieldChainExpression newScope = expression.Scope != null ? Visit(expression.Scope, argument) as ResourceFieldChainExpression : null; var newExpression = new QueryStringParameterScopeExpression(newParameterName, newScope); return newExpression.Equals(expression) ? expression : newExpression; @@ -244,7 +242,7 @@ public override QueryExpression PaginationQueryStringValue(PaginationQueryString { if (expression != null) { - var newElements = VisitSequence(expression.Elements, argument); + IReadOnlyCollection newElements = VisitSequence(expression.Elements, argument); var newExpression = new PaginationQueryStringValueExpression(newElements); return newExpression.Equals(expression) ? expression : newExpression; @@ -253,14 +251,11 @@ public override QueryExpression PaginationQueryStringValue(PaginationQueryString return null; } - public override QueryExpression PaginationElementQueryStringValue(PaginationElementQueryStringValueExpression expression, - TArgument argument) + public override QueryExpression PaginationElementQueryStringValue(PaginationElementQueryStringValueExpression expression, TArgument argument) { if (expression != null) { - var newScope = expression.Scope != null - ? Visit(expression.Scope, argument) as ResourceFieldChainExpression - : null; + ResourceFieldChainExpression newScope = expression.Scope != null ? Visit(expression.Scope, argument) as ResourceFieldChainExpression : null; var newExpression = new PaginationElementQueryStringValueExpression(newScope, expression.Value); return newExpression.Equals(expression) ? expression : newExpression; @@ -273,7 +268,7 @@ public override QueryExpression VisitInclude(IncludeExpression expression, TArgu { if (expression != null) { - var newElements = VisitSequence(expression.Elements, argument); + IReadOnlyCollection newElements = VisitSequence(expression.Elements, argument); if (newElements.Count == 0) { @@ -291,7 +286,7 @@ public override QueryExpression VisitIncludeElement(IncludeElementExpression exp { if (expression != null) { - var newElements = VisitSequence(expression.Children, argument); + IReadOnlyCollection newElements = VisitSequence(expression.Children, argument); var newExpression = new IncludeElementExpression(expression.Relationship, newElements); return newExpression.Equals(expression) ? expression : newExpression; @@ -305,8 +300,7 @@ public override QueryExpression VisitQueryableHandler(QueryableHandlerExpression return expression; } - protected virtual IReadOnlyCollection VisitSequence(IEnumerable elements, - TArgument argument) + protected virtual IReadOnlyCollection VisitSequence(IEnumerable elements, TArgument argument) where TExpression : QueryExpression { var newElements = new List(); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs index 6b7da6210d..f16a7b0424 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs @@ -3,7 +3,7 @@ namespace JsonApiDotNetCore.Queries.Expressions { /// - /// Implements the visitor design pattern that enables traversing a tree. + /// Implements the visitor design pattern that enables traversing a tree. /// [PublicAPI] public abstract class QueryExpressionVisitor diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs index ca514c6638..7a6071e450 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs @@ -42,7 +42,7 @@ public override bool Equals(object obj) return false; } - var other = (QueryStringParameterScopeExpression) obj; + var other = (QueryStringParameterScopeExpression)obj; return ParameterName.Equals(other.ParameterName) && Equals(Scope, other.Scope); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs index dd47117388..9ebf5f0c2b 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.Queries.Expressions { /// - /// Holds an expression, used for custom query string handlers from s. + /// Holds an expression, used for custom query string handlers from s. /// [PublicAPI] public class QueryableHandlerExpression : QueryExpression @@ -26,7 +26,7 @@ public QueryableHandlerExpression(object queryableHandler, StringValues paramete public IQueryable Apply(IQueryable query) where TResource : class, IIdentifiable { - var handler = (Func, StringValues, IQueryable>) _queryableHandler; + var handler = (Func, StringValues, IQueryable>)_queryableHandler; return handler(query, _parameterValue); } @@ -52,7 +52,7 @@ public override bool Equals(object obj) return false; } - var other = (QueryableHandlerExpression) obj; + var other = (QueryableHandlerExpression)obj; return _queryableHandler == other._queryableHandler && _parameterValue.Equals(other._parameterValue); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs index 40c30113d9..156c7cd091 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs @@ -33,8 +33,7 @@ public ResourceFieldChainExpression(IReadOnlyCollection Fields = fields; } - public override TResult Accept(QueryExpressionVisitor visitor, - TArgument argument) + public override TResult Accept(QueryExpressionVisitor visitor, TArgument argument) { return visitor.VisitResourceFieldChain(this, argument); } @@ -56,7 +55,7 @@ public override bool Equals(object obj) return false; } - var other = (ResourceFieldChainExpression) obj; + var other = (ResourceFieldChainExpression)obj; return Fields.SequenceEqual(other.Fields); } @@ -65,7 +64,7 @@ public override int GetHashCode() { var hashCode = new HashCode(); - foreach (var field in Fields) + foreach (ResourceFieldAttribute field in Fields) { hashCode.Add(field); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs index 4711bd0cdc..d84564ae9b 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.Queries.Expressions { /// - /// Represents an element in . + /// Represents an element in . /// [PublicAPI] public class SortElementExpression : QueryExpression @@ -68,7 +68,7 @@ public override bool Equals(object obj) return false; } - var other = (SortElementExpression) obj; + var other = (SortElementExpression)obj; return Equals(TargetAttribute, other.TargetAttribute) && Equals(Count, other.Count) && IsAscending == other.IsAscending; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs index 43e25df7e3..ab4d4a6f43 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs @@ -47,7 +47,7 @@ public override bool Equals(object obj) return false; } - var other = (SortExpression) obj; + var other = (SortExpression)obj; return Elements.SequenceEqual(other.Elements); } @@ -56,7 +56,7 @@ public override int GetHashCode() { var hashCode = new HashCode(); - foreach (var element in Elements) + foreach (SortElementExpression element in Elements) { hashCode.Add(element); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs index db55b1ee29..32ba68c45e 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs @@ -48,7 +48,7 @@ public override bool Equals(object obj) return false; } - var other = (SparseFieldSetExpression) obj; + var other = (SparseFieldSetExpression)obj; return Fields.SequenceEqual(other.Fields); } @@ -57,7 +57,7 @@ public override int GetHashCode() { var hashCode = new HashCode(); - foreach (var field in Fields) + foreach (ResourceFieldAttribute field in Fields) { hashCode.Add(field); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs index 7375326189..62010b7e11 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; @@ -18,9 +19,9 @@ public static SparseFieldSetExpression Including(this SparseFieldSetE ArgumentGuard.NotNull(fieldSelector, nameof(fieldSelector)); ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - var newSparseFieldSet = sparseFieldSet; + SparseFieldSetExpression newSparseFieldSet = sparseFieldSet; - foreach (var field in resourceGraph.GetFields(fieldSelector)) + foreach (ResourceFieldAttribute field in resourceGraph.GetFields(fieldSelector)) { newSparseFieldSet = IncludeField(newSparseFieldSet, field); } @@ -35,7 +36,7 @@ private static SparseFieldSetExpression IncludeField(SparseFieldSetExpression sp return sparseFieldSet; } - var fieldSet = sparseFieldSet.Fields.ToHashSet(); + HashSet fieldSet = sparseFieldSet.Fields.ToHashSet(); fieldSet.Add(fieldToInclude); return new SparseFieldSetExpression(fieldSet); } @@ -47,9 +48,9 @@ public static SparseFieldSetExpression Excluding(this SparseFieldSetE ArgumentGuard.NotNull(fieldSelector, nameof(fieldSelector)); ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - var newSparseFieldSet = sparseFieldSet; + SparseFieldSetExpression newSparseFieldSet = sparseFieldSet; - foreach (var field in resourceGraph.GetFields(fieldSelector)) + foreach (ResourceFieldAttribute field in resourceGraph.GetFields(fieldSelector)) { newSparseFieldSet = ExcludeField(newSparseFieldSet, field); } @@ -69,7 +70,7 @@ private static SparseFieldSetExpression ExcludeField(SparseFieldSetExpression sp return sparseFieldSet; } - var fieldSet = sparseFieldSet.Fields.ToHashSet(); + HashSet fieldSet = sparseFieldSet.Fields.ToHashSet(); fieldSet.Remove(fieldToExclude); return new SparseFieldSetExpression(fieldSet); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs index fcbdd2fd82..48c86a1cba 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs @@ -36,7 +36,7 @@ public override string ToString() { var builder = new StringBuilder(); - foreach (var (resource, fields) in Table) + foreach ((ResourceContext resource, SparseFieldSetExpression fields) in Table) { if (builder.Length > 0) { @@ -64,7 +64,7 @@ public override bool Equals(object obj) return false; } - var other = (SparseFieldTableExpression) obj; + var other = (SparseFieldTableExpression)obj; return Table.SequenceEqual(other.Table); } @@ -73,7 +73,7 @@ public override int GetHashCode() { var hashCode = new HashCode(); - foreach (var (resourceContext, sparseFieldSet) in Table) + foreach ((ResourceContext resourceContext, SparseFieldSetExpression sparseFieldSet) in Table) { hashCode.Add(resourceContext); hashCode.Add(sparseFieldSet); diff --git a/src/JsonApiDotNetCore/Queries/IPaginationContext.cs b/src/JsonApiDotNetCore/Queries/IPaginationContext.cs index e80199de7c..fb249cfbec 100644 --- a/src/JsonApiDotNetCore/Queries/IPaginationContext.cs +++ b/src/JsonApiDotNetCore/Queries/IPaginationContext.cs @@ -8,32 +8,30 @@ namespace JsonApiDotNetCore.Queries public interface IPaginationContext { /// - /// The value 1, unless specified from query string. Never null. - /// Cannot be higher than options.MaximumPageNumber. + /// The value 1, unless specified from query string. Never null. Cannot be higher than options.MaximumPageNumber. /// PageNumber PageNumber { get; set; } /// - /// The default page size from options, unless specified in query string. Can be null, which means no paging. - /// Cannot be higher than options.MaximumPageSize. + /// The default page size from options, unless specified in query string. Can be null, which means no paging. Cannot be higher than + /// options.MaximumPageSize. /// PageSize PageSize { get; set; } /// - /// Indicates whether the number of resources on the current page equals the page size. - /// When true, a subsequent page might exist (assuming is unknown). + /// Indicates whether the number of resources on the current page equals the page size. When true, a subsequent page might exist (assuming + /// is unknown). /// bool IsPageFull { get; set; } /// - /// The total number of resources. - /// null when is set to false. + /// The total number of resources. null when is set to false. /// int? TotalResourceCount { get; set; } /// - /// The total number of resource pages. - /// null when is set to false or is null. + /// The total number of resource pages. null when is set to false or + /// is null. /// int? TotalPageCount { get; } } diff --git a/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs index 1f1628edd0..c3fa8428e4 100644 --- a/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.Queries { /// - /// Takes scoped expressions from s and transforms them. + /// Takes scoped expressions from s and transforms them. /// public interface IQueryLayerComposer { @@ -17,12 +17,12 @@ public interface IQueryLayerComposer FilterExpression GetTopFilterFromConstraints(ResourceContext resourceContext); /// - /// Collects constraints and builds a out of them, used to retrieve the actual resources. + /// Collects constraints and builds a out of them, used to retrieve the actual resources. /// QueryLayer ComposeFromConstraints(ResourceContext requestResource); /// - /// Collects constraints and builds a out of them, used to retrieve one resource. + /// Collects constraints and builds a out of them, used to retrieve one resource. /// QueryLayer ComposeForGetById(TId id, ResourceContext resourceContext, TopFieldSelection fieldSelection); @@ -34,11 +34,12 @@ public interface IQueryLayerComposer /// /// Wraps a layer for a secondary endpoint into a primary layer, rewriting top-level includes. /// - QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceContext primaryResourceContext, - TId primaryId, RelationshipAttribute secondaryRelationship); + QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceContext primaryResourceContext, TId primaryId, + RelationshipAttribute secondaryRelationship); /// - /// Builds a query that retrieves the primary resource, including all of its attributes and all targeted relationships, during a create/update/delete request. + /// Builds a query that retrieves the primary resource, including all of its attributes and all targeted relationships, during a create/update/delete + /// request. /// QueryLayer ComposeForUpdate(TId id, ResourceContext primaryResource); diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FieldChainRequirements.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FieldChainRequirements.cs index 443c63a3c0..5864c18d3e 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FieldChainRequirements.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FieldChainRequirements.cs @@ -4,30 +4,30 @@ namespace JsonApiDotNetCore.Queries.Internal.Parsing { /// - /// Used internally when parsing subexpressions in the query string parsers to indicate requirements when resolving a chain of fields. - /// Note these may be interpreted differently or even discarded completely by the various parser implementations, - /// as they tend to better understand the characteristics of the entire expression being parsed. + /// Used internally when parsing subexpressions in the query string parsers to indicate requirements when resolving a chain of fields. Note these may be + /// interpreted differently or even discarded completely by the various parser implementations, as they tend to better understand the characteristics of + /// the entire expression being parsed. /// [Flags] public enum FieldChainRequirements { /// - /// Indicates a single , optionally preceded by a chain of s. + /// Indicates a single , optionally preceded by a chain of s. /// EndsInAttribute = 1, /// - /// Indicates a single , optionally preceded by a chain of s. + /// Indicates a single , optionally preceded by a chain of s. /// EndsInToOne = 2, /// - /// Indicates a single , optionally preceded by a chain of s. + /// Indicates a single , optionally preceded by a chain of s. /// EndsInToMany = 4, /// - /// Indicates one or a chain of s. + /// Indicates one or a chain of s. /// IsRelationship = EndsInToOne | EndsInToMany } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs index aeb5358249..cb2a9ec329 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs @@ -36,7 +36,7 @@ public FilterExpression Parse(string source, ResourceContext resourceContextInSc Tokenize(source); - var expression = ParseFilter(); + FilterExpression expression = ParseFilter(); AssertTokenStackIsEmpty(); @@ -135,7 +135,7 @@ protected ComparisonExpression ParseComparison(string operatorName) EatSingleCharacterToken(TokenKind.OpenParen); // Allow equality comparison of a HasOne relationship with null. - var leftChainRequirements = comparisonOperator == ComparisonOperator.Equals + FieldChainRequirements leftChainRequirements = comparisonOperator == ComparisonOperator.Equals ? FieldChainRequirements.EndsInAttribute | FieldChainRequirements.EndsInToOne : FieldChainRequirements.EndsInAttribute; @@ -149,14 +149,14 @@ protected ComparisonExpression ParseComparison(string operatorName) if (leftTerm is ResourceFieldChainExpression leftChain) { - if (leftChainRequirements.HasFlag(FieldChainRequirements.EndsInToOne) && - !(rightTerm is NullConstantExpression)) + if (leftChainRequirements.HasFlag(FieldChainRequirements.EndsInToOne) && !(rightTerm is NullConstantExpression)) { // Run another pass over left chain to have it fail when chain ends in relationship. OnResolveFieldChain(leftChain.ToString(), FieldChainRequirements.EndsInAttribute); } PropertyInfo leftProperty = leftChain.Fields.Last().Property; + if (leftProperty.Name == nameof(Identifiable.Id) && rightTerm is LiteralConstantExpression rightConstant) { string id = DeObfuscateStringId(leftProperty.ReflectedType, rightConstant.Value); @@ -214,6 +214,7 @@ protected EqualsAnyOfExpression ParseAny() EatSingleCharacterToken(TokenKind.CloseParen); PropertyInfo targetAttributeProperty = targetAttribute.Fields.Last().Property; + if (targetAttributeProperty.Name == nameof(Identifiable.Id)) { for (int index = 0; index < constants.Count; index++) @@ -302,7 +303,7 @@ protected LiteralConstantExpression ParseConstant() private string DeObfuscateStringId(Type resourceType, string stringId) { - var tempResource = _resourceFactory.CreateInstance(resourceType); + IIdentifiable tempResource = _resourceFactory.CreateInstance(resourceType); tempResource.StringId = stringId; return tempResource.GetTypedId().ToString(); } @@ -319,8 +320,7 @@ protected override IReadOnlyCollection OnResolveFieldCha return ChainResolver.ResolveToOneChainEndingInAttribute(_resourceContextInScope, path, _validateSingleFieldCallback); } - if (chainRequirements.HasFlag(FieldChainRequirements.EndsInAttribute) && - chainRequirements.HasFlag(FieldChainRequirements.EndsInToOne)) + if (chainRequirements.HasFlag(FieldChainRequirements.EndsInAttribute) && chainRequirements.HasFlag(FieldChainRequirements.EndsInToOne)) { return ChainResolver.ResolveToOneChainEndingInAttributeOrToOne(_resourceContextInScope, path, _validateSingleFieldCallback); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index d1b7d73321..448bff8762 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -29,7 +29,7 @@ public IncludeExpression Parse(string source, ResourceContext resourceContextInS Tokenize(source); - var expression = ParseInclude(maximumDepth); + IncludeExpression expression = ParseInclude(maximumDepth); AssertTokenStackIsEmpty(); @@ -38,16 +38,15 @@ public IncludeExpression Parse(string source, ResourceContext resourceContextInS protected IncludeExpression ParseInclude(int? maximumDepth) { - ResourceFieldChainExpression firstChain = - ParseFieldChain(FieldChainRequirements.IsRelationship, "Relationship name expected."); + ResourceFieldChainExpression firstChain = ParseFieldChain(FieldChainRequirements.IsRelationship, "Relationship name expected."); - var chains = firstChain.AsList(); + List chains = firstChain.AsList(); while (TokenStack.Any()) { EatSingleCharacterToken(TokenKind.Comma); - var nextChain = ParseFieldChain(FieldChainRequirements.IsRelationship, "Relationship name expected."); + ResourceFieldChainExpression nextChain = ParseFieldChain(FieldChainRequirements.IsRelationship, "Relationship name expected."); chains.Add(nextChain); } @@ -60,11 +59,11 @@ private static void ValidateMaximumIncludeDepth(int? maximumDepth, IReadOnlyColl { if (maximumDepth != null) { - foreach (var chain in chains) + foreach (ResourceFieldChainExpression chain in chains) { if (chain.Fields.Count > maximumDepth) { - var path = string.Join('.', chain.Fields.Select(field => field.PublicName)); + string path = string.Join('.', chain.Fields.Select(field => field.PublicName)); throw new QueryParseException($"Including '{path}' exceeds the maximum inclusion depth of {maximumDepth}."); } } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs index c4744876a7..156c5281dc 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs @@ -29,7 +29,7 @@ public PaginationQueryStringValueExpression Parse(string source, ResourceContext Tokenize(source); - var expression = ParsePagination(); + PaginationQueryStringValueExpression expression = ParsePagination(); AssertTokenStackIsEmpty(); @@ -40,7 +40,7 @@ protected PaginationQueryStringValueExpression ParsePagination() { var elements = new List(); - var element = ParsePaginationElement(); + PaginationElementQueryStringValueExpression element = ParsePaginationElement(); elements.Add(element); while (TokenStack.Any()) @@ -56,17 +56,19 @@ protected PaginationQueryStringValueExpression ParsePagination() protected PaginationElementQueryStringValueExpression ParsePaginationElement() { - var number = TryParseNumber(); + int? number = TryParseNumber(); + if (number != null) { return new PaginationElementQueryStringValueExpression(null, number.Value); } - var scope = ParseFieldChain(FieldChainRequirements.EndsInToMany, "Number or relationship name expected."); + ResourceFieldChainExpression scope = ParseFieldChain(FieldChainRequirements.EndsInToMany, "Number or relationship name expected."); EatSingleCharacterToken(TokenKind.Colon); number = TryParseNumber(); + if (number == null) { throw new QueryParseException("Number expected."); @@ -85,8 +87,7 @@ protected PaginationElementQueryStringValueExpression ParsePaginationElement() { TokenStack.Pop(); - if (TokenStack.TryPop(out Token token) && token.Kind == TokenKind.Text && - int.TryParse(token.Value, out number)) + if (TokenStack.TryPop(out Token token) && token.Kind == TokenKind.Text && int.TryParse(token.Value, out number)) { return -number; } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryExpressionParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryExpressionParser.cs index 15b189c7f7..48fcb44a07 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryExpressionParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryExpressionParser.cs @@ -11,15 +11,14 @@ namespace JsonApiDotNetCore.Queries.Internal.Parsing /// The base class for parsing query string parameters, using the Recursive Descent algorithm. /// /// - /// Uses a tokenizer to populate a stack of tokens, which is then manipulated from the various parsing routines for subexpressions. - /// Implementations should throw on invalid input. + /// Uses a tokenizer to populate a stack of tokens, which is then manipulated from the various parsing routines for subexpressions. Implementations + /// should throw on invalid input. /// [PublicAPI] public abstract class QueryExpressionParser { - private protected ResourceFieldChainResolver ChainResolver { get; } - protected Stack TokenStack { get; private set; } + private protected ResourceFieldChainResolver ChainResolver { get; } protected QueryExpressionParser(IResourceContextProvider resourceContextProvider) { @@ -41,7 +40,8 @@ protected ResourceFieldChainExpression ParseFieldChain(FieldChainRequirements ch { if (TokenStack.TryPop(out Token token) && token.Kind == TokenKind.Text) { - var chain = OnResolveFieldChain(token.Value, chainRequirements); + IReadOnlyCollection chain = OnResolveFieldChain(token.Value, chainRequirements); + if (chain.Any()) { return new ResourceFieldChainExpression(chain); diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryParseException.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryParseException.cs index da2d85f25a..26010e3528 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryParseException.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryParseException.cs @@ -6,7 +6,8 @@ namespace JsonApiDotNetCore.Queries.Internal.Parsing [PublicAPI] public sealed class QueryParseException : Exception { - public QueryParseException(string message) : base(message) + public QueryParseException(string message) + : base(message) { } } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs index 33965facd4..4944f090b1 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs @@ -14,7 +14,7 @@ public class QueryStringParameterScopeParser : QueryExpressionParser private readonly Action _validateSingleFieldCallback; private ResourceContext _resourceContextInScope; - public QueryStringParameterScopeParser(IResourceContextProvider resourceContextProvider, FieldChainRequirements chainRequirements, + public QueryStringParameterScopeParser(IResourceContextProvider resourceContextProvider, FieldChainRequirements chainRequirements, Action validateSingleFieldCallback = null) : base(resourceContextProvider) { @@ -30,7 +30,7 @@ public QueryStringParameterScopeExpression Parse(string source, ResourceContext Tokenize(source); - var expression = ParseQueryStringParameterScope(); + QueryStringParameterScopeExpression expression = ParseQueryStringParameterScope(); AssertTokenStackIsEmpty(); diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs index 0084fa7bc2..04feabd60d 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs @@ -8,8 +8,8 @@ namespace JsonApiDotNetCore.Queries.Internal.Parsing [PublicAPI] public sealed class QueryTokenizer { - public static readonly IReadOnlyDictionary SingleCharacterToTokenKinds = - new ReadOnlyDictionary(new Dictionary + public static readonly IReadOnlyDictionary SingleCharacterToTokenKinds = new ReadOnlyDictionary( + new Dictionary { ['('] = TokenKind.OpenParen, [')'] = TokenKind.CloseParen, diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs index b0a4af334f..c4c0d1c546 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs @@ -28,12 +28,12 @@ public IReadOnlyCollection ResolveToManyChain(ResourceCo { var chain = new List(); - var publicNameParts = path.Split("."); - var nextResourceContext = resourceContext; + string[] publicNameParts = path.Split("."); + ResourceContext nextResourceContext = resourceContext; foreach (string publicName in publicNameParts[..^1]) { - var relationship = GetRelationship(publicName, nextResourceContext, path); + RelationshipAttribute relationship = GetRelationship(publicName, nextResourceContext, path); validateCallback?.Invoke(relationship, nextResourceContext, path); @@ -42,7 +42,7 @@ public IReadOnlyCollection ResolveToManyChain(ResourceCo } string lastName = publicNameParts[^1]; - var lastToManyRelationship = GetToManyRelationship(lastName, nextResourceContext, path); + RelationshipAttribute lastToManyRelationship = GetToManyRelationship(lastName, nextResourceContext, path); validateCallback?.Invoke(lastToManyRelationship, nextResourceContext, path); @@ -62,15 +62,15 @@ public IReadOnlyCollection ResolveToManyChain(ResourceCo /// articles.revisions.author /// /// - public IReadOnlyCollection ResolveRelationshipChain(ResourceContext resourceContext, string path, + public IReadOnlyCollection ResolveRelationshipChain(ResourceContext resourceContext, string path, Action validateCallback = null) { var chain = new List(); - var nextResourceContext = resourceContext; + ResourceContext nextResourceContext = resourceContext; foreach (string publicName in path.Split(".")) { - var relationship = GetRelationship(publicName, nextResourceContext, path); + RelationshipAttribute relationship = GetRelationship(publicName, nextResourceContext, path); validateCallback?.Invoke(relationship, nextResourceContext, path); @@ -86,21 +86,19 @@ public IReadOnlyCollection ResolveRelationshipChain(Reso /// /// author.address.country.name /// - /// - /// name - /// + /// name /// public IReadOnlyCollection ResolveToOneChainEndingInAttribute(ResourceContext resourceContext, string path, Action validateCallback = null) { - List chain = new List(); + var chain = new List(); - var publicNameParts = path.Split("."); - var nextResourceContext = resourceContext; + string[] publicNameParts = path.Split("."); + ResourceContext nextResourceContext = resourceContext; foreach (string publicName in publicNameParts[..^1]) { - var toOneRelationship = GetToOneRelationship(publicName, nextResourceContext, path); + RelationshipAttribute toOneRelationship = GetToOneRelationship(publicName, nextResourceContext, path); validateCallback?.Invoke(toOneRelationship, nextResourceContext, path); @@ -109,7 +107,7 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr } string lastName = publicNameParts[^1]; - var lastAttribute = GetAttribute(lastName, nextResourceContext, path); + AttrAttribute lastAttribute = GetAttribute(lastName, nextResourceContext, path); validateCallback?.Invoke(lastAttribute, nextResourceContext, path); @@ -129,14 +127,14 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr public IReadOnlyCollection ResolveToOneChainEndingInToMany(ResourceContext resourceContext, string path, Action validateCallback = null) { - List chain = new List(); + var chain = new List(); - var publicNameParts = path.Split("."); - var nextResourceContext = resourceContext; + string[] publicNameParts = path.Split("."); + ResourceContext nextResourceContext = resourceContext; foreach (string publicName in publicNameParts[..^1]) { - var toOneRelationship = GetToOneRelationship(publicName, nextResourceContext, path); + RelationshipAttribute toOneRelationship = GetToOneRelationship(publicName, nextResourceContext, path); validateCallback?.Invoke(toOneRelationship, nextResourceContext, path); @@ -146,7 +144,7 @@ public IReadOnlyCollection ResolveToOneChainEndingInToMa string lastName = publicNameParts[^1]; - var toManyRelationship = GetToManyRelationship(lastName, nextResourceContext, path); + RelationshipAttribute toManyRelationship = GetToManyRelationship(lastName, nextResourceContext, path); validateCallback?.Invoke(toManyRelationship, nextResourceContext, path); @@ -166,14 +164,14 @@ public IReadOnlyCollection ResolveToOneChainEndingInToMa public IReadOnlyCollection ResolveToOneChainEndingInAttributeOrToOne(ResourceContext resourceContext, string path, Action validateCallback = null) { - List chain = new List(); + var chain = new List(); - var publicNameParts = path.Split("."); - var nextResourceContext = resourceContext; + string[] publicNameParts = path.Split("."); + ResourceContext nextResourceContext = resourceContext; foreach (string publicName in publicNameParts[..^1]) { - var toOneRelationship = GetToOneRelationship(publicName, nextResourceContext, path); + RelationshipAttribute toOneRelationship = GetToOneRelationship(publicName, nextResourceContext, path); validateCallback?.Invoke(toOneRelationship, nextResourceContext, path); @@ -182,7 +180,7 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr } string lastName = publicNameParts[^1]; - var lastField = GetField(lastName, nextResourceContext, path); + ResourceFieldAttribute lastField = GetField(lastName, nextResourceContext, path); if (lastField is HasManyAttribute) { @@ -199,7 +197,7 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr private RelationshipAttribute GetRelationship(string publicName, ResourceContext resourceContext, string path) { - var relationship = resourceContext.Relationships.FirstOrDefault(r => r.PublicName == publicName); + RelationshipAttribute relationship = resourceContext.Relationships.FirstOrDefault(r => r.PublicName == publicName); if (relationship == null) { @@ -213,7 +211,7 @@ private RelationshipAttribute GetRelationship(string publicName, ResourceContext private RelationshipAttribute GetToManyRelationship(string publicName, ResourceContext resourceContext, string path) { - var relationship = GetRelationship(publicName, resourceContext, path); + RelationshipAttribute relationship = GetRelationship(publicName, resourceContext, path); if (!(relationship is HasManyAttribute)) { @@ -227,7 +225,7 @@ private RelationshipAttribute GetToManyRelationship(string publicName, ResourceC private RelationshipAttribute GetToOneRelationship(string publicName, ResourceContext resourceContext, string path) { - var relationship = GetRelationship(publicName, resourceContext, path); + RelationshipAttribute relationship = GetRelationship(publicName, resourceContext, path); if (!(relationship is HasOneAttribute)) { @@ -241,7 +239,7 @@ private RelationshipAttribute GetToOneRelationship(string publicName, ResourceCo private AttrAttribute GetAttribute(string publicName, ResourceContext resourceContext, string path) { - var attribute = resourceContext.Attributes.FirstOrDefault(a => a.PublicName == publicName); + AttrAttribute attribute = resourceContext.Attributes.FirstOrDefault(a => a.PublicName == publicName); if (attribute == null) { @@ -255,7 +253,7 @@ private AttrAttribute GetAttribute(string publicName, ResourceContext resourceCo public ResourceFieldAttribute GetField(string publicName, ResourceContext resourceContext, string path) { - var field = resourceContext.Fields.FirstOrDefault(a => a.PublicName == publicName); + ResourceFieldAttribute field = resourceContext.Fields.FirstOrDefault(a => a.PublicName == publicName); if (field == null) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs index 2b00f686ef..3b146b1785 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs @@ -40,7 +40,7 @@ protected SortExpression ParseSort() { SortElementExpression firstElement = ParseSortElement(); - var elements = firstElement.AsList(); + List elements = firstElement.AsList(); while (TokenStack.Any()) { @@ -64,12 +64,13 @@ protected SortElementExpression ParseSortElement() } CountExpression count = TryParseCount(); + if (count != null) { return new SortElementExpression(count, isAscending); } - var errorMessage = isAscending ? "-, count function or field name expected." : "Count function or field name expected."; + string errorMessage = isAscending ? "-, count function or field name expected." : "Count function or field name expected."; ResourceFieldChainExpression targetAttribute = ParseFieldChain(FieldChainRequirements.EndsInAttribute, errorMessage); return new SortElementExpression(targetAttribute, isAscending); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs index aea883f43e..52fbe6610b 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs @@ -14,7 +14,8 @@ public class SparseFieldSetParser : QueryExpressionParser private readonly Action _validateSingleFieldCallback; private ResourceContext _resourceContext; - public SparseFieldSetParser(IResourceContextProvider resourceContextProvider, Action validateSingleFieldCallback = null) + public SparseFieldSetParser(IResourceContextProvider resourceContextProvider, + Action validateSingleFieldCallback = null) : base(resourceContextProvider) { _validateSingleFieldCallback = validateSingleFieldCallback; @@ -28,7 +29,7 @@ public SparseFieldSetExpression Parse(string source, ResourceContext resourceCon Tokenize(source); - var expression = ParseSparseFieldSet(); + SparseFieldSetExpression expression = ParseSparseFieldSet(); AssertTokenStackIsEmpty(); @@ -57,7 +58,7 @@ protected SparseFieldSetExpression ParseSparseFieldSet() protected override IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) { - var field = ChainResolver.GetField(path, _resourceContext, path); + ResourceFieldAttribute field = ChainResolver.GetField(path, _resourceContext, path); _validateSingleFieldCallback?.Invoke(field, _resourceContext, path); diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs index 18fd966167..361710e345 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs @@ -21,7 +21,7 @@ public ResourceContext Parse(string source) { Tokenize(source); - var resourceContext = ParseSparseFieldTarget(); + ResourceContext resourceContext = ParseSparseFieldTarget(); AssertTokenStackIsEmpty(); @@ -37,7 +37,7 @@ private ResourceContext ParseSparseFieldTarget() EatSingleCharacterToken(TokenKind.OpenBracket); - var resourceContext = ParseResourceName(); + ResourceContext resourceContext = ParseResourceName(); EatSingleCharacterToken(TokenKind.CloseBracket); @@ -56,7 +56,8 @@ private ResourceContext ParseResourceName() private ResourceContext GetResourceContext(string resourceName) { - var resourceContext = _resourceContextProvider.GetResourceContext(resourceName); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceName); + if (resourceContext == null) { throw new QueryParseException($"Resource type '{resourceName}' does not exist."); diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index eac149391a..8476ecfcb4 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -21,12 +21,8 @@ public class QueryLayerComposer : IQueryLayerComposer private readonly ITargetedFields _targetedFields; private readonly SparseFieldSetCache _sparseFieldSetCache; - public QueryLayerComposer( - IEnumerable constraintProviders, - IResourceContextProvider resourceContextProvider, - IResourceDefinitionAccessor resourceDefinitionAccessor, - IJsonApiOptions options, - IPaginationContext paginationContext, + public QueryLayerComposer(IEnumerable constraintProviders, IResourceContextProvider resourceContextProvider, + IResourceDefinitionAccessor resourceDefinitionAccessor, IJsonApiOptions options, IPaginationContext paginationContext, ITargetedFields targetedFields) { ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); @@ -48,12 +44,12 @@ public QueryLayerComposer( /// public FilterExpression GetTopFilterFromConstraints(ResourceContext resourceContext) { - var constraints = _constraintProviders.SelectMany(provider => provider.GetConstraints()).ToArray(); + ExpressionInScope[] constraints = _constraintProviders.SelectMany(provider => provider.GetConstraints()).ToArray(); // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var filtersInTopScope = constraints + FilterExpression[] filtersInTopScope = constraints .Where(constraint => constraint.Scope == null) .Select(constraint => constraint.Expression) .OfType() @@ -70,9 +66,9 @@ public QueryLayer ComposeFromConstraints(ResourceContext requestResource) { ArgumentGuard.NotNull(requestResource, nameof(requestResource)); - var constraints = _constraintProviders.SelectMany(provider => provider.GetConstraints()).ToArray(); + ExpressionInScope[] constraints = _constraintProviders.SelectMany(provider => provider.GetConstraints()).ToArray(); - var topLayer = ComposeTopLayer(constraints, requestResource); + QueryLayer topLayer = ComposeTopLayer(constraints, requestResource); topLayer.Include = ComposeChildren(topLayer, constraints); return topLayer; @@ -83,7 +79,7 @@ private QueryLayer ComposeTopLayer(IEnumerable constraints, R // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var expressionsInTopScope = constraints + QueryExpression[] expressionsInTopScope = constraints .Where(constraint => constraint.Scope == null) .Select(constraint => constraint.Expression) .ToArray(); @@ -91,7 +87,8 @@ private QueryLayer ComposeTopLayer(IEnumerable constraints, R // @formatter:keep_existing_linebreaks restore // @formatter:wrap_chained_method_calls restore - var topPagination = GetPagination(expressionsInTopScope, resourceContext); + PaginationExpression topPagination = GetPagination(expressionsInTopScope, resourceContext); + if (topPagination != null) { _paginationContext.PageSize = topPagination.PageSize; @@ -112,7 +109,7 @@ private IncludeExpression ComposeChildren(QueryLayer topLayer, ICollection constraint.Scope == null) .Select(constraint => constraint.Expression) .OfType() @@ -121,7 +118,7 @@ private IncludeExpression ComposeChildren(QueryLayer topLayer, ICollection includeElements = ProcessIncludeSet(include.Elements, topLayer, new List(), constraints); return !ReferenceEquals(includeElements, include.Elements) @@ -132,11 +129,12 @@ private IncludeExpression ComposeChildren(QueryLayer topLayer, ICollection ProcessIncludeSet(IReadOnlyCollection includeElements, QueryLayer parentLayer, ICollection parentRelationshipChain, ICollection constraints) { - var includeElementsEvaluated = GetIncludeElements(includeElements, parentLayer.ResourceContext) ?? Array.Empty(); + IReadOnlyCollection includeElementsEvaluated = + GetIncludeElements(includeElements, parentLayer.ResourceContext) ?? Array.Empty(); var updatesInChildren = new Dictionary>(); - foreach (var includeElement in includeElementsEvaluated) + foreach (IncludeElementExpression includeElement in includeElementsEvaluated) { parentLayer.Projection ??= new Dictionary(); @@ -150,7 +148,7 @@ private IReadOnlyCollection ProcessIncludeSet(IReadOnl // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var expressionsInCurrentScope = constraints + QueryExpression[] expressionsInCurrentScope = constraints .Where(constraint => constraint.Scope != null && constraint.Scope.Fields.SequenceEqual(relationshipChain)) .Select(constraint => constraint.Expression) @@ -159,16 +157,13 @@ private IReadOnlyCollection ProcessIncludeSet(IReadOnl // @formatter:keep_existing_linebreaks restore // @formatter:wrap_chained_method_calls restore - var resourceContext = - _resourceContextProvider.GetResourceContext(includeElement.Relationship.RightType); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(includeElement.Relationship.RightType); var child = new QueryLayer(resourceContext) { Filter = GetFilter(expressionsInCurrentScope, resourceContext), Sort = GetSort(expressionsInCurrentScope, resourceContext), - Pagination = ((JsonApiOptions)_options).DisableChildrenPagination - ? null - : GetPagination(expressionsInCurrentScope, resourceContext), + Pagination = ((JsonApiOptions)_options).DisableChildrenPagination ? null : GetPagination(expressionsInCurrentScope, resourceContext), Projection = GetProjectionForSparseAttributeSet(resourceContext) }; @@ -176,7 +171,8 @@ private IReadOnlyCollection ProcessIncludeSet(IReadOnl if (includeElement.Children.Any()) { - var updatedChildren = ProcessIncludeSet(includeElement.Children, child, relationshipChain, constraints); + IReadOnlyCollection updatedChildren = + ProcessIncludeSet(includeElement.Children, child, relationshipChain, constraints); if (!ReferenceEquals(includeElement.Children, updatedChildren)) { @@ -192,11 +188,11 @@ private IReadOnlyCollection ProcessIncludeSet(IReadOnl private static IReadOnlyCollection ApplyIncludeElementUpdates(IEnumerable includeElements, IDictionary> updatesInChildren) { - var newIncludeElements = includeElements.ToList(); + List newIncludeElements = includeElements.ToList(); - foreach (var (existingElement, updatedChildren) in updatesInChildren) + foreach ((IncludeElementExpression existingElement, IReadOnlyCollection updatedChildren) in updatesInChildren) { - var existingIndex = newIncludeElements.IndexOf(existingElement); + int existingIndex = newIncludeElements.IndexOf(existingElement); newIncludeElements[existingIndex] = new IncludeElementExpression(existingElement.Relationship, updatedChildren); } @@ -208,9 +204,9 @@ public QueryLayer ComposeForGetById(TId id, ResourceContext resourceContext { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); - var idAttribute = GetIdAttribute(resourceContext); + AttrAttribute idAttribute = GetIdAttribute(resourceContext); - var queryLayer = ComposeFromConstraints(resourceContext); + QueryLayer queryLayer = ComposeFromConstraints(resourceContext); queryLayer.Sort = null; queryLayer.Pagination = null; queryLayer.Filter = CreateFilterByIds(id.AsArray(), idAttribute, queryLayer.Filter); @@ -239,7 +235,7 @@ public QueryLayer ComposeSecondaryLayerForRelationship(ResourceContext secondary { ArgumentGuard.NotNull(secondaryResourceContext, nameof(secondaryResourceContext)); - var secondaryLayer = ComposeFromConstraints(secondaryResourceContext); + QueryLayer secondaryLayer = ComposeFromConstraints(secondaryResourceContext); secondaryLayer.Projection = GetProjectionForRelationship(secondaryResourceContext); secondaryLayer.Include = null; @@ -248,27 +244,31 @@ public QueryLayer ComposeSecondaryLayerForRelationship(ResourceContext secondary private IDictionary GetProjectionForRelationship(ResourceContext secondaryResourceContext) { - var secondaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(secondaryResourceContext); + IReadOnlyCollection secondaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(secondaryResourceContext); return secondaryAttributeSet.ToDictionary(key => (ResourceFieldAttribute)key, _ => (QueryLayer)null); } /// - public QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceContext primaryResourceContext, TId primaryId, RelationshipAttribute secondaryRelationship) + public QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceContext primaryResourceContext, TId primaryId, + RelationshipAttribute secondaryRelationship) { ArgumentGuard.NotNull(secondaryLayer, nameof(secondaryLayer)); ArgumentGuard.NotNull(primaryResourceContext, nameof(primaryResourceContext)); ArgumentGuard.NotNull(secondaryRelationship, nameof(secondaryRelationship)); - var innerInclude = secondaryLayer.Include; + IncludeExpression innerInclude = secondaryLayer.Include; secondaryLayer.Include = null; - var primaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(primaryResourceContext); - var primaryProjection = primaryAttributeSet.ToDictionary(key => (ResourceFieldAttribute)key, _ => (QueryLayer)null); + IReadOnlyCollection primaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(primaryResourceContext); + + Dictionary primaryProjection = + primaryAttributeSet.ToDictionary(key => (ResourceFieldAttribute)key, _ => (QueryLayer)null); + primaryProjection[secondaryRelationship] = secondaryLayer; - var primaryFilter = GetFilter(Array.Empty(), primaryResourceContext); - var primaryIdAttribute = GetIdAttribute(primaryResourceContext); + FilterExpression primaryFilter = GetFilter(Array.Empty(), primaryResourceContext); + AttrAttribute primaryIdAttribute = GetIdAttribute(primaryResourceContext); return new QueryLayer(primaryResourceContext) { @@ -280,7 +280,7 @@ public QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression relativeInclude, RelationshipAttribute secondaryRelationship) { - var parentElement = relativeInclude != null + IncludeElementExpression parentElement = relativeInclude != null ? new IncludeElementExpression(secondaryRelationship, relativeInclude.Elements) : new IncludeElementExpression(secondaryRelationship); @@ -300,7 +300,7 @@ private FilterExpression CreateFilterByIds(ICollection ids, AttrAttrib } else if (ids.Count > 1) { - var constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToList(); + List constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToList(); filter = new EqualsAnyOfExpression(idChain, constants); } @@ -320,12 +320,12 @@ public QueryLayer ComposeForUpdate(TId id, ResourceContext primaryResource) { ArgumentGuard.NotNull(primaryResource, nameof(primaryResource)); - var includeElements = _targetedFields.Relationships + IncludeElementExpression[] includeElements = _targetedFields.Relationships .Select(relationship => new IncludeElementExpression(relationship)).ToArray(); - var primaryIdAttribute = GetIdAttribute(primaryResource); + AttrAttribute primaryIdAttribute = GetIdAttribute(primaryResource); - var primaryLayer = ComposeTopLayer(Array.Empty(), primaryResource); + QueryLayer primaryLayer = ComposeTopLayer(Array.Empty(), primaryResource); primaryLayer.Include = includeElements.Any() ? new IncludeExpression(includeElements) : IncludeExpression.Empty; primaryLayer.Sort = null; primaryLayer.Pagination = null; @@ -340,14 +340,14 @@ public QueryLayer ComposeForUpdate(TId id, ResourceContext primaryResource) { ArgumentGuard.NotNull(primaryResource, nameof(primaryResource)); - foreach (var relationship in _targetedFields.Relationships) + foreach (RelationshipAttribute relationship in _targetedFields.Relationships) { object rightValue = relationship.GetValue(primaryResource); ICollection rightResourceIds = TypeHelper.ExtractResources(rightValue); if (rightResourceIds.Any()) { - var queryLayer = ComposeForGetRelationshipRightIds(relationship, rightResourceIds); + QueryLayer queryLayer = ComposeForGetRelationshipRightIds(relationship, rightResourceIds); yield return (queryLayer, relationship); } } @@ -359,13 +359,13 @@ public QueryLayer ComposeForGetRelationshipRightIds(RelationshipAttribute relati ArgumentGuard.NotNull(relationship, nameof(relationship)); ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); - var rightResourceContext = _resourceContextProvider.GetResourceContext(relationship.RightType); - var rightIdAttribute = GetIdAttribute(rightResourceContext); + ResourceContext rightResourceContext = _resourceContextProvider.GetResourceContext(relationship.RightType); + AttrAttribute rightIdAttribute = GetIdAttribute(rightResourceContext); - var typedIds = rightResourceIds.Select(resource => resource.GetTypedId()).ToArray(); + object[] typedIds = rightResourceIds.Select(resource => resource.GetTypedId()).ToArray(); - var baseFilter = GetFilter(Array.Empty(), rightResourceContext); - var filter = CreateFilterByIds(typedIds, rightIdAttribute, baseFilter); + FilterExpression baseFilter = GetFilter(Array.Empty(), rightResourceContext); + FilterExpression filter = CreateFilterByIds(typedIds, rightIdAttribute, baseFilter); return new QueryLayer(rightResourceContext) { @@ -384,15 +384,15 @@ public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, T ArgumentGuard.NotNull(hasManyRelationship, nameof(hasManyRelationship)); ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); - var leftResourceContext = _resourceContextProvider.GetResourceContext(hasManyRelationship.LeftType); - var leftIdAttribute = GetIdAttribute(leftResourceContext); + ResourceContext leftResourceContext = _resourceContextProvider.GetResourceContext(hasManyRelationship.LeftType); + AttrAttribute leftIdAttribute = GetIdAttribute(leftResourceContext); - var rightResourceContext = _resourceContextProvider.GetResourceContext(hasManyRelationship.RightType); - var rightIdAttribute = GetIdAttribute(rightResourceContext); - var rightTypedIds = rightResourceIds.Select(resource => resource.GetTypedId()).ToArray(); + ResourceContext rightResourceContext = _resourceContextProvider.GetResourceContext(hasManyRelationship.RightType); + AttrAttribute rightIdAttribute = GetIdAttribute(rightResourceContext); + object[] rightTypedIds = rightResourceIds.Select(resource => resource.GetTypedId()).ToArray(); - var leftFilter = CreateFilterByIds(leftId.AsArray(), leftIdAttribute, null); - var rightFilter = CreateFilterByIds(rightTypedIds, rightIdAttribute, null); + FilterExpression leftFilter = CreateFilterByIds(leftId.AsArray(), leftIdAttribute, null); + FilterExpression rightFilter = CreateFilterByIds(rightTypedIds, rightIdAttribute, null); return new QueryLayer(leftResourceContext) { @@ -413,7 +413,8 @@ public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, T }; } - protected virtual IReadOnlyCollection GetIncludeElements(IReadOnlyCollection includeElements, ResourceContext resourceContext) + protected virtual IReadOnlyCollection GetIncludeElements(IReadOnlyCollection includeElements, + ResourceContext resourceContext) { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); @@ -425,11 +426,9 @@ protected virtual FilterExpression GetFilter(IReadOnlyCollection().ToArray(); + FilterExpression[] filters = expressionsInScope.OfType().ToArray(); - var filter = filters.Length > 1 - ? new LogicalExpression(LogicalOperator.And, filters) - : filters.FirstOrDefault(); + FilterExpression filter = filters.Length > 1 ? new LogicalExpression(LogicalOperator.And, filters) : filters.FirstOrDefault(); return _resourceDefinitionAccessor.OnApplyFilter(resourceContext.ResourceType, filter); } @@ -439,13 +438,13 @@ protected virtual SortExpression GetSort(IReadOnlyCollection ex ArgumentGuard.NotNull(expressionsInScope, nameof(expressionsInScope)); ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); - var sort = expressionsInScope.OfType().FirstOrDefault(); + SortExpression sort = expressionsInScope.OfType().FirstOrDefault(); sort = _resourceDefinitionAccessor.OnApplySort(resourceContext.ResourceType, sort); if (sort == null) { - var idAttribute = GetIdAttribute(resourceContext); + AttrAttribute idAttribute = GetIdAttribute(resourceContext); sort = new SortExpression(new SortElementExpression(new ResourceFieldChainExpression(idAttribute), true).AsArray()); } @@ -457,7 +456,7 @@ protected virtual PaginationExpression GetPagination(IReadOnlyCollection().FirstOrDefault(); + PaginationExpression pagination = expressionsInScope.OfType().FirstOrDefault(); pagination = _resourceDefinitionAccessor.OnApplyPagination(resourceContext.ResourceType, pagination); @@ -470,14 +469,15 @@ protected virtual IDictionary GetProjectionF { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); - var fieldSet = _sparseFieldSetCache.GetSparseFieldSetForQuery(resourceContext); + IReadOnlyCollection fieldSet = _sparseFieldSetCache.GetSparseFieldSetForQuery(resourceContext); + if (!fieldSet.Any()) { return null; } - var attributeSet = fieldSet.OfType().ToHashSet(); - var idAttribute = GetIdAttribute(resourceContext); + HashSet attributeSet = fieldSet.OfType().ToHashSet(); + AttrAttribute idAttribute = GetIdAttribute(resourceContext); attributeSet.Add(idAttribute); return attributeSet.ToDictionary(key => (ResourceFieldAttribute)key, _ => (QueryLayer)null); diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/IncludeClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/IncludeClauseBuilder.cs index 2571b4931f..424ff9962e 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/IncludeClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/IncludeClauseBuilder.cs @@ -10,7 +10,7 @@ namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding { /// - /// Transforms into calls. + /// Transforms into calls. /// [PublicAPI] public class IncludeClauseBuilder : QueryClauseBuilder @@ -41,7 +41,7 @@ public Expression ApplyInclude(IncludeExpression include) public override Expression VisitInclude(IncludeExpression expression, object argument) { - var source = ApplyEagerLoads(_source, _resourceContext.EagerLoads, null); + Expression source = ApplyEagerLoads(_source, _resourceContext.EagerLoads, null); foreach (ResourceFieldChainExpression chain in IncludeChainConverter.GetRelationshipChains(expression)) { @@ -54,13 +54,13 @@ public override Expression VisitInclude(IncludeExpression expression, object arg private Expression ProcessRelationshipChain(ResourceFieldChainExpression chain, Expression source) { string path = null; - var result = source; + Expression result = source; - foreach (var relationship in chain.Fields.Cast()) + foreach (RelationshipAttribute relationship in chain.Fields.Cast()) { path = path == null ? relationship.RelationshipPath : path + "." + relationship.RelationshipPath; - var resourceContext = _resourceContextProvider.GetResourceContext(relationship.RightType); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(relationship.RightType); result = ApplyEagerLoads(result, resourceContext.EagerLoads, path); } @@ -69,9 +69,9 @@ private Expression ProcessRelationshipChain(ResourceFieldChainExpression chain, private Expression ApplyEagerLoads(Expression source, IEnumerable eagerLoads, string pathPrefix) { - var result = source; + Expression result = source; - foreach (var eagerLoad in eagerLoads) + foreach (EagerLoadAttribute eagerLoad in eagerLoads) { string path = pathPrefix != null ? pathPrefix + "." + eagerLoad.Property.Name : eagerLoad.Property.Name; result = IncludeExtensionMethodCall(result, path); diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs index 2ddfb60e47..543a89d673 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs @@ -9,7 +9,8 @@ namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding { /// - /// Transforms into calls. + /// Transforms into + /// calls. /// [PublicAPI] public class OrderClauseBuilder : QueryClauseBuilder @@ -48,9 +49,7 @@ public override Expression VisitSort(SortExpression expression, Expression argum public override Expression VisitSortElement(SortElementExpression expression, Expression previousExpression) { - Expression body = expression.Count != null - ? Visit(expression.Count, null) - : Visit(expression.TargetAttribute, null); + Expression body = expression.Count != null ? Visit(expression.Count, null) : Visit(expression.TargetAttribute, null); LambdaExpression lambda = Expression.Lambda(body, LambdaScope.Parameter); @@ -69,16 +68,15 @@ private static string GetOperationName(bool hasPrecedingSort, bool isAscending) return isAscending ? "OrderBy" : "OrderByDescending"; } - private Expression ExtensionMethodCall(Expression source, string operationName, Type keyType, - LambdaExpression keySelector) + private Expression ExtensionMethodCall(Expression source, string operationName, Type keyType, LambdaExpression keySelector) { - var typeArguments = ArrayFactory.Create(LambdaScope.Parameter.Type, keyType); + Type[] typeArguments = ArrayFactory.Create(LambdaScope.Parameter.Type, keyType); return Expression.Call(_extensionType, operationName, typeArguments, source, keySelector); } protected override MemberExpression CreatePropertyExpressionForFieldChain(IReadOnlyCollection chain, Expression source) { - var components = chain.Select(GetPropertyName).ToArray(); + string[] components = chain.Select(GetPropertyName).ToArray(); return CreatePropertyExpressionFromComponents(LambdaScope.Accessor, components); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs index 60909aa272..519a800cc2 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding { /// - /// Base class for transforming trees into system trees. + /// Base class for transforming trees into system trees. /// public abstract class QueryClauseBuilder : QueryExpressionVisitor { @@ -24,9 +24,10 @@ protected QueryClauseBuilder(LambdaScope lambdaScope) public override Expression VisitCount(CountExpression expression, TArgument argument) { - var collectionExpression = Visit(expression.TargetCollection, argument); + Expression collectionExpression = Visit(expression.TargetCollection, argument); + + Expression propertyExpression = TryGetCollectionCount(collectionExpression); - var propertyExpression = TryGetCollectionCount(collectionExpression); if (propertyExpression == null) { throw new InvalidOperationException($"Field '{expression.TargetCollection}' must be a collection."); @@ -38,15 +39,16 @@ public override Expression VisitCount(CountExpression expression, TArgument argu private static Expression TryGetCollectionCount(Expression collectionExpression) { var properties = new HashSet(collectionExpression.Type.GetProperties()); + if (collectionExpression.Type.IsInterface) { - foreach (var item in collectionExpression.Type.GetInterfaces().SelectMany(i => i.GetProperties())) + foreach (PropertyInfo item in collectionExpression.Type.GetInterfaces().SelectMany(i => i.GetProperties())) { properties.Add(item); } } - foreach (var property in properties) + foreach (PropertyInfo property in properties) { if (property.Name == "Count" || property.Name == "Length") { @@ -64,9 +66,9 @@ public override Expression VisitResourceFieldChain(ResourceFieldChainExpression protected virtual MemberExpression CreatePropertyExpressionForFieldChain(IReadOnlyCollection chain, Expression source) { - var components = chain.Select(field => - field is RelationshipAttribute relationship ? relationship.RelationshipPath : field.Property.Name).ToArray(); - + string[] components = chain.Select(field => field is RelationshipAttribute relationship ? relationship.RelationshipPath : field.Property.Name) + .ToArray(); + return CreatePropertyExpressionFromComponents(source, components); } @@ -74,19 +76,16 @@ protected static MemberExpression CreatePropertyExpressionFromComponents(Express { MemberExpression property = null; - foreach (var propertyName in components) + foreach (string propertyName in components) { Type parentType = property == null ? source.Type : property.Type; if (parentType.GetProperty(propertyName) == null) { - throw new InvalidOperationException( - $"Type '{parentType.Name}' does not contain a property named '{propertyName}'."); + throw new InvalidOperationException($"Type '{parentType.Name}' does not contain a property named '{propertyName}'."); } - property = property == null - ? Expression.Property(source, propertyName) - : Expression.Property(property, propertyName); + property = property == null ? Expression.Property(source, propertyName) : Expression.Property(property, propertyName); } return property; diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs index 2faa590dc1..4d4187b84f 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs @@ -12,7 +12,7 @@ namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding { /// - /// Drives conversion from into system trees. + /// Drives conversion from into system trees. /// [PublicAPI] public class QueryableBuilder @@ -84,7 +84,7 @@ public virtual Expression ApplyQuery(QueryLayer layer) protected virtual Expression ApplyInclude(Expression source, IncludeExpression include, ResourceContext resourceContext) { - using var lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); + using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); var builder = new IncludeClauseBuilder(source, lambdaScope, resourceContext, _resourceContextProvider); return builder.ApplyInclude(include); @@ -92,7 +92,7 @@ protected virtual Expression ApplyInclude(Expression source, IncludeExpression i protected virtual Expression ApplyFilter(Expression source, FilterExpression filter) { - using var lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); + using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); var builder = new WhereClauseBuilder(source, lambdaScope, _extensionType); return builder.ApplyWhere(filter); @@ -100,7 +100,7 @@ protected virtual Expression ApplyFilter(Expression source, FilterExpression fil protected virtual Expression ApplySort(Expression source, SortExpression sort) { - using var lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); + using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); var builder = new OrderClauseBuilder(source, lambdaScope, _extensionType); return builder.ApplyOrderBy(sort); @@ -108,15 +108,16 @@ protected virtual Expression ApplySort(Expression source, SortExpression sort) protected virtual Expression ApplyPagination(Expression source, PaginationExpression pagination) { - using var lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); + using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); var builder = new SkipTakeClauseBuilder(source, lambdaScope, _extensionType); return builder.ApplySkipTake(pagination); } - protected virtual Expression ApplyProjection(Expression source, IDictionary projection, ResourceContext resourceContext) + protected virtual Expression ApplyProjection(Expression source, IDictionary projection, + ResourceContext resourceContext) { - using var lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); + using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); var builder = new SelectClauseBuilder(source, lambdaScope, _entityModel, _extensionType, _nameFactory, _resourceFactory, _resourceContextProvider); return builder.ApplySelect(projection, resourceContext); diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs index 09f1d3c3ed..6925591b9f 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs @@ -15,7 +15,8 @@ namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding { /// - /// Transforms into calls. + /// Transforms into + /// calls. /// [PublicAPI] public class SelectClauseBuilder : QueryClauseBuilder @@ -29,8 +30,8 @@ public class SelectClauseBuilder : QueryClauseBuilder private readonly IResourceFactory _resourceFactory; private readonly IResourceContextProvider _resourceContextProvider; - public SelectClauseBuilder(Expression source, LambdaScope lambdaScope, IModel entityModel, Type extensionType, - LambdaParameterNameFactory nameFactory, IResourceFactory resourceFactory, IResourceContextProvider resourceContextProvider) + public SelectClauseBuilder(Expression source, LambdaScope lambdaScope, IModel entityModel, Type extensionType, LambdaParameterNameFactory nameFactory, + IResourceFactory resourceFactory, IResourceContextProvider resourceContextProvider) : base(lambdaScope) { ArgumentGuard.NotNull(source, nameof(source)); @@ -67,8 +68,10 @@ public Expression ApplySelect(IDictionary se private Expression CreateLambdaBodyInitializer(IDictionary selectors, ResourceContext resourceContext, LambdaScope lambdaScope, bool lambdaAccessorRequiresTestForNull) { - var propertySelectors = ToPropertySelectors(selectors, resourceContext, lambdaScope.Accessor.Type); - MemberBinding[] propertyAssignments = propertySelectors.Select(selector => CreatePropertyAssignment(selector, lambdaScope)).Cast().ToArray(); + ICollection propertySelectors = ToPropertySelectors(selectors, resourceContext, lambdaScope.Accessor.Type); + + MemberBinding[] propertyAssignments = + propertySelectors.Select(selector => CreatePropertyAssignment(selector, lambdaScope)).Cast().ToArray(); NewExpression newExpression = _resourceFactory.CreateNewExpression(lambdaScope.Accessor.Type); Expression memberInit = Expression.MemberInit(newExpression, propertyAssignments); @@ -89,10 +92,10 @@ private Expression CreateLambdaBodyInitializer(IDictionary ToPropertySelectors(IDictionary resourceFieldSelectors, + private ICollection ToPropertySelectors(IDictionary resourceFieldSelectors, ResourceContext resourceContext, Type elementType) { - Dictionary propertySelectors = new Dictionary(); + var propertySelectors = new Dictionary(); // If a read-only attribute is selected, its value likely depends on another property, so select all resource properties. bool includesReadOnlyAttribute = resourceFieldSelectors.Any(selector => @@ -100,9 +103,10 @@ private ICollection ToPropertySelectors(IDictionary selector.Key is RelationshipAttribute); - foreach (var fieldSelector in resourceFieldSelectors) + foreach (KeyValuePair fieldSelector in resourceFieldSelectors) { var propertySelector = new PropertySelector(fieldSelector.Key, fieldSelector.Value); + if (propertySelector.Property.SetMethod != null) { propertySelectors[propertySelector.Property] = propertySelector; @@ -111,12 +115,13 @@ private ICollection ToPropertySelectors(IDictionary type.ClrType == elementType); + IEntityType entityModel = _entityModel.GetEntityTypes().Single(type => type.ClrType == elementType); IEnumerable entityProperties = entityModel.GetProperties().Where(p => !p.IsShadowProperty()).ToArray(); - foreach (var entityProperty in entityProperties) + foreach (IProperty entityProperty in entityProperties) { var propertySelector = new PropertySelector(entityProperty.PropertyInfo); + if (propertySelector.Property.SetMethod != null) { propertySelectors[propertySelector.Property] = propertySelector; @@ -124,7 +129,7 @@ private ICollection ToPropertySelectors(IDictionary - /// Transforms into and calls. + /// Transforms into and calls. /// [PublicAPI] public class SkipTakeClauseBuilder : QueryClauseBuilder diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs index 25da976355..6ce2a920b2 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs @@ -11,7 +11,8 @@ namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding { /// - /// Transforms into calls. + /// Transforms into + /// calls. /// [PublicAPI] public class WhereClauseBuilder : QueryClauseBuilder @@ -127,8 +128,7 @@ public override Expression VisitLogical(LogicalExpression expression, Type argum throw new InvalidOperationException($"Unknown logical operator '{expression.Operator}'."); } - private static BinaryExpression Compose(Queue argumentQueue, - Func applyOperator) + private static BinaryExpression Compose(Queue argumentQueue, Func applyOperator) { Expression left = argumentQueue.Dequeue(); Expression right = argumentQueue.Dequeue(); @@ -186,7 +186,7 @@ public override Expression VisitComparison(ComparisonExpression expression, Type private Type TryResolveCommonType(QueryExpression left, QueryExpression right) { - var leftType = ResolveFixedType(left); + Type leftType = ResolveFixedType(left); if (TypeHelper.CanContainNull(leftType)) { @@ -198,7 +198,8 @@ private Type TryResolveCommonType(QueryExpression left, QueryExpression right) return typeof(Nullable<>).MakeGenericType(leftType); } - var rightType = TryResolveFixedType(right); + Type rightType = TryResolveFixedType(right); + if (rightType != null && TypeHelper.CanContainNull(rightType)) { return rightType; @@ -209,7 +210,7 @@ private Type TryResolveCommonType(QueryExpression left, QueryExpression right) private Type ResolveFixedType(QueryExpression expression) { - var result = Visit(expression, null); + Expression result = Visit(expression, null); return result.Type; } @@ -233,9 +234,7 @@ private static Expression WrapInConvert(Expression expression, Type targetType) { try { - return targetType != null && expression.Type != targetType - ? Expression.Convert(expression, targetType) - : expression; + return targetType != null && expression.Type != targetType ? Expression.Convert(expression, targetType) : expression; } catch (InvalidOperationException exception) { @@ -250,9 +249,7 @@ public override Expression VisitNullConstant(NullConstantExpression expression, public override Expression VisitLiteralConstant(LiteralConstantExpression expression, Type expressionType) { - var convertedValue = expressionType != null - ? ConvertTextToTargetType(expression.Value, expressionType) - : expression.Value; + object convertedValue = expressionType != null ? ConvertTextToTargetType(expression.Value, expressionType) : expression.Value; return CreateTupleAccessExpressionForConstant(convertedValue, expressionType ?? typeof(string)); } @@ -271,7 +268,7 @@ private static object ConvertTextToTargetType(string text, Type targetType) protected override MemberExpression CreatePropertyExpressionForFieldChain(IReadOnlyCollection chain, Expression source) { - var components = chain.Select(GetPropertyName).ToArray(); + string[] components = chain.Select(GetPropertyName).ToArray(); return CreatePropertyExpressionFromComponents(LambdaScope.Accessor, components); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs index 9f355696d3..35b7f4ac3f 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs @@ -10,7 +10,8 @@ namespace JsonApiDotNetCore.Queries.Internal { /// - /// Takes sparse fieldsets from s and invokes on them. + /// Takes sparse fieldsets from s and invokes + /// on them. /// [PublicAPI] public sealed class SparseFieldSetCache @@ -34,7 +35,7 @@ private static IDictionary> Bui // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var sparseFieldTables = constraintProviders + KeyValuePair[] sparseFieldTables = constraintProviders .SelectMany(provider => provider.GetConstraints()) .Where(constraint => constraint.Scope == null) .Select(constraint => constraint.Expression) @@ -48,7 +49,7 @@ private static IDictionary> Bui var mergedTable = new Dictionary>(); - foreach (var (resourceContext, sparseFieldSet) in sparseFieldTables) + foreach ((ResourceContext resourceContext, SparseFieldSetExpression sparseFieldSet) in sparseFieldTables) { if (!mergedTable.ContainsKey(resourceContext)) { @@ -61,10 +62,9 @@ private static IDictionary> Bui return mergedTable; } - private static void AddSparseFieldsToSet(IReadOnlyCollection sparseFieldsToAdd, - HashSet sparseFieldSet) + private static void AddSparseFieldsToSet(IReadOnlyCollection sparseFieldsToAdd, HashSet sparseFieldSet) { - foreach (var field in sparseFieldsToAdd) + foreach (ResourceFieldAttribute field in sparseFieldsToAdd) { sparseFieldSet.Add(field); } @@ -76,13 +76,13 @@ public IReadOnlyCollection GetSparseFieldSetForQuery(Res if (!_visitedTable.ContainsKey(resourceContext)) { - var inputExpression = _lazySourceTable.Value.ContainsKey(resourceContext) + SparseFieldSetExpression inputExpression = _lazySourceTable.Value.ContainsKey(resourceContext) ? new SparseFieldSetExpression(_lazySourceTable.Value[resourceContext]) : null; - var outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression); + SparseFieldSetExpression outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression); - var outputFields = outputExpression == null + HashSet outputFields = outputExpression == null ? new HashSet() : outputExpression.Fields.ToHashSet(); @@ -96,13 +96,13 @@ public IReadOnlyCollection GetIdAttributeSetForRelationshipQuery( { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); - var idAttribute = resourceContext.Attributes.Single(attr => attr.Property.Name == nameof(Identifiable.Id)); + AttrAttribute idAttribute = resourceContext.Attributes.Single(attr => attr.Property.Name == nameof(Identifiable.Id)); var inputExpression = new SparseFieldSetExpression(idAttribute.AsArray()); // Intentionally not cached, as we are fetching ID only (ignoring any sparse fieldset that came from query string). - var outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression); + SparseFieldSetExpression outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression); - var outputAttributes = outputExpression == null + HashSet outputAttributes = outputExpression == null ? new HashSet() : outputExpression.Fields.OfType().ToHashSet(); @@ -116,14 +116,15 @@ public IReadOnlyCollection GetSparseFieldSetForSerialize if (!_visitedTable.ContainsKey(resourceContext)) { - var inputFields = _lazySourceTable.Value.ContainsKey(resourceContext) + HashSet inputFields = _lazySourceTable.Value.ContainsKey(resourceContext) ? _lazySourceTable.Value[resourceContext] : GetResourceFields(resourceContext); var inputExpression = new SparseFieldSetExpression(inputFields); - var outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression); + SparseFieldSetExpression outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression); HashSet outputFields; + if (outputExpression == null) { outputFields = GetResourceFields(resourceContext); @@ -146,12 +147,12 @@ private HashSet GetResourceFields(ResourceContext resour var fieldSet = new HashSet(); - foreach (var attribute in resourceContext.Attributes.Where(attr => attr.Capabilities.HasFlag(AttrCapabilities.AllowView))) + foreach (AttrAttribute attribute in resourceContext.Attributes.Where(attr => attr.Capabilities.HasFlag(AttrCapabilities.AllowView))) { fieldSet.Add(attribute); } - foreach (var relationship in resourceContext.Relationships) + foreach (RelationshipAttribute relationship in resourceContext.Relationships) { fieldSet.Add(relationship); } diff --git a/src/JsonApiDotNetCore/Queries/PaginationContext.cs b/src/JsonApiDotNetCore/Queries/PaginationContext.cs index 0ca1e25076..beb760555c 100644 --- a/src/JsonApiDotNetCore/Queries/PaginationContext.cs +++ b/src/JsonApiDotNetCore/Queries/PaginationContext.cs @@ -19,8 +19,7 @@ internal sealed class PaginationContext : IPaginationContext public int? TotalResourceCount { get; set; } /// - public int? TotalPageCount => TotalResourceCount == null || PageSize == null - ? null - : (int?) Math.Ceiling((decimal) TotalResourceCount.Value / PageSize.Value); + public int? TotalPageCount => + TotalResourceCount == null || PageSize == null ? null : (int?)Math.Ceiling((decimal)TotalResourceCount.Value / PageSize.Value); } } diff --git a/src/JsonApiDotNetCore/Queries/QueryLayer.cs b/src/JsonApiDotNetCore/Queries/QueryLayer.cs index c50146a329..dfc022f164 100644 --- a/src/JsonApiDotNetCore/Queries/QueryLayer.cs +++ b/src/JsonApiDotNetCore/Queries/QueryLayer.cs @@ -10,7 +10,7 @@ namespace JsonApiDotNetCore.Queries { /// - /// A nested data structure that contains constraints per resource type. + /// A nested data structure that contains constraints per resource type. /// [PublicAPI] public sealed class QueryLayer @@ -69,9 +69,10 @@ private static void WriteLayer(IndentingStringWriter writer, QueryLayer layer, s if (layer.Projection != null && layer.Projection.Any()) { writer.WriteLine(nameof(Projection)); + using (writer.Indent()) { - foreach (var (field, nextLayer) in layer.Projection) + foreach ((ResourceFieldAttribute field, QueryLayer nextLayer) in layer.Projection) { if (nextLayer == null) { diff --git a/src/JsonApiDotNetCore/QueryStrings/IQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/IQueryStringParameterReader.cs index b07399b98c..a7688bf8f7 100644 --- a/src/JsonApiDotNetCore/QueryStrings/IQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/IQueryStringParameterReader.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCore.QueryStrings public interface IQueryStringParameterReader { /// - /// Indicates whether usage of this query string parameter is blocked using on a controller. + /// Indicates whether usage of this query string parameter is blocked using on a controller. /// bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttribute); diff --git a/src/JsonApiDotNetCore/QueryStrings/IQueryStringReader.cs b/src/JsonApiDotNetCore/QueryStrings/IQueryStringReader.cs index c26b14bd1e..39c07ec036 100644 --- a/src/JsonApiDotNetCore/QueryStrings/IQueryStringReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/IQueryStringReader.cs @@ -11,7 +11,7 @@ public interface IQueryStringReader /// Reads and processes the key/value pairs from the request query string. /// /// - /// The if set on the controller that is targeted by the current request. + /// The if set on the controller that is targeted by the current request. /// void ReadAll(DisableQueryStringAttribute disableQueryStringAttribute); } diff --git a/src/JsonApiDotNetCore/QueryStrings/IResourceDefinitionQueryableParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/IResourceDefinitionQueryableParameterReader.cs index 58e1f18d40..baea3e2938 100644 --- a/src/JsonApiDotNetCore/QueryStrings/IResourceDefinitionQueryableParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/IResourceDefinitionQueryableParameterReader.cs @@ -5,8 +5,8 @@ namespace JsonApiDotNetCore.QueryStrings { /// - /// Reads custom query string parameters for which handlers on are registered - /// and produces a set of query constraints from it. + /// Reads custom query string parameters for which handlers on are registered and produces a set of + /// query constraints from it. /// [PublicAPI] public interface IResourceDefinitionQueryableParameterReader : IQueryStringParameterReader, IQueryConstraintProvider diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/DefaultsQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/DefaultsQueryStringParameterReader.cs index bf88cfb9e5..0faa4d6ff4 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/DefaultsQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/DefaultsQueryStringParameterReader.cs @@ -30,7 +30,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); return _options.AllowQueryStringOverrideForSerializerDefaultValueHandling && - !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Defaults); + !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Defaults); } /// @@ -42,10 +42,9 @@ public virtual bool CanRead(string parameterName) /// public virtual void Read(string parameterName, StringValues parameterValue) { - if (!bool.TryParse(parameterValue, out var result)) + if (!bool.TryParse(parameterValue, out bool result)) { - throw new InvalidQueryStringParameterException(parameterName, - "The specified defaults is invalid.", + throw new InvalidQueryStringParameterException(parameterName, "The specified defaults is invalid.", $"The value '{parameterValue}' must be 'true' or 'false'."); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs index 3565f9beb1..c39c5e6f47 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs @@ -25,11 +25,14 @@ public class FilterQueryStringParameterReader : QueryStringParameterReader, IFil private readonly FilterParser _filterParser; private readonly List _filtersInGlobalScope = new List(); - private readonly Dictionary> _filtersPerScope = new Dictionary>(); + + private readonly Dictionary> _filtersPerScope = + new Dictionary>(); + private string _lastParameterName; - public FilterQueryStringParameterReader(IJsonApiRequest request, - IResourceContextProvider resourceContextProvider, IResourceFactory resourceFactory, IJsonApiOptions options) + public FilterQueryStringParameterReader(IJsonApiRequest request, IResourceContextProvider resourceContextProvider, IResourceFactory resourceFactory, + IJsonApiOptions options) : base(request, resourceContextProvider) { ArgumentGuard.NotNull(options, nameof(options)); @@ -53,8 +56,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && - !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Filter); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Filter); } /// @@ -62,7 +64,7 @@ public virtual bool CanRead(string parameterName) { ArgumentGuard.NotNull(parameterName, nameof(parameterName)); - var isNested = parameterName.StartsWith("filter[", StringComparison.Ordinal) && parameterName.EndsWith("]", StringComparison.Ordinal); + bool isNested = parameterName.StartsWith("filter[", StringComparison.Ordinal) && parameterName.EndsWith("]", StringComparison.Ordinal); return parameterName == "filter" || isNested; } @@ -117,7 +119,7 @@ private void ReadSingleValue(string parameterName, string parameterValue) private ResourceFieldChainExpression GetScope(string parameterName) { - var parameterScope = _scopeParser.Parse(parameterName, RequestResource); + QueryStringParameterScopeExpression parameterScope = _scopeParser.Parse(parameterName, RequestResource); if (parameterScope.Scope == null) { @@ -160,13 +162,13 @@ private IEnumerable EnumerateFiltersInScopes() { if (_filtersInGlobalScope.Any()) { - var filter = MergeFilters(_filtersInGlobalScope); + FilterExpression filter = MergeFilters(_filtersInGlobalScope); yield return new ExpressionInScope(null, filter); } - foreach (var (scope, filters) in _filtersPerScope) + foreach ((ResourceFieldChainExpression scope, List filters) in _filtersPerScope) { - var filter = MergeFilters(filters); + FilterExpression filter = MergeFilters(filters); yield return new ExpressionInScope(scope, filter); } } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs index f9dc9d30d7..c0fbb4a60d 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs @@ -34,8 +34,7 @@ protected void ValidateSingleRelationship(RelationshipAttribute relationship, Re { if (!relationship.CanInclude) { - throw new InvalidQueryStringParameterException(_lastParameterName, - "Including the requested relationship is not allowed.", + throw new InvalidQueryStringParameterException(_lastParameterName, "Including the requested relationship is not allowed.", path == relationship.PublicName ? $"Including the relationship '{relationship.PublicName}' on '{resourceContext.PublicName}' is not allowed." : $"Including the relationship '{relationship.PublicName}' in '{path}' on '{resourceContext.PublicName}' is not allowed."); @@ -47,8 +46,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && - !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Include); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Include); } /// @@ -68,8 +66,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) } catch (QueryParseException exception) { - throw new InvalidQueryStringParameterException(parameterName, "The specified include is invalid.", - exception.Message, exception); + throw new InvalidQueryStringParameterException(parameterName, "The specified include is invalid.", exception.Message, exception); } } @@ -81,7 +78,7 @@ private IncludeExpression GetInclude(string parameterValue) /// public virtual IReadOnlyCollection GetConstraints() { - var expressionInScope = _includeExpression != null + ExpressionInScope expressionInScope = _includeExpression != null ? new ExpressionInScope(null, _includeExpression) : new ExpressionInScope(null, IncludeExpression.Empty); diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs index ae8e7a2230..ff4a47f845 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs @@ -31,8 +31,7 @@ public IEnumerable ExtractConditions(string parameterValue) { ArgumentGuard.NotNull(parameterValue, nameof(parameterValue)); - if (parameterValue.StartsWith(ExpressionPrefix, StringComparison.Ordinal) || - parameterValue.StartsWith(InPrefix, StringComparison.Ordinal) || + if (parameterValue.StartsWith(ExpressionPrefix, StringComparison.Ordinal) || parameterValue.StartsWith(InPrefix, StringComparison.Ordinal) || parameterValue.StartsWith(NotInPrefix, StringComparison.Ordinal)) { yield return parameterValue; @@ -57,13 +56,13 @@ public IEnumerable ExtractConditions(string parameterValue) return (parameterName, expression); } - var attributeName = ExtractAttributeName(parameterName); + string attributeName = ExtractAttributeName(parameterName); - foreach (var (prefix, keyword) in PrefixConversionTable) + foreach ((string prefix, string keyword) in PrefixConversionTable) { if (parameterValue.StartsWith(prefix, StringComparison.Ordinal)) { - var value = parameterValue.Substring(prefix.Length); + string value = parameterValue.Substring(prefix.Length); string escapedValue = EscapeQuotes(value); string expression = $"{keyword}({attributeName},'{escapedValue}')"; @@ -73,7 +72,7 @@ public IEnumerable ExtractConditions(string parameterValue) if (parameterValue.StartsWith(NotEqualsPrefix, StringComparison.Ordinal)) { - var value = parameterValue.Substring(NotEqualsPrefix.Length); + string value = parameterValue.Substring(NotEqualsPrefix.Length); string escapedValue = EscapeQuotes(value); string expression = $"{Keywords.Not}({Keywords.Equals}({attributeName},'{escapedValue}'))"; @@ -83,7 +82,7 @@ public IEnumerable ExtractConditions(string parameterValue) if (parameterValue.StartsWith(InPrefix, StringComparison.Ordinal)) { string[] valueParts = parameterValue.Substring(InPrefix.Length).Split(","); - var valueList = "'" + string.Join("','", valueParts) + "'"; + string valueList = "'" + string.Join("','", valueParts) + "'"; string expression = $"{Keywords.Any}({attributeName},{valueList})"; return (OutputParameterName, expression); @@ -92,7 +91,7 @@ public IEnumerable ExtractConditions(string parameterValue) if (parameterValue.StartsWith(NotInPrefix, StringComparison.Ordinal)) { string[] valueParts = parameterValue.Substring(NotInPrefix.Length).Split(","); - var valueList = "'" + string.Join("','", valueParts) + "'"; + string valueList = "'" + string.Join("','", valueParts) + "'"; string expression = $"{Keywords.Not}({Keywords.Any}({attributeName},{valueList}))"; return (OutputParameterName, expression); @@ -120,7 +119,8 @@ public IEnumerable ExtractConditions(string parameterValue) private static string ExtractAttributeName(string parameterName) { - if (parameterName.StartsWith(ParameterNamePrefix, StringComparison.Ordinal) && parameterName.EndsWith(ParameterNameSuffix, StringComparison.Ordinal)) + if (parameterName.StartsWith(ParameterNamePrefix, StringComparison.Ordinal) && + parameterName.EndsWith(ParameterNameSuffix, StringComparison.Ordinal)) { string attributeName = parameterName.Substring(ParameterNamePrefix.Length, parameterName.Length - ParameterNamePrefix.Length - ParameterNameSuffix.Length); diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/NullsQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/NullsQueryStringParameterReader.cs index 98658bd356..3f20c6ba2a 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/NullsQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/NullsQueryStringParameterReader.cs @@ -30,7 +30,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); return _options.AllowQueryStringOverrideForSerializerNullValueHandling && - !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Nulls); + !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Nulls); } /// @@ -42,10 +42,9 @@ public virtual bool CanRead(string parameterName) /// public virtual void Read(string parameterName, StringValues parameterValue) { - if (!bool.TryParse(parameterValue, out var result)) + if (!bool.TryParse(parameterValue, out bool result)) { - throw new InvalidQueryStringParameterException(parameterName, - "The specified nulls is invalid.", + throw new InvalidQueryStringParameterException(parameterName, "The specified nulls is invalid.", $"The value '{parameterValue}' must be 'true' or 'false'."); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs index 0eb3a71b49..bf980f6d40 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs @@ -39,8 +39,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && - !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Page); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Page); } /// @@ -54,7 +53,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) { try { - var constraint = GetPageConstraint(parameterValue); + PaginationQueryStringValueExpression constraint = GetPageConstraint(parameterValue); if (constraint.Elements.Any(element => element.Scope == null)) { @@ -107,8 +106,7 @@ protected virtual void ValidatePageSize(PaginationQueryStringValueExpression con [AssertionMethod] protected virtual void ValidatePageNumber(PaginationQueryStringValueExpression constraint) { - if (_options.MaximumPageNumber != null && - constraint.Elements.Any(element => element.Value > _options.MaximumPageNumber.OneBasedValue)) + if (_options.MaximumPageNumber != null && constraint.Elements.Any(element => element.Value > _options.MaximumPageNumber.OneBasedValue)) { throw new QueryParseException($"Page number cannot be higher than {_options.MaximumPageNumber}."); } @@ -124,16 +122,18 @@ public virtual IReadOnlyCollection GetConstraints() { var context = new PaginationContext(); - foreach (var element in _pageSizeConstraint?.Elements ?? Array.Empty()) + foreach (PaginationElementQueryStringValueExpression element in _pageSizeConstraint?.Elements ?? + Array.Empty()) { - var entry = context.ResolveEntryInScope(element.Scope); + MutablePaginationEntry entry = context.ResolveEntryInScope(element.Scope); entry.PageSize = element.Value == 0 ? null : new PageSize(element.Value); entry.HasSetPageSize = true; } - foreach (var element in _pageNumberConstraint?.Elements ?? Array.Empty()) + foreach (PaginationElementQueryStringValueExpression element in _pageNumberConstraint?.Elements ?? + Array.Empty()) { - var entry = context.ResolveEntryInScope(element.Scope); + MutablePaginationEntry entry = context.ResolveEntryInScope(element.Scope); entry.PageNumber = new PageNumber(element.Value); } @@ -145,7 +145,9 @@ public virtual IReadOnlyCollection GetConstraints() private sealed class PaginationContext { private readonly MutablePaginationEntry _globalScope = new MutablePaginationEntry(); - private readonly Dictionary _nestedScopes = new Dictionary(); + + private readonly Dictionary _nestedScopes = + new Dictionary(); public MutablePaginationEntry ResolveEntryInScope(ResourceFieldChainExpression scope) { @@ -166,7 +168,7 @@ public void ApplyOptions(IJsonApiOptions options) { ApplyOptionsInEntry(_globalScope, options); - foreach (var (_, entry) in _nestedScopes) + foreach ((ResourceFieldChainExpression _, MutablePaginationEntry entry) in _nestedScopes) { ApplyOptionsInEntry(entry, options); } @@ -191,7 +193,7 @@ private IEnumerable EnumerateExpressionsInScope() { yield return new ExpressionInScope(null, new PaginationExpression(_globalScope.PageNumber, _globalScope.PageSize)); - foreach (var (scope, entry) in _nestedScopes) + foreach ((ResourceFieldChainExpression scope, MutablePaginationEntry entry) in _nestedScopes) { yield return new ExpressionInScope(scope, new PaginationExpression(entry.PageNumber, entry.PageSize)); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs index 496c179fcc..79c698627e 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; @@ -33,8 +34,8 @@ protected ResourceContext GetResourceContextForScope(ResourceFieldChainExpressio return RequestResource; } - var lastField = scope.Fields.Last(); - var type = lastField is RelationshipAttribute relationship ? relationship.RightType : lastField.Property.PropertyType; + ResourceFieldAttribute lastField = scope.Fields.Last(); + Type type = lastField is RelationshipAttribute relationship ? relationship.RightType : lastField.Property.PropertyType; return _resourceContextProvider.GetResourceContext(type); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs index b7528624d2..ce7ac0288d 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs @@ -5,6 +5,7 @@ using JsonApiDotNetCore.Controllers.Annotations; using JsonApiDotNetCore.Errors; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace JsonApiDotNetCore.QueryStrings.Internal { @@ -34,22 +35,21 @@ public QueryStringReader(IJsonApiOptions options, IRequestQueryStringAccessor qu /// public virtual void ReadAll(DisableQueryStringAttribute disableQueryStringAttribute) { - var disableQueryStringAttributeNotNull = disableQueryStringAttribute ?? DisableQueryStringAttribute.Empty; + DisableQueryStringAttribute disableQueryStringAttributeNotNull = disableQueryStringAttribute ?? DisableQueryStringAttribute.Empty; - foreach (var (parameterName, parameterValue) in _queryStringAccessor.Query) + foreach ((string parameterName, StringValues parameterValue) in _queryStringAccessor.Query) { if (string.IsNullOrEmpty(parameterValue)) { - throw new InvalidQueryStringParameterException(parameterName, - "Missing query string parameter value.", + throw new InvalidQueryStringParameterException(parameterName, "Missing query string parameter value.", $"Missing value for '{parameterName}' query string parameter."); } - var reader = _parameterReaders.FirstOrDefault(r => r.CanRead(parameterName)); + IQueryStringParameterReader reader = _parameterReaders.FirstOrDefault(r => r.CanRead(parameterName)); + if (reader != null) { - _logger.LogDebug( - $"Query string parameter '{parameterName}' with value '{parameterValue}' was accepted by {reader.GetType().Name}."); + _logger.LogDebug($"Query string parameter '{parameterName}' with value '{parameterValue}' was accepted by {reader.GetType().Name}."); if (!reader.IsEnabled(disableQueryStringAttributeNotNull)) { diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/ResourceDefinitionQueryableParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/ResourceDefinitionQueryableParameterReader.cs index 868551fa26..b73777855f 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/ResourceDefinitionQueryableParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/ResourceDefinitionQueryableParameterReader.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.Controllers.Annotations; @@ -41,27 +42,26 @@ public virtual bool CanRead(string parameterName) return false; } - var queryableHandler = GetQueryableHandler(parameterName); + object queryableHandler = GetQueryableHandler(parameterName); return queryableHandler != null; } /// public virtual void Read(string parameterName, StringValues parameterValue) { - var queryableHandler = GetQueryableHandler(parameterName); + object queryableHandler = GetQueryableHandler(parameterName); var expressionInScope = new ExpressionInScope(null, new QueryableHandlerExpression(queryableHandler, parameterValue)); _constraints.Add(expressionInScope); } private object GetQueryableHandler(string parameterName) { - var resourceType = _request.PrimaryResource.ResourceType; - var handler = _resourceDefinitionAccessor.GetQueryableHandlerForQueryStringParameter(resourceType, parameterName); + Type resourceType = _request.PrimaryResource.ResourceType; + object handler = _resourceDefinitionAccessor.GetQueryableHandlerForQueryStringParameter(resourceType, parameterName); if (handler != null && _request.Kind != EndpointKind.Primary) { - throw new InvalidQueryStringParameterException(parameterName, - "Custom query string parameters cannot be used on nested resource endpoints.", + throw new InvalidQueryStringParameterException(parameterName, "Custom query string parameters cannot be used on nested resource endpoints.", $"Query string parameter '{parameterName}' cannot be used on a nested resource endpoint."); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs index e9ca858acf..0271d3f95d 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs @@ -42,8 +42,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && - !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Sort); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Sort); } /// @@ -51,7 +50,7 @@ public virtual bool CanRead(string parameterName) { ArgumentGuard.NotNull(parameterName, nameof(parameterName)); - var isNested = parameterName.StartsWith("sort[", StringComparison.Ordinal) && parameterName.EndsWith("]", StringComparison.Ordinal); + bool isNested = parameterName.StartsWith("sort[", StringComparison.Ordinal) && parameterName.EndsWith("]", StringComparison.Ordinal); return parameterName == "sort" || isNested; } @@ -76,7 +75,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) private ResourceFieldChainExpression GetScope(string parameterName) { - var parameterScope = _scopeParser.Parse(parameterName, RequestResource); + QueryStringParameterScopeExpression parameterScope = _scopeParser.Parse(parameterName, RequestResource); if (parameterScope.Scope == null) { diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs index b1cc88afdc..6a0cbf9244 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs @@ -43,8 +43,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && - !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Fields); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Fields); } /// @@ -62,15 +61,14 @@ public virtual void Read(string parameterName, StringValues parameterValue) try { - var targetResource = GetSparseFieldType(parameterName); - var sparseFieldSet = GetSparseFieldSet(parameterValue, targetResource); + ResourceContext targetResource = GetSparseFieldType(parameterName); + SparseFieldSetExpression sparseFieldSet = GetSparseFieldSet(parameterValue, targetResource); _sparseFieldTable[targetResource] = sparseFieldSet; } catch (QueryParseException exception) { - throw new InvalidQueryStringParameterException(parameterName, "The specified fieldset is invalid.", - exception.Message, exception); + throw new InvalidQueryStringParameterException(parameterName, "The specified fieldset is invalid.", exception.Message, exception); } } diff --git a/src/JsonApiDotNetCore/QueryStrings/StandardQueryStringParameters.cs b/src/JsonApiDotNetCore/QueryStrings/StandardQueryStringParameters.cs index f488e823f6..521a9af37b 100644 --- a/src/JsonApiDotNetCore/QueryStrings/StandardQueryStringParameters.cs +++ b/src/JsonApiDotNetCore/QueryStrings/StandardQueryStringParameters.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.QueryStrings { /// - /// Lists query string parameters used by . + /// Lists query string parameters used by . /// [Flags] public enum StandardQueryStringParameters diff --git a/src/JsonApiDotNetCore/Repositories/DbContextExtensions.cs b/src/JsonApiDotNetCore/Repositories/DbContextExtensions.cs index 5adfbc2c8f..a6d95560a2 100644 --- a/src/JsonApiDotNetCore/Repositories/DbContextExtensions.cs +++ b/src/JsonApiDotNetCore/Repositories/DbContextExtensions.cs @@ -12,7 +12,7 @@ namespace JsonApiDotNetCore.Repositories public static class DbContextExtensions { /// - /// If not already tracked, attaches the specified resource to the change tracker in state. + /// If not already tracked, attaches the specified resource to the change tracker in state. /// public static IIdentifiable GetTrackedOrAttach(this DbContext dbContext, IIdentifiable resource) { @@ -20,6 +20,7 @@ public static IIdentifiable GetTrackedOrAttach(this DbContext dbContext, IIdenti ArgumentGuard.NotNull(resource, nameof(resource)); var trackedIdentifiable = (IIdentifiable)dbContext.GetTrackedIdentifiable(resource); + if (trackedIdentifiable == null) { dbContext.Entry(resource).State = EntityState.Unchanged; @@ -30,25 +31,24 @@ public static IIdentifiable GetTrackedOrAttach(this DbContext dbContext, IIdenti } /// - /// Searches the change tracker for an entity that matches the type and ID of . + /// Searches the change tracker for an entity that matches the type and ID of . /// public static object GetTrackedIdentifiable(this DbContext dbContext, IIdentifiable identifiable) { ArgumentGuard.NotNull(dbContext, nameof(dbContext)); ArgumentGuard.NotNull(identifiable, nameof(identifiable)); - var resourceType = identifiable.GetType(); + Type resourceType = identifiable.GetType(); string stringId = identifiable.StringId; - var entityEntry = dbContext.ChangeTracker.Entries() - .FirstOrDefault(entry => IsResource(entry, resourceType, stringId)); + EntityEntry entityEntry = dbContext.ChangeTracker.Entries().FirstOrDefault(entry => IsResource(entry, resourceType, stringId)); return entityEntry?.Entity; } private static bool IsResource(EntityEntry entry, Type resourceType, string stringId) { - return entry.Entity.GetType() == resourceType && ((IIdentifiable) entry.Entity).StringId == stringId; + return entry.Entity.GetType() == resourceType && ((IIdentifiable)entry.Entity).StringId == stringId; } /// diff --git a/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs b/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs index 9b8f1327ce..4e1c7b4552 100644 --- a/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs +++ b/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs @@ -17,8 +17,14 @@ public DbContextResolver(TDbContext context) _context = context; } - public DbContext GetContext() => _context; - - public TDbContext GetTypedContext() => _context; + public DbContext GetContext() + { + return _context; + } + + public TDbContext GetTypedContext() + { + return _context; + } } } diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index a87155cda1..b305938bb0 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -37,13 +38,8 @@ public class EntityFrameworkCoreRepository : IResourceRepository /// public virtual Guid? TransactionId => _dbContext.Database.CurrentTransaction?.TransactionId; - public EntityFrameworkCoreRepository( - ITargetedFields targetedFields, - IDbContextResolver contextResolver, - IResourceGraph resourceGraph, - IResourceFactory resourceFactory, - IEnumerable constraintProviders, - ILoggerFactory loggerFactory) + public EntityFrameworkCoreRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) { ArgumentGuard.NotNull(contextResolver, nameof(contextResolver)); ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); @@ -63,7 +59,10 @@ public EntityFrameworkCoreRepository( /// public virtual async Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {layer}); + _traceWriter.LogMethodStart(new + { + layer + }); ArgumentGuard.NotNull(layer, nameof(layer)); @@ -74,9 +73,13 @@ public virtual async Task> GetAsync(QueryLayer la /// public virtual async Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {topFilter}); + _traceWriter.LogMethodStart(new + { + topFilter + }); + + ResourceContext resourceContext = _resourceGraph.GetResourceContext(); - var resourceContext = _resourceGraph.GetResourceContext(); var layer = new QueryLayer(resourceContext) { Filter = topFilter @@ -88,11 +91,15 @@ public virtual async Task CountAsync(FilterExpression topFilter, Cancellati protected virtual IQueryable ApplyQueryLayer(QueryLayer layer) { - _traceWriter.LogMethodStart(new {layer}); + _traceWriter.LogMethodStart(new + { + layer + }); ArgumentGuard.NotNull(layer, nameof(layer)); QueryLayer rewrittenLayer = layer; + if (EntityFrameworkCoreSupport.Version.Major < 5) { var writer = new MemoryLeakDetectionBugRewriter(); @@ -104,7 +111,7 @@ protected virtual IQueryable ApplyQueryLayer(QueryLayer layer) // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var queryableHandlers = _constraintProviders + QueryableHandlerExpression[] queryableHandlers = _constraintProviders .SelectMany(provider => provider.GetConstraints()) .Where(expressionInScope => expressionInScope.Scope == null) .Select(expressionInScope => expressionInScope.Expression) @@ -114,15 +121,17 @@ protected virtual IQueryable ApplyQueryLayer(QueryLayer layer) // @formatter:keep_existing_linebreaks restore // @formatter:wrap_chained_method_calls restore - foreach (var queryableHandler in queryableHandlers) + foreach (QueryableHandlerExpression queryableHandler in queryableHandlers) { source = queryableHandler.Apply(source); } var nameFactory = new LambdaParameterNameFactory(); - var builder = new QueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, _resourceGraph, _dbContext.Model); - var expression = builder.ApplyQuery(rewrittenLayer); + var builder = new QueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, _resourceGraph, + _dbContext.Model); + + Expression expression = builder.ApplyQuery(rewrittenLayer); return source.Provider.CreateQuery(expression); } @@ -143,25 +152,29 @@ public virtual Task GetForCreateAsync(TId id, CancellationToken cance /// public virtual async Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {resourceFromRequest, resourceForDatabase}); + _traceWriter.LogMethodStart(new + { + resourceFromRequest, + resourceForDatabase + }); ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); ArgumentGuard.NotNull(resourceForDatabase, nameof(resourceForDatabase)); using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); - foreach (var relationship in _targetedFields.Relationships) + foreach (RelationshipAttribute relationship in _targetedFields.Relationships) { - var rightResources = relationship.GetValue(resourceFromRequest); + object rightResources = relationship.GetValue(resourceFromRequest); await UpdateRelationshipAsync(relationship, resourceForDatabase, rightResources, collector, cancellationToken); } - foreach (var attribute in _targetedFields.Attributes) + foreach (AttrAttribute attribute in _targetedFields.Attributes) { attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest)); } - var dbSet = _dbContext.Set(); + DbSet dbSet = _dbContext.Set(); await dbSet.AddAsync(resourceForDatabase, cancellationToken); await SaveChangesAsync(cancellationToken); @@ -170,30 +183,34 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r /// public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) { - var resources = await GetAsync(queryLayer, cancellationToken); + IReadOnlyCollection resources = await GetAsync(queryLayer, cancellationToken); return resources.FirstOrDefault(); } /// public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {resourceFromRequest, resourceFromDatabase}); + _traceWriter.LogMethodStart(new + { + resourceFromRequest, + resourceFromDatabase + }); ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); ArgumentGuard.NotNull(resourceFromDatabase, nameof(resourceFromDatabase)); using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); - foreach (var relationship in _targetedFields.Relationships) + foreach (RelationshipAttribute relationship in _targetedFields.Relationships) { - var rightResources = relationship.GetValue(resourceFromRequest); + object rightResources = relationship.GetValue(resourceFromRequest); AssertIsNotClearingRequiredRelationship(relationship, resourceFromDatabase, rightResources); await UpdateRelationshipAsync(relationship, resourceFromDatabase, rightResources, collector, cancellationToken); } - foreach (var attribute in _targetedFields.Attributes) + foreach (AttrAttribute attribute in _targetedFields.Attributes) { attribute.SetValue(resourceFromDatabase, attribute.GetValue(resourceFromRequest)); } @@ -207,17 +224,17 @@ protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute rel if (!(relationship is HasManyThroughAttribute)) { - var navigation = TryGetNavigation(relationship); + INavigation navigation = TryGetNavigation(relationship); relationshipIsRequired = navigation?.ForeignKey?.IsRequired ?? false; } - var relationshipIsBeingCleared = relationship is HasOneAttribute + bool relationshipIsBeingCleared = relationship is HasOneAttribute ? rightValue == null : IsToManyRelationshipBeingCleared(relationship, leftResource, rightValue); - + if (relationshipIsRequired && relationshipIsBeingCleared) { - var resourceType = _resourceGraph.GetResourceContext().PublicName; + string resourceType = _resourceGraph.GetResourceContext().PublicName; throw new CannotClearRequiredRelationshipException(relationship.PublicName, leftResource.StringId, resourceType); } } @@ -226,8 +243,8 @@ private static bool IsToManyRelationshipBeingCleared(RelationshipAttribute relat { ICollection newRightResourceIds = TypeHelper.ExtractResources(valueToAssign); - var existingRightValue = relationship.GetValue(leftResource); - var existingRightResourceIds = TypeHelper.ExtractResources(existingRightValue).ToHashSet(IdentifiableComparer.Instance); + object existingRightValue = relationship.GetValue(leftResource); + HashSet existingRightResourceIds = TypeHelper.ExtractResources(existingRightValue).ToHashSet(IdentifiableComparer.Instance); existingRightResourceIds.ExceptWith(newRightResourceIds); @@ -237,18 +254,21 @@ private static bool IsToManyRelationshipBeingCleared(RelationshipAttribute relat /// public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id}); + _traceWriter.LogMethodStart(new + { + id + }); using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); - var resource = collector.CreateForId(id); + TResource resource = collector.CreateForId(id); - foreach (var relationship in _resourceGraph.GetRelationships()) + foreach (RelationshipAttribute relationship in _resourceGraph.GetRelationships()) { // Loads the data of the relationship, if in EF Core it is configured in such a way that loading the related // entities into memory is required for successfully executing the selected deletion behavior. if (RequiresLoadOfRelationshipForDeletion(relationship)) { - var navigation = GetNavigationEntry(resource, relationship); + NavigationEntry navigation = GetNavigationEntry(resource, relationship); await navigation.LoadAsync(cancellationToken); } } @@ -281,7 +301,7 @@ private NavigationEntry GetNavigationEntry(TResource resource, RelationshipAttri private bool RequiresLoadOfRelationshipForDeletion(RelationshipAttribute relationship) { - var navigation = TryGetNavigation(relationship); + INavigation navigation = TryGetNavigation(relationship); bool isClearOfForeignKeyRequired = navigation?.ForeignKey.DeleteBehavior == DeleteBehavior.ClientSetNull; bool hasForeignKeyAtLeftSide = HasForeignKeyAtLeftSide(relationship); @@ -291,7 +311,7 @@ private bool RequiresLoadOfRelationshipForDeletion(RelationshipAttribute relatio private INavigation TryGetNavigation(RelationshipAttribute relationship) { - var entityType = _dbContext.Model.FindEntityType(typeof(TResource)); + IEntityType entityType = _dbContext.Model.FindEntityType(typeof(TResource)); return entityType?.FindNavigation(relationship.Property.Name); } @@ -299,7 +319,7 @@ private bool HasForeignKeyAtLeftSide(RelationshipAttribute relationship) { if (relationship is HasOneAttribute) { - var navigation = TryGetNavigation(relationship); + INavigation navigation = TryGetNavigation(relationship); return navigation?.IsDependentToPrincipal() ?? false; } @@ -309,9 +329,13 @@ private bool HasForeignKeyAtLeftSide(RelationshipAttribute relationship) /// public virtual async Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {primaryResource, secondaryResourceIds}); + _traceWriter.LogMethodStart(new + { + primaryResource, + secondaryResourceIds + }); - var relationship = _targetedFields.Relationships.Single(); + RelationshipAttribute relationship = _targetedFields.Relationships.Single(); AssertIsNotClearingRequiredRelationship(relationship, primaryResource, secondaryResourceIds); @@ -324,16 +348,20 @@ public virtual async Task SetRelationshipAsync(TResource primaryResource, object /// public virtual async Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {primaryId, secondaryResourceIds}); + _traceWriter.LogMethodStart(new + { + primaryId, + secondaryResourceIds + }); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); - var relationship = _targetedFields.Relationships.Single(); + RelationshipAttribute relationship = _targetedFields.Relationships.Single(); if (secondaryResourceIds.Any()) { using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); - var primaryResource = collector.CreateForId(primaryId); + TResource primaryResource = collector.CreateForId(primaryId); await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIds, collector, cancellationToken); @@ -342,17 +370,22 @@ public virtual async Task AddToToManyRelationshipAsync(TId primaryId, ISet - public virtual async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) + public virtual async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, + CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {primaryResource, secondaryResourceIds}); + _traceWriter.LogMethodStart(new + { + primaryResource, + secondaryResourceIds + }); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); var relationship = (HasManyAttribute)_targetedFields.Relationships.Single(); - var rightValue = relationship.GetValue(primaryResource); + object rightValue = relationship.GetValue(primaryResource); - var rightResourceIds= TypeHelper.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance); + HashSet rightResourceIds = TypeHelper.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance); rightResourceIds.ExceptWith(secondaryResourceIds); AssertIsNotClearingRequiredRelationship(relationship, primaryResource, rightResourceIds); @@ -363,15 +396,15 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TResource primaryRes await SaveChangesAsync(cancellationToken); } - protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship, TResource leftResource, - object valueToAssign, PlaceholderResourceCollector collector, CancellationToken cancellationToken) + protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship, TResource leftResource, object valueToAssign, + PlaceholderResourceCollector collector, CancellationToken cancellationToken) { - var trackedValueToAssign = EnsureRelationshipValueToAssignIsTracked(valueToAssign, relationship.Property.PropertyType, collector); + object trackedValueToAssign = EnsureRelationshipValueToAssignIsTracked(valueToAssign, relationship.Property.PropertyType, collector); if (RequireLoadOfInverseRelationship(relationship, trackedValueToAssign)) { - var entityEntry = _dbContext.Entry(trackedValueToAssign); - var inversePropertyName = relationship.InverseNavigationProperty.Name; + EntityEntry entityEntry = _dbContext.Entry(trackedValueToAssign); + string inversePropertyName = relationship.InverseNavigationProperty.Name; await entityEntry.Reference(inversePropertyName).LoadAsync(cancellationToken); } @@ -379,19 +412,18 @@ protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship, relationship.SetValue(leftResource, trackedValueToAssign); } - private object EnsureRelationshipValueToAssignIsTracked(object rightValue, Type relationshipPropertyType, - PlaceholderResourceCollector collector) + private object EnsureRelationshipValueToAssignIsTracked(object rightValue, Type relationshipPropertyType, PlaceholderResourceCollector collector) { if (rightValue == null) { return null; } - var rightResources = TypeHelper.ExtractResources(rightValue); - var rightResourcesTracked = rightResources.Select(collector.CaptureExisting).ToArray(); + ICollection rightResources = TypeHelper.ExtractResources(rightValue); + IIdentifiable[] rightResourcesTracked = rightResources.Select(collector.CaptureExisting).ToArray(); return rightValue is IEnumerable - ? (object) TypeHelper.CopyToTypedCollection(rightResourcesTracked, relationshipPropertyType) + ? (object)TypeHelper.CopyToTypedCollection(rightResourcesTracked, relationshipPropertyType) : rightResourcesTracked.Single(); } @@ -405,7 +437,7 @@ private static bool IsOneToOneRelationship(RelationshipAttribute relationship) { if (relationship is HasOneAttribute hasOneRelationship) { - var elementType = TypeHelper.TryGetCollectionElementType(hasOneRelationship.InverseNavigationProperty.PropertyType); + Type elementType = TypeHelper.TryGetCollectionElementType(hasOneRelationship.InverseNavigationProperty.PropertyType); return elementType == null; } @@ -439,13 +471,8 @@ protected virtual async Task SaveChangesAsync(CancellationToken cancellationToke public class EntityFrameworkCoreRepository : EntityFrameworkCoreRepository, IResourceRepository where TResource : class, IIdentifiable { - public EntityFrameworkCoreRepository( - ITargetedFields targetedFields, - IDbContextResolver contextResolver, - IResourceGraph resourceGraph, - IResourceFactory resourceFactory, - IEnumerable constraintProviders, - ILoggerFactory loggerFactory) + public EntityFrameworkCoreRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) { } diff --git a/src/JsonApiDotNetCore/Repositories/IDbContextResolver.cs b/src/JsonApiDotNetCore/Repositories/IDbContextResolver.cs index 693724ed90..0a38f2dfcc 100644 --- a/src/JsonApiDotNetCore/Repositories/IDbContextResolver.cs +++ b/src/JsonApiDotNetCore/Repositories/IDbContextResolver.cs @@ -3,7 +3,7 @@ namespace JsonApiDotNetCore.Repositories { /// - /// Provides a method to resolve a . + /// Provides a method to resolve a . /// public interface IDbContextResolver { diff --git a/src/JsonApiDotNetCore/Repositories/IRepositorySupportsTransaction.cs b/src/JsonApiDotNetCore/Repositories/IRepositorySupportsTransaction.cs index fb1f026d31..15e65f3bda 100644 --- a/src/JsonApiDotNetCore/Repositories/IRepositorySupportsTransaction.cs +++ b/src/JsonApiDotNetCore/Repositories/IRepositorySupportsTransaction.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Repositories { /// - /// Used to indicate that an supports execution inside a transaction. + /// Used to indicate that an supports execution inside a transaction. /// [PublicAPI] public interface IRepositorySupportsTransaction diff --git a/src/JsonApiDotNetCore/Repositories/IResourceReadRepository.cs b/src/JsonApiDotNetCore/Repositories/IResourceReadRepository.cs index ec373094db..a75d95c213 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceReadRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceReadRepository.cs @@ -9,16 +9,20 @@ namespace JsonApiDotNetCore.Repositories { /// - public interface IResourceReadRepository - : IResourceReadRepository - where TResource : class, IIdentifiable - { } + public interface IResourceReadRepository : IResourceReadRepository + where TResource : class, IIdentifiable + { + } /// /// Groups read operations. /// - /// The resource type. - /// The resource identifier type. + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// [PublicAPI] public interface IResourceReadRepository where TResource : class, IIdentifiable diff --git a/src/JsonApiDotNetCore/Repositories/IResourceRepository.cs b/src/JsonApiDotNetCore/Repositories/IResourceRepository.cs index 266672cf77..d43e355f06 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceRepository.cs @@ -6,7 +6,9 @@ namespace JsonApiDotNetCore.Repositories /// /// Represents the foundational Resource Repository layer in the JsonApiDotNetCore architecture that provides data access to an underlying store. /// - /// The resource type. + /// + /// The resource type. + /// [PublicAPI] public interface IResourceRepository : IResourceRepository, IResourceReadRepository, IResourceWriteRepository @@ -17,10 +19,13 @@ public interface IResourceRepository /// /// Represents the foundational Resource Repository layer in the JsonApiDotNetCore architecture that provides data access to an underlying store. /// - /// The resource type. - /// The resource identifier type. - public interface IResourceRepository - : IResourceReadRepository, IResourceWriteRepository + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// + public interface IResourceRepository : IResourceReadRepository, IResourceWriteRepository where TResource : class, IIdentifiable { } diff --git a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs index 93f9640038..607512c242 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs @@ -9,73 +9,74 @@ namespace JsonApiDotNetCore.Repositories { /// - /// Retrieves an instance from the D/I container and invokes a method on it. + /// Retrieves an instance from the D/I container and invokes a method on it. /// public interface IResourceRepositoryAccessor { /// - /// Invokes . + /// Invokes . /// Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// - /// Invokes for the specified resource type. + /// Invokes for the specified resource type. /// Task> GetAsync(Type resourceType, QueryLayer layer, CancellationToken cancellationToken); /// - /// Invokes for the specified resource type. + /// Invokes for the specified resource type. /// Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// - /// Invokes . + /// Invokes . /// Task GetForCreateAsync(TId id, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// - /// Invokes . + /// Invokes . /// Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// - /// Invokes . + /// Invokes . /// Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// - /// Invokes . + /// Invokes . /// Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// - /// Invokes for the specified resource type. + /// Invokes for the specified resource type. /// Task DeleteAsync(TId id, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// - /// Invokes . + /// Invokes . /// Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// - /// Invokes for the specified resource type. + /// Invokes for the specified resource type. /// Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// - /// Invokes . + /// Invokes . /// - Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) + Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, + CancellationToken cancellationToken) where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs b/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs index 35f7ab1459..049e02ce8c 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs @@ -8,22 +8,26 @@ namespace JsonApiDotNetCore.Repositories { /// - public interface IResourceWriteRepository - : IResourceWriteRepository + public interface IResourceWriteRepository : IResourceWriteRepository where TResource : class, IIdentifiable - { } + { + } /// /// Groups write operations. /// - /// The resource type. - /// The resource identifier type. + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// [PublicAPI] public interface IResourceWriteRepository where TResource : class, IIdentifiable { /// - /// Creates a new resource instance, in preparation for . + /// Creates a new resource instance, in preparation for . /// /// /// This method can be overridden to assign resource-specific required relationships. @@ -36,7 +40,7 @@ public interface IResourceWriteRepository Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase, CancellationToken cancellationToken); /// - /// Retrieves a resource with all of its attributes, including the set of targeted relationships, in preparation for . + /// Retrieves a resource with all of its attributes, including the set of targeted relationships, in preparation for . /// Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken); diff --git a/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs b/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs index 0b7509fc28..9d6c58a0ca 100644 --- a/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs +++ b/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs @@ -8,12 +8,12 @@ namespace JsonApiDotNetCore.Repositories { /// - /// Removes projections from a when its resource type uses injected parameters, - /// as a workaround for EF Core bug https://github.com/dotnet/efcore/issues/20502, which exists in versions below v5. + /// Removes projections from a when its resource type uses injected parameters, as a workaround for EF Core bug + /// https://github.com/dotnet/efcore/issues/20502, which exists in versions below v5. /// /// - /// Note that by using this workaround, nested filtering, paging and sorting all remain broken in EF Core 3.1 when using injected parameters in resources. - /// But at least it enables simple top-level queries to succeed without an exception. + /// Note that by using this workaround, nested filtering, paging and sorting all remain broken in EF Core 3.1 when using injected parameters in + /// resources. But at least it enables simple top-level queries to succeed without an exception. /// [PublicAPI] public sealed class MemoryLeakDetectionBugRewriter @@ -35,7 +35,8 @@ private QueryLayer RewriteLayer(QueryLayer queryLayer) return queryLayer; } - private IDictionary RewriteProjection(IDictionary projection, ResourceContext resourceContext) + private IDictionary RewriteProjection(IDictionary projection, + ResourceContext resourceContext) { if (projection == null || projection.Count == 0) { @@ -43,9 +44,10 @@ private IDictionary RewriteProjection(IDicti } var newProjection = new Dictionary(); - foreach (var (field, layer) in projection) + + foreach ((ResourceFieldAttribute field, QueryLayer layer) in projection) { - var newLayer = RewriteLayer(layer); + QueryLayer newLayer = RewriteLayer(layer); newProjection.Add(field, newLayer); } diff --git a/src/JsonApiDotNetCore/Repositories/PlaceholderResourceCollector.cs b/src/JsonApiDotNetCore/Repositories/PlaceholderResourceCollector.cs index 3dfa3c9d99..d28ab4a6c0 100644 --- a/src/JsonApiDotNetCore/Repositories/PlaceholderResourceCollector.cs +++ b/src/JsonApiDotNetCore/Repositories/PlaceholderResourceCollector.cs @@ -7,9 +7,9 @@ namespace JsonApiDotNetCore.Repositories { /// - /// Creates placeholder resource instances (with only their ID property set), which are added to the - /// Entity Framework Core change tracker so they can be used in relationship updates without fetching the resource. - /// On disposal, the created placeholders are detached, leaving the change tracker in a clean state for reuse. + /// Creates placeholder resource instances (with only their ID property set), which are added to the Entity Framework Core change tracker so they can be + /// used in relationship updates without fetching the resource. On disposal, the created placeholders are detached, leaving the change tracker in a clean + /// state for reuse. /// [PublicAPI] public sealed class PlaceholderResourceCollector : IDisposable @@ -28,8 +28,8 @@ public PlaceholderResourceCollector(IResourceFactory resourceFactory, DbContext } /// - /// Creates a new placeholder resource, assigns the specified ID, adds it to the change tracker - /// in state and registers it for detachment. + /// Creates a new placeholder resource, assigns the specified ID, adds it to the change tracker in state and + /// registers it for detachment. /// public TResource CreateForId(TId id) where TResource : IIdentifiable @@ -41,13 +41,13 @@ public TResource CreateForId(TId id) } /// - /// Takes an existing placeholder resource, adds it to the change tracker - /// in state and registers it for detachment. + /// Takes an existing placeholder resource, adds it to the change tracker in state and registers it for detachment. /// public TResource CaptureExisting(TResource placeholderResource) where TResource : IIdentifiable { - var resourceTracked = (TResource) _dbContext.GetTrackedOrAttach(placeholderResource); + var resourceTracked = (TResource)_dbContext.GetTrackedOrAttach(placeholderResource); + if (ReferenceEquals(resourceTracked, placeholderResource)) { _resources.Add(resourceTracked); @@ -68,7 +68,7 @@ public void Dispose() private void Detach(IEnumerable resources) { - foreach (var resource in resources) + foreach (object resource in resources) { _dbContext.Entry(resource).State = EntityState.Detached; } diff --git a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs index 8dfe3971e3..887ebb06a6 100644 --- a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs @@ -37,7 +37,7 @@ public async Task> GetAsync(QueryLayer where TResource : class, IIdentifiable { dynamic repository = ResolveReadRepository(typeof(TResource)); - return (IReadOnlyCollection) await repository.GetAsync(layer, cancellationToken); + return (IReadOnlyCollection)await repository.GetAsync(layer, cancellationToken); } /// @@ -46,7 +46,7 @@ public async Task> GetAsync(Type resourceType ArgumentGuard.NotNull(resourceType, nameof(resourceType)); dynamic repository = ResolveReadRepository(resourceType); - return (IReadOnlyCollection) await repository.GetAsync(layer, cancellationToken); + return (IReadOnlyCollection)await repository.GetAsync(layer, cancellationToken); } /// @@ -54,7 +54,7 @@ public async Task CountAsync(FilterExpression topFilter, Cancell where TResource : class, IIdentifiable { dynamic repository = ResolveReadRepository(typeof(TResource)); - return (int) await repository.CountAsync(topFilter, cancellationToken); + return (int)await repository.CountAsync(topFilter, cancellationToken); } /// @@ -106,7 +106,8 @@ public async Task SetRelationshipAsync(TResource primaryResource, obj } /// - public async Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + public async Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, + CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); @@ -114,7 +115,8 @@ public async Task AddToToManyRelationshipAsync(TId primaryId, IS } /// - public async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) + public async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, + CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); @@ -123,12 +125,12 @@ public async Task RemoveFromToManyRelationshipAsync(TResource primary protected virtual object ResolveReadRepository(Type resourceType) { - var resourceContext = _resourceContextProvider.GetResourceContext(resourceType); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType); if (resourceContext.IdentityType == typeof(int)) { - var intRepositoryType = typeof(IResourceReadRepository<>).MakeGenericType(resourceContext.ResourceType); - var intRepository = _serviceProvider.GetService(intRepositoryType); + Type intRepositoryType = typeof(IResourceReadRepository<>).MakeGenericType(resourceContext.ResourceType); + object intRepository = _serviceProvider.GetService(intRepositoryType); if (intRepository != null) { @@ -136,19 +138,19 @@ protected virtual object ResolveReadRepository(Type resourceType) } } - var resourceDefinitionType = typeof(IResourceReadRepository<,>).MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType); + Type resourceDefinitionType = typeof(IResourceReadRepository<,>).MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType); return _serviceProvider.GetRequiredService(resourceDefinitionType); } private object GetWriteRepository(Type resourceType) { - var writeRepository = ResolveWriteRepository(resourceType); + object writeRepository = ResolveWriteRepository(resourceType); if (_request.TransactionId != null) { if (!(writeRepository is IRepositorySupportsTransaction repository)) { - var resourceContext = _resourceContextProvider.GetResourceContext(resourceType); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType); throw new MissingTransactionSupportException(resourceContext.PublicName); } @@ -163,12 +165,12 @@ private object GetWriteRepository(Type resourceType) protected virtual object ResolveWriteRepository(Type resourceType) { - var resourceContext = _resourceContextProvider.GetResourceContext(resourceType); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType); if (resourceContext.IdentityType == typeof(int)) { - var intRepositoryType = typeof(IResourceWriteRepository<>).MakeGenericType(resourceContext.ResourceType); - var intRepository = _serviceProvider.GetService(intRepositoryType); + Type intRepositoryType = typeof(IResourceWriteRepository<>).MakeGenericType(resourceContext.ResourceType); + object intRepository = _serviceProvider.GetService(intRepositoryType); if (intRepository != null) { @@ -176,7 +178,7 @@ protected virtual object ResolveWriteRepository(Type resourceType) } } - var resourceDefinitionType = typeof(IResourceWriteRepository<,>).MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType); + Type resourceDefinitionType = typeof(IResourceWriteRepository<,>).MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType); return _serviceProvider.GetRequiredService(resourceDefinitionType); } } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/AttrAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/AttrAttribute.cs index bd19f5e850..1987eb4d94 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/AttrAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/AttrAttribute.cs @@ -15,8 +15,8 @@ public sealed class AttrAttribute : ResourceFieldAttribute internal bool HasExplicitCapabilities => _capabilities != null; /// - /// The set of capabilities that are allowed to be performed on this attribute. - /// When not explicitly assigned, the configured default set of capabilities is used. + /// The set of capabilities that are allowed to be performed on this attribute. When not explicitly assigned, the configured default set of capabilities + /// is used. /// /// /// @@ -34,8 +34,7 @@ public AttrCapabilities Capabilities } /// - /// Get the value of the attribute for the given object. - /// Throws if the attribute does not belong to the provided object. + /// Get the value of the attribute for the given object. Throws if the attribute does not belong to the provided object. /// public object GetValue(object resource) { @@ -58,11 +57,10 @@ public void SetValue(object resource, object newValue) if (Property.SetMethod == null) { - throw new InvalidOperationException( - $"Property '{Property.DeclaringType?.Name}.{Property.Name}' is read-only."); + throw new InvalidOperationException($"Property '{Property.DeclaringType?.Name}.{Property.Name}' is read-only."); } - var convertedValue = TypeHelper.ConvertType(newValue, Property.PropertyType); + object convertedValue = TypeHelper.ConvertType(newValue, Property.PropertyType); Property.SetValue(resource, convertedValue); } @@ -78,7 +76,7 @@ public override bool Equals(object obj) return false; } - var other = (AttrAttribute) obj; + var other = (AttrAttribute)obj; return Capabilities == other.Capabilities && base.Equals(other); } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/AttrCapabilities.cs b/src/JsonApiDotNetCore/Resources/Annotations/AttrCapabilities.cs index af1448ce43..9eb93c9377 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/AttrCapabilities.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/AttrCapabilities.cs @@ -3,7 +3,7 @@ namespace JsonApiDotNetCore.Resources.Annotations { /// - /// Indicates capabilities that can be performed on an . + /// Indicates capabilities that can be performed on an . /// [Flags] public enum AttrCapabilities @@ -11,32 +11,27 @@ public enum AttrCapabilities None = 0, /// - /// Whether or not GET requests can retrieve the attribute. - /// Attempts to retrieve when disabled will return an HTTP 400 response. + /// Whether or not GET requests can retrieve the attribute. Attempts to retrieve when disabled will return an HTTP 400 response. /// AllowView = 1, /// - /// Whether or not POST requests can assign the attribute value. - /// Attempts to assign when disabled will return an HTTP 422 response. + /// Whether or not POST requests can assign the attribute value. Attempts to assign when disabled will return an HTTP 422 response. /// AllowCreate = 2, /// - /// Whether or not PATCH requests can update the attribute value. - /// Attempts to update when disabled will return an HTTP 422 response. + /// Whether or not PATCH requests can update the attribute value. Attempts to update when disabled will return an HTTP 422 response. /// AllowChange = 4, /// - /// Whether or not an attribute can be filtered on via a query string parameter. - /// Attempts to filter when disabled will return an HTTP 400 response. + /// Whether or not an attribute can be filtered on via a query string parameter. Attempts to filter when disabled will return an HTTP 400 response. /// AllowFilter = 8, /// - /// Whether or not an attribute can be sorted on via a query string parameter. - /// Attempts to sort when disabled will return an HTTP 400 response. + /// Whether or not an attribute can be sorted on via a query string parameter. Attempts to sort when disabled will return an HTTP 400 response. /// AllowSort = 16, diff --git a/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs index 869e322ee8..b2f51f4a47 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs @@ -3,7 +3,8 @@ namespace JsonApiDotNetCore.Resources.Annotations { /// - /// Used to expose a property on a resource class as a JSON:API to-many relationship (https://jsonapi.org/format/#document-resource-object-relationships). + /// Used to expose a property on a resource class as a JSON:API to-many relationship + /// (https://jsonapi.org/format/#document-resource-object-relationships). /// /// /// /// - /// In the following example, we expose a relationship named "tags" - /// through the navigation property `ArticleTags`. - /// The `Tags` property is decorated with `NotMapped` so that EF does not try - /// to map this to a database relationship. + /// In the following example, we expose a relationship named "tags" through the navigation property `ArticleTags`. The `Tags` property is decorated with + /// `NotMapped` so that EF does not try to map this to a database relationship. /// Tags { get; set; } /// public ISet ArticleTags { get; set; } /// } - /// + /// /// public class Tag : Identifiable /// { /// [Attr] /// public string Name { get; set; } /// } - /// + /// /// public sealed class ArticleTag /// { /// public int ArticleId { get; set; } /// public Article Article { get; set; } - /// + /// /// public int TagId { get; set; } /// public Tag Tag { get; set; } /// } @@ -48,83 +46,77 @@ namespace JsonApiDotNetCore.Resources.Annotations public sealed class HasManyThroughAttribute : HasManyAttribute { /// - /// The name of the join property on the parent resource. - /// In the example described above, this would be "ArticleTags". + /// The name of the join property on the parent resource. In the example described above, this would be "ArticleTags". /// public string ThroughPropertyName { get; } /// - /// The join type. - /// In the example described above, this would be `ArticleTag`. + /// The join type. In the example described above, this would be `ArticleTag`. /// public Type ThroughType { get; internal set; } /// - /// The navigation property back to the parent resource from the through type. - /// In the example described above, this would point to the `Article.ArticleTags.Article` property. + /// The navigation property back to the parent resource from the through type. In the example described above, this would point to the + /// `Article.ArticleTags.Article` property. /// public PropertyInfo LeftProperty { get; internal set; } /// - /// The ID property back to the parent resource from the through type. - /// In the example described above, this would point to the `Article.ArticleTags.ArticleId` property. + /// The ID property back to the parent resource from the through type. In the example described above, this would point to the + /// `Article.ArticleTags.ArticleId` property. /// public PropertyInfo LeftIdProperty { get; internal set; } /// - /// The navigation property to the related resource from the through type. - /// In the example described above, this would point to the `Article.ArticleTags.Tag` property. + /// The navigation property to the related resource from the through type. In the example described above, this would point to the + /// `Article.ArticleTags.Tag` property. /// public PropertyInfo RightProperty { get; internal set; } /// - /// The ID property to the related resource from the through type. - /// In the example described above, this would point to the `Article.ArticleTags.TagId` property. + /// The ID property to the related resource from the through type. In the example described above, this would point to the `Article.ArticleTags.TagId` + /// property. /// public PropertyInfo RightIdProperty { get; internal set; } /// - /// The join resource property on the parent resource. - /// In the example described above, this would point to the `Article.ArticleTags` property. + /// The join resource property on the parent resource. In the example described above, this would point to the `Article.ArticleTags` property. /// public PropertyInfo ThroughProperty { get; internal set; } /// - /// The internal navigation property path to the related resource. - /// In the example described above, this would contain "ArticleTags.Tag". + /// The internal navigation property path to the related resource. In the example described above, this would contain "ArticleTags.Tag". /// public override string RelationshipPath => $"{ThroughProperty.Name}.{RightProperty.Name}"; /// - /// Required for a self-referencing many-to-many relationship. - /// Contains the name of the property back to the parent resource from the through type. + /// Required for a self-referencing many-to-many relationship. Contains the name of the property back to the parent resource from the through type. /// public string LeftPropertyName { get; set; } /// - /// Required for a self-referencing many-to-many relationship. - /// Contains the name of the property to the related resource from the through type. + /// Required for a self-referencing many-to-many relationship. Contains the name of the property to the related resource from the through type. /// public string RightPropertyName { get; set; } /// - /// Optional. Can be used to indicate a non-default name for the ID property back to the parent resource from the through type. - /// Defaults to the name of suffixed with "Id". - /// In the example described above, this would be "ArticleId". + /// Optional. Can be used to indicate a non-default name for the ID property back to the parent resource from the through type. Defaults to the name of + /// suffixed with "Id". In the example described above, this would be "ArticleId". /// public string LeftIdPropertyName { get; set; } /// - /// Optional. Can be used to indicate a non-default name for the ID property to the related resource from the through type. - /// Defaults to the name of suffixed with "Id". - /// In the example described above, this would be "TagId". + /// Optional. Can be used to indicate a non-default name for the ID property to the related resource from the through type. Defaults to the name of + /// suffixed with "Id". In the example described above, this would be "TagId". /// public string RightIdPropertyName { get; set; } /// /// Creates a HasMany relationship through a many-to-many join relationship. /// - /// The name of the navigation property that will be used to access the join relationship. + /// + /// The name of the navigation property that will be used to access the join relationship. + /// public HasManyThroughAttribute(string throughPropertyName) { ArgumentGuard.NotNull(throughPropertyName, nameof(throughPropertyName)); @@ -133,29 +125,28 @@ public HasManyThroughAttribute(string throughPropertyName) } /// - /// Traverses through the provided resource and returns the value of the relationship on the other side of the through type. - /// In the example described above, this would be the value of "Articles.ArticleTags.Tag". + /// Traverses through the provided resource and returns the value of the relationship on the other side of the through type. In the example described + /// above, this would be the value of "Articles.ArticleTags.Tag". /// public override object GetValue(object resource) { ArgumentGuard.NotNull(resource, nameof(resource)); - var throughEntity = ThroughProperty.GetValue(resource); + object? throughEntity = ThroughProperty.GetValue(resource); + if (throughEntity == null) { return null; } - IEnumerable rightResources = ((IEnumerable) throughEntity) - .Cast() - .Select(rightResource => RightProperty.GetValue(rightResource)); + IEnumerable rightResources = ((IEnumerable)throughEntity).Cast().Select(rightResource => RightProperty.GetValue(rightResource)); return TypeHelper.CopyToTypedCollection(rightResources, Property.PropertyType); } /// - /// Traverses through the provided resource and sets the value of the relationship on the other side of the through type. - /// In the example described above, this would be the value of "Articles.ArticleTags.Tag". + /// Traverses through the provided resource and sets the value of the relationship on the other side of the through type. In the example described above, + /// this would be the value of "Articles.ArticleTags.Tag". /// public override void SetValue(object resource, object newValue) { @@ -169,17 +160,18 @@ public override void SetValue(object resource, object newValue) } else { - List throughResources = new List(); + var throughResources = new List(); + foreach (IIdentifiable rightResource in (IEnumerable)newValue) { - var throughEntity = TypeHelper.CreateInstance(ThroughType); + object throughEntity = TypeHelper.CreateInstance(ThroughType); LeftProperty.SetValue(throughEntity, resource); RightProperty.SetValue(throughEntity, rightResource); throughResources.Add(throughEntity); } - var typedCollection = TypeHelper.CopyToTypedCollection(throughResources, ThroughProperty.PropertyType); + IEnumerable typedCollection = TypeHelper.CopyToTypedCollection(throughResources, ThroughProperty.PropertyType); ThroughProperty.SetValue(resource, typedCollection); } } @@ -196,18 +188,17 @@ public override bool Equals(object obj) return false; } - var other = (HasManyThroughAttribute) obj; + var other = (HasManyThroughAttribute)obj; - return ThroughPropertyName == other.ThroughPropertyName && ThroughType == other.ThroughType && - LeftProperty == other.LeftProperty && LeftIdProperty == other.LeftIdProperty && - RightProperty == other.RightProperty && RightIdProperty == other.RightIdProperty && - ThroughProperty == other.ThroughProperty && base.Equals(other); + return ThroughPropertyName == other.ThroughPropertyName && ThroughType == other.ThroughType && LeftProperty == other.LeftProperty && + LeftIdProperty == other.LeftIdProperty && RightProperty == other.RightProperty && RightIdProperty == other.RightIdProperty && + ThroughProperty == other.ThroughProperty && base.Equals(other); } public override int GetHashCode() { - return HashCode.Combine(ThroughPropertyName, ThroughType, LeftProperty, LeftIdProperty, RightProperty, - RightIdProperty, ThroughProperty, base.GetHashCode()); + return HashCode.Combine(ThroughPropertyName, ThroughType, LeftProperty, LeftIdProperty, RightProperty, RightIdProperty, ThroughProperty, + base.GetHashCode()); } } } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs index cd7188b3a5..bb46646faa 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs @@ -14,8 +14,7 @@ namespace JsonApiDotNetCore.Resources.Annotations public abstract class RelationshipAttribute : ResourceFieldAttribute { /// - /// The property name of the EF Core inverse navigation, which may or may not exist. - /// Even if it exists, it may not be exposed as a JSON:API relationship. + /// The property name of the EF Core inverse navigation, which may or may not exist. Even if it exists, it may not be exposed as a JSON:API relationship. /// /// /// /// - /// In all cases except for relationships, this equals the property name. + /// In all cases except for relationships, this equals the property name. /// public virtual string RelationshipPath => Property.Name; /// - /// The child resource type. This does not necessarily match the navigation property type. - /// In the case of a relationship, this value will be the collection element type. + /// The child resource type. This does not necessarily match the navigation property type. In the case of a relationship, + /// this value will be the collection element type. /// /// /// - /// Configures which links to show in the - /// object for this relationship. - /// Defaults to , which falls back to - /// and then falls back to . + /// Configures which links to show in the object for this relationship. Defaults to + /// , which falls back to and then falls back to + /// . /// public LinkTypes Links { get; set; } = LinkTypes.NotConfigured; /// - /// Whether or not this relationship can be included using the ?include=publicName query string parameter. - /// This is true by default. + /// Whether or not this relationship can be included using the + /// + /// ?include=publicName + /// + /// query string parameter. This is true by default. /// public bool CanInclude { get; set; } = true; @@ -104,10 +105,9 @@ public override bool Equals(object obj) return false; } - var other = (RelationshipAttribute) obj; + var other = (RelationshipAttribute)obj; - return LeftType == other.LeftType && RightType == other.RightType && Links == other.Links && - CanInclude == other.CanInclude && base.Equals(other); + return LeftType == other.LeftType && RightType == other.RightType && Links == other.Links && CanInclude == other.CanInclude && base.Equals(other); } public override int GetHashCode() diff --git a/src/JsonApiDotNetCore/Resources/Annotations/ResourceAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/ResourceAttribute.cs index df3227acf1..db6f7c8621 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/ResourceAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/ResourceAttribute.cs @@ -11,8 +11,8 @@ namespace JsonApiDotNetCore.Resources.Annotations public sealed class ResourceAttribute : Attribute { /// - /// The publicly exposed name of this resource type. - /// When not explicitly assigned, the configured naming convention is applied on the pluralized resource class name. + /// The publicly exposed name of this resource type. When not explicitly assigned, the configured naming convention is applied on the pluralized resource + /// class name. /// public string PublicName { get; } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/ResourceFieldAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/ResourceFieldAttribute.cs index c538c6a12c..93efe49696 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/ResourceFieldAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/ResourceFieldAttribute.cs @@ -7,8 +7,8 @@ namespace JsonApiDotNetCore.Resources.Annotations { /// - /// Used to expose a property on a resource class as a JSON:API field (attribute or relationship). - /// See https://jsonapi.org/format/#document-resource-object-fields. + /// Used to expose a property on a resource class as a JSON:API field (attribute or relationship). See + /// https://jsonapi.org/format/#document-resource-object-fields. /// [PublicAPI] public abstract class ResourceFieldAttribute : Attribute @@ -16,8 +16,7 @@ public abstract class ResourceFieldAttribute : Attribute private string _publicName; /// - /// The publicly exposed name of this JSON:API field. - /// When not explicitly assigned, the configured naming convention is applied on the property name. + /// The publicly exposed name of this JSON:API field. When not explicitly assigned, the configured naming convention is applied on the property name. /// public string PublicName { @@ -28,6 +27,7 @@ public string PublicName { throw new ArgumentException("Exposed name cannot be null, empty or contain only whitespace.", nameof(value)); } + _publicName = value; } } @@ -54,7 +54,7 @@ public override bool Equals(object obj) return false; } - var other = (ResourceFieldAttribute) obj; + var other = (ResourceFieldAttribute)obj; return PublicName == other.PublicName && Property == other.Property; } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/ResourceLinksAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/ResourceLinksAttribute.cs index ede3a402be..f4d3a1ffc7 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/ResourceLinksAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/ResourceLinksAttribute.cs @@ -12,24 +12,21 @@ namespace JsonApiDotNetCore.Resources.Annotations public sealed class ResourceLinksAttribute : Attribute { /// - /// Configures which links to show in the - /// object for this resource type. - /// Defaults to , which falls back to . + /// Configures which links to show in the object for this resource type. Defaults to + /// , which falls back to . /// public LinkTypes TopLevelLinks { get; set; } = LinkTypes.NotConfigured; /// - /// Configures which links to show in the - /// object for this resource type. - /// Defaults to , which falls back to . + /// Configures which links to show in the object for this resource type. Defaults to + /// , which falls back to . /// public LinkTypes ResourceLinks { get; set; } = LinkTypes.NotConfigured; - + /// - /// Configures which links to show in the - /// object for all relationships of this resource type. - /// Defaults to , which falls back to . - /// This can be overruled per relationship by setting . + /// Configures which links to show in the object for all relationships of this resource type. + /// Defaults to , which falls back to . This can be overruled per + /// relationship by setting . /// public LinkTypes RelationshipLinks { get; set; } = LinkTypes.NotConfigured; } diff --git a/src/JsonApiDotNetCore/Resources/IIdentifiable.cs b/src/JsonApiDotNetCore/Resources/IIdentifiable.cs index 6fa523c617..99559870a4 100644 --- a/src/JsonApiDotNetCore/Resources/IIdentifiable.cs +++ b/src/JsonApiDotNetCore/Resources/IIdentifiable.cs @@ -1,8 +1,8 @@ namespace JsonApiDotNetCore.Resources { /// - /// When implemented by a class, indicates to JsonApiDotNetCore that the class represents a JSON:API resource. - /// Note that JsonApiDotNetCore also assumes that a property named 'Id' exists. + /// When implemented by a class, indicates to JsonApiDotNetCore that the class represents a JSON:API resource. Note that JsonApiDotNetCore also assumes + /// that a property named 'Id' exists. /// public interface IIdentifiable { @@ -20,7 +20,9 @@ public interface IIdentifiable /// /// When implemented by a class, indicates to JsonApiDotNetCore that the class represents a JSON:API resource. /// - /// The resource identifier type. + /// + /// The resource identifier type. + /// public interface IIdentifiable : IIdentifiable { /// diff --git a/src/JsonApiDotNetCore/Resources/IResourceChangeTracker.cs b/src/JsonApiDotNetCore/Resources/IResourceChangeTracker.cs index cceb0994d1..79eca2ed6a 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceChangeTracker.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceChangeTracker.cs @@ -3,11 +3,12 @@ namespace JsonApiDotNetCore.Resources /// /// Used to determine whether additional changes to a resource (side effects), not specified in a POST or PATCH request, have been applied. /// - public interface IResourceChangeTracker where TResource : class, IIdentifiable + public interface IResourceChangeTracker + where TResource : class, IIdentifiable { /// - /// Sets the exposed resource attributes as stored in database, before applying the PATCH operation. - /// For POST operations, this sets exposed resource attributes to their default value. + /// Sets the exposed resource attributes as stored in database, before applying the PATCH operation. For POST operations, this sets exposed resource + /// attributes to their default value. /// void SetInitiallyStoredAttributeValues(TResource resource); @@ -22,8 +23,8 @@ public interface IResourceChangeTracker where TResource : class, I void SetFinallyStoredAttributeValues(TResource resource); /// - /// Validates if any exposed resource attributes that were not in the POST or PATCH request have been changed. - /// And validates if the values from the request are stored without modification. + /// Validates if any exposed resource attributes that were not in the POST or PATCH request have been changed. And validates if the values from the + /// request are stored without modification. /// /// /// true if the attribute values from the POST or PATCH request were the only changes; false, otherwise. diff --git a/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs b/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs index 0ca5e77950..8688a412ec 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs @@ -6,10 +6,12 @@ namespace JsonApiDotNetCore.Resources { /// - /// Provides a resource-centric extensibility point for executing custom code when something happens with a resource. - /// The goal here is to reduce the need for overriding the service and repository layers. + /// Provides a resource-centric extensibility point for executing custom code when something happens with a resource. The goal here is to reduce the need + /// for overriding the service and repository layers. /// - /// The resource type. + /// + /// The resource type. + /// [PublicAPI] public interface IResourceDefinition : IResourceDefinition where TResource : class, IIdentifiable @@ -17,11 +19,15 @@ public interface IResourceDefinition : IResourceDefinition - /// Provides a resource-centric extensibility point for executing custom code when something happens with a resource. - /// The goal here is to reduce the need for overriding the service and repository layers. + /// Provides a resource-centric extensibility point for executing custom code when something happens with a resource. The goal here is to reduce the need + /// for overriding the service and repository layers. /// - /// The resource type. - /// The resource identifier type. + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// [PublicAPI] public interface IResourceDefinition where TResource : class, IIdentifiable @@ -47,10 +53,10 @@ public interface IResourceDefinition /// The new filter, or null to disable the existing filter. /// FilterExpression OnApplyFilter(FilterExpression existingFilter); - + /// - /// Enables to extend, replace or remove a sort order that is being applied on a set of this resource type. - /// Tip: Use to build from a lambda expression. + /// Enables to extend, replace or remove a sort order that is being applied on a set of this resource type. Tip: Use + /// to build from a lambda expression. /// /// /// An optional existing sort order, coming from query string. Can be null. @@ -59,7 +65,7 @@ public interface IResourceDefinition /// The new sort order, or null to disable the existing sort order and sort by ID. /// SortExpression OnApplySort(SortExpression existingSort); - + /// /// Enables to extend, replace or remove pagination that is being applied on a set of this resource type. /// @@ -67,24 +73,24 @@ public interface IResourceDefinition /// An optional existing pagination, coming from query string. Can be null. /// /// - /// The changed pagination, or null to use the first page with default size from options. - /// To disable paging, set to null. + /// The changed pagination, or null to use the first page with default size from options. To disable paging, set + /// to null. /// PaginationExpression OnApplyPagination(PaginationExpression existingPagination); - + /// - /// Enables to extend, replace or remove a sparse fieldset that is being applied on a set of this resource type. - /// Tip: Use and - /// to safely change the fieldset without worrying about nulls. + /// Enables to extend, replace or remove a sparse fieldset that is being applied on a set of this resource type. Tip: Use + /// and to + /// safely change the fieldset without worrying about nulls. /// /// - /// This method executes twice for a single request: first to select which fields to retrieve from the data store and then to - /// select which fields to serialize. Including extra fields from this method will retrieve them, but not include them in the json output. - /// This enables you to expose calculated properties whose value depends on a field that is not in the sparse fieldset. + /// This method executes twice for a single request: first to select which fields to retrieve from the data store and then to select which fields to + /// serialize. Including extra fields from this method will retrieve them, but not include them in the json output. This enables you to expose calculated + /// properties whose value depends on a field that is not in the sparse fieldset. /// - /// The incoming sparse fieldset from query string. - /// At query execution time, this is null if the query string contains no sparse fieldset. - /// At serialization time, this contains all viewable fields if the query string contains no sparse fieldset. + /// + /// The incoming sparse fieldset from query string. At query execution time, this is null if the query string contains no sparse fieldset. At + /// serialization time, this contains all viewable fields if the query string contains no sparse fieldset. /// /// /// The new sparse fieldset, or null to discard the existing sparse fieldset and select all viewable fields. @@ -92,8 +98,8 @@ public interface IResourceDefinition SparseFieldSetExpression OnApplySparseFieldSet(SparseFieldSetExpression existingSparseFieldSet); /// - /// Enables to adapt the Entity Framework Core query, based on custom query string parameters. - /// Note this only works on primary resource requests, such as /articles, but not on /blogs/1/articles or /blogs?include=articles. + /// Enables to adapt the Entity Framework Core query, based on custom query string parameters. Note this only works on + /// primary resource requests, such as /articles, but not on /blogs/1/articles or /blogs?include=articles. /// /// /// /// ["isHighRisk"] = FilterByHighRisk /// }; /// } - /// + /// /// private static IQueryable FilterByHighRisk(IQueryable source, StringValues parameterValue) /// { /// bool isFilterOnHighRisk = bool.Parse(parameterValue); diff --git a/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs b/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs index b3b744ba1f..90910c8d5b 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs @@ -6,43 +6,43 @@ namespace JsonApiDotNetCore.Resources { /// - /// Retrieves an instance from the D/I container and invokes a callback on it. + /// Retrieves an instance from the D/I container and invokes a callback on it. /// public interface IResourceDefinitionAccessor { /// - /// Invokes for the specified resource type. + /// Invokes for the specified resource type. /// IReadOnlyCollection OnApplyIncludes(Type resourceType, IReadOnlyCollection existingIncludes); /// - /// Invokes for the specified resource type. + /// Invokes for the specified resource type. /// FilterExpression OnApplyFilter(Type resourceType, FilterExpression existingFilter); /// - /// Invokes for the specified resource type. + /// Invokes for the specified resource type. /// SortExpression OnApplySort(Type resourceType, SortExpression existingSort); /// - /// Invokes for the specified resource type. + /// Invokes for the specified resource type. /// PaginationExpression OnApplyPagination(Type resourceType, PaginationExpression existingPagination); /// - /// Invokes for the specified resource type. + /// Invokes for the specified resource type. /// SparseFieldSetExpression OnApplySparseFieldSet(Type resourceType, SparseFieldSetExpression existingSparseFieldSet); /// - /// Invokes for the specified resource type, - /// then returns the expression for the specified parameter name. + /// Invokes for the specified resource type, then + /// returns the expression for the specified parameter name. /// object GetQueryableHandlerForQueryStringParameter(Type resourceType, string parameterName); /// - /// Invokes for the specified resource. + /// Invokes for the specified resource. /// IDictionary GetMeta(Type resourceType, IIdentifiable resourceInstance); } diff --git a/src/JsonApiDotNetCore/Resources/Identifiable.cs b/src/JsonApiDotNetCore/Resources/Identifiable.cs index 6b95285da4..69875bfdac 100644 --- a/src/JsonApiDotNetCore/Resources/Identifiable.cs +++ b/src/JsonApiDotNetCore/Resources/Identifiable.cs @@ -5,12 +5,15 @@ namespace JsonApiDotNetCore.Resources { /// public abstract class Identifiable : Identifiable - { } + { + } /// - /// A convenient basic implementation of that provides conversion between and . + /// A convenient basic implementation of that provides conversion between and . /// - /// The resource identifier type. + /// + /// The resource identifier type. + /// public abstract class Identifiable : IIdentifiable { /// diff --git a/src/JsonApiDotNetCore/Resources/IdentifiableComparer.cs b/src/JsonApiDotNetCore/Resources/IdentifiableComparer.cs index 21782057db..332995b294 100644 --- a/src/JsonApiDotNetCore/Resources/IdentifiableComparer.cs +++ b/src/JsonApiDotNetCore/Resources/IdentifiableComparer.cs @@ -5,8 +5,8 @@ namespace JsonApiDotNetCore.Resources { /// - /// Compares `IIdentifiable` instances with each other based on their type and , - /// falling back to when both StringIds are null. + /// Compares `IIdentifiable` instances with each other based on their type and , falling back to + /// when both StringIds are null. /// [PublicAPI] public sealed class IdentifiableComparer : IEqualityComparer diff --git a/src/JsonApiDotNetCore/Resources/IdentifiableExtensions.cs b/src/JsonApiDotNetCore/Resources/IdentifiableExtensions.cs index b89e8ec580..807d6c3f23 100644 --- a/src/JsonApiDotNetCore/Resources/IdentifiableExtensions.cs +++ b/src/JsonApiDotNetCore/Resources/IdentifiableExtensions.cs @@ -10,7 +10,7 @@ public static object GetTypedId(this IIdentifiable identifiable) ArgumentGuard.NotNull(identifiable, nameof(identifiable)); PropertyInfo property = identifiable.GetType().GetProperty(nameof(Identifiable.Id)); - + if (property == null) { throw new InvalidOperationException($"Resource of type '{identifiable.GetType()}' does not have an 'Id' property."); diff --git a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs index 1d3e945c00..06987f123a 100644 --- a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs @@ -6,19 +6,22 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries.Expressions; +using JsonApiDotNetCore.Resources.Annotations; namespace JsonApiDotNetCore.Resources { /// - /// Provides a resource-centric extensibility point for executing custom code when something happens with a resource. - /// The goal here is to reduce the need for overriding the service and repository layers. + /// Provides a resource-centric extensibility point for executing custom code when something happens with a resource. The goal here is to reduce the need + /// for overriding the service and repository layers. /// - /// The resource type. + /// + /// The resource type. + /// [PublicAPI] public class JsonApiResourceDefinition : JsonApiResourceDefinition, IResourceDefinition where TResource : class, IIdentifiable { - public JsonApiResourceDefinition(IResourceGraph resourceGraph) + public JsonApiResourceDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } @@ -57,7 +60,7 @@ public virtual SortExpression OnApplySort(SortExpression existingSort) } /// - /// Creates a from a lambda expression. + /// Creates a from a lambda expression. /// /// /// sortElements = new List(); + var sortElements = new List(); - foreach (var (keySelector, sortDirection) in keySelectors) + foreach ((Expression> keySelector, ListSortDirection sortDirection) in keySelectors) { bool isAscending = sortDirection == ListSortDirection.Ascending; - var attribute = ResourceGraph.GetAttributes(keySelector).Single(); + AttrAttribute attribute = ResourceGraph.GetAttributes(keySelector).Single(); var sortElement = new SortElementExpression(new ResourceFieldChainExpression(attribute), isAscending); sortElements.Add(sortElement); @@ -111,8 +114,8 @@ public virtual IDictionary GetMeta(TResource resource) } /// - /// This is an alias type intended to simplify the implementation's method signature. - /// See for usage details. + /// This is an alias type intended to simplify the implementation's method signature. See for usage + /// details. /// public sealed class PropertySortOrder : List<(Expression> KeySelector, ListSortDirection SortDirection)> { diff --git a/src/JsonApiDotNetCore/Resources/OperationContainer.cs b/src/JsonApiDotNetCore/Resources/OperationContainer.cs index 85000dff0e..33dd890f39 100644 --- a/src/JsonApiDotNetCore/Resources/OperationContainer.cs +++ b/src/JsonApiDotNetCore/Resources/OperationContainer.cs @@ -17,8 +17,7 @@ public sealed class OperationContainer public ITargetedFields TargetedFields { get; } public IJsonApiRequest Request { get; } - public OperationContainer(OperationKind kind, IIdentifiable resource, ITargetedFields targetedFields, - IJsonApiRequest request) + public OperationContainer(OperationKind kind, IIdentifiable resource, ITargetedFields targetedFields, IJsonApiRequest request) { ArgumentGuard.NotNull(resource, nameof(resource)); ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); @@ -32,7 +31,7 @@ public OperationContainer(OperationKind kind, IIdentifiable resource, ITargetedF public void SetTransactionId(Guid transactionId) { - ((JsonApiRequest) Request).TransactionId = transactionId; + ((JsonApiRequest)Request).TransactionId = transactionId; } public OperationContainer WithResource(IIdentifiable resource) @@ -46,7 +45,7 @@ public ISet GetSecondaryResources() { var secondaryResources = new HashSet(IdentifiableComparer.Instance); - foreach (var relationship in TargetedFields.Relationships) + foreach (RelationshipAttribute relationship in TargetedFields.Relationships) { AddSecondaryResources(relationship, secondaryResources); } @@ -56,9 +55,9 @@ public ISet GetSecondaryResources() private void AddSecondaryResources(RelationshipAttribute relationship, HashSet secondaryResources) { - var rightValue = relationship.GetValue(Resource); + object rightValue = relationship.GetValue(Resource); - foreach (var rightResource in TypeHelper.ExtractResources(rightValue)) + foreach (IIdentifiable rightResource in TypeHelper.ExtractResources(rightValue)) { secondaryResources.Add(rightResource); } diff --git a/src/JsonApiDotNetCore/Resources/QueryStringParameterHandlers.cs b/src/JsonApiDotNetCore/Resources/QueryStringParameterHandlers.cs index 5914fd174f..1f5d412252 100644 --- a/src/JsonApiDotNetCore/Resources/QueryStringParameterHandlers.cs +++ b/src/JsonApiDotNetCore/Resources/QueryStringParameterHandlers.cs @@ -6,8 +6,8 @@ namespace JsonApiDotNetCore.Resources { /// - /// This is an alias type intended to simplify the implementation's method signature. - /// See for usage details. + /// This is an alias type intended to simplify the implementation's method signature. See + /// for usage details. /// public sealed class QueryStringParameterHandlers : Dictionary, StringValues, IQueryable>> { diff --git a/src/JsonApiDotNetCore/Resources/ResourceChangeTracker.cs b/src/JsonApiDotNetCore/Resources/ResourceChangeTracker.cs index f4e88c8c27..1dfc5a09a7 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceChangeTracker.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceChangeTracker.cs @@ -8,7 +8,8 @@ namespace JsonApiDotNetCore.Resources { /// [PublicAPI] - public sealed class ResourceChangeTracker : IResourceChangeTracker where TResource : class, IIdentifiable + public sealed class ResourceChangeTracker : IResourceChangeTracker + where TResource : class, IIdentifiable { private readonly IJsonApiOptions _options; private readonly IResourceContextProvider _resourceContextProvider; @@ -18,8 +19,7 @@ public sealed class ResourceChangeTracker : IResourceChangeTracker _requestedAttributeValues; private IDictionary _finallyStoredAttributeValues; - public ResourceChangeTracker(IJsonApiOptions options, IResourceContextProvider resourceContextProvider, - ITargetedFields targetedFields) + public ResourceChangeTracker(IJsonApiOptions options, IResourceContextProvider resourceContextProvider, ITargetedFields targetedFields) { ArgumentGuard.NotNull(options, nameof(options)); ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); @@ -35,7 +35,7 @@ public void SetInitiallyStoredAttributeValues(TResource resource) { ArgumentGuard.NotNull(resource, nameof(resource)); - var resourceContext = _resourceContextProvider.GetResourceContext(); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); _initiallyStoredAttributeValues = CreateAttributeDictionary(resource, resourceContext.Attributes); } @@ -52,19 +52,18 @@ public void SetFinallyStoredAttributeValues(TResource resource) { ArgumentGuard.NotNull(resource, nameof(resource)); - var resourceContext = _resourceContextProvider.GetResourceContext(); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); _finallyStoredAttributeValues = CreateAttributeDictionary(resource, resourceContext.Attributes); } - private IDictionary CreateAttributeDictionary(TResource resource, - IEnumerable attributes) + private IDictionary CreateAttributeDictionary(TResource resource, IEnumerable attributes) { var result = new Dictionary(); - foreach (var attribute in attributes) + foreach (AttrAttribute attribute in attributes) { object value = attribute.GetValue(resource); - var json = JsonConvert.SerializeObject(value, _options.SerializerSettings); + string json = JsonConvert.SerializeObject(value, _options.SerializerSettings); result.Add(attribute.PublicName, json); } @@ -74,12 +73,12 @@ private IDictionary CreateAttributeDictionary(TResource resource /// public bool HasImplicitChanges() { - foreach (var key in _initiallyStoredAttributeValues.Keys) + foreach (string key in _initiallyStoredAttributeValues.Keys) { if (_requestedAttributeValues.ContainsKey(key)) { - var requestedValue = _requestedAttributeValues[key]; - var actualValue = _finallyStoredAttributeValues[key]; + string requestedValue = _requestedAttributeValues[key]; + string actualValue = _finallyStoredAttributeValues[key]; if (requestedValue != actualValue) { @@ -88,8 +87,8 @@ public bool HasImplicitChanges() } else { - var initiallyStoredValue = _initiallyStoredAttributeValues[key]; - var finallyStoredValue = _finallyStoredAttributeValues[key]; + string initiallyStoredValue = _initiallyStoredAttributeValues[key]; + string finallyStoredValue = _finallyStoredAttributeValues[key]; if (initiallyStoredValue != finallyStoredValue) { diff --git a/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs b/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs index fe5dc40088..958e735e4d 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs @@ -75,7 +75,7 @@ public object GetQueryableHandlerForQueryStringParameter(Type resourceType, stri ArgumentGuard.NotNull(parameterName, nameof(parameterName)); dynamic resourceDefinition = ResolveResourceDefinition(resourceType); - var handlers = resourceDefinition.OnRegisterQueryableHandlersForQueryStringParameters(); + dynamic handlers = resourceDefinition.OnRegisterQueryableHandlersForQueryStringParameters(); return handlers != null && handlers.ContainsKey(parameterName) ? handlers[parameterName] : null; } @@ -86,25 +86,25 @@ public IDictionary GetMeta(Type resourceType, IIdentifiable reso ArgumentGuard.NotNull(resourceType, nameof(resourceType)); dynamic resourceDefinition = ResolveResourceDefinition(resourceType); - return resourceDefinition.GetMeta((dynamic) resourceInstance); + return resourceDefinition.GetMeta((dynamic)resourceInstance); } protected virtual object ResolveResourceDefinition(Type resourceType) { - var resourceContext = _resourceContextProvider.GetResourceContext(resourceType); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType); if (resourceContext.IdentityType == typeof(int)) { - var intResourceDefinitionType = typeof(IResourceDefinition<>).MakeGenericType(resourceContext.ResourceType); - var intResourceDefinition = _serviceProvider.GetService(intResourceDefinitionType); - + Type intResourceDefinitionType = typeof(IResourceDefinition<>).MakeGenericType(resourceContext.ResourceType); + object intResourceDefinition = _serviceProvider.GetService(intResourceDefinitionType); + if (intResourceDefinition != null) { return intResourceDefinition; } } - var resourceDefinitionType = typeof(IResourceDefinition<,>).MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType); + Type resourceDefinitionType = typeof(IResourceDefinition<,>).MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType); return _serviceProvider.GetRequiredService(resourceDefinitionType); } } diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index 76e96cb6f5..cb802a4b3b 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -31,7 +31,7 @@ public IIdentifiable CreateInstance(Type resourceType) public TResource CreateInstance() where TResource : IIdentifiable { - return (TResource) InnerCreateInstance(typeof(TResource), _serviceProvider); + return (TResource)InnerCreateInstance(typeof(TResource), _serviceProvider); } private static IIdentifiable InnerCreateInstance(Type type, IServiceProvider serviceProvider) @@ -46,10 +46,10 @@ private static IIdentifiable InnerCreateInstance(Type type, IServiceProvider ser } catch (Exception exception) { - throw new InvalidOperationException(hasSingleConstructorWithoutParameters + throw new InvalidOperationException( + hasSingleConstructorWithoutParameters ? $"Failed to create an instance of '{type.FullName}' using its default constructor." - : $"Failed to create an instance of '{type.FullName}' using injected constructor parameters.", - exception); + : $"Failed to create an instance of '{type.FullName}' using injected constructor parameters.", exception); } } @@ -63,18 +63,17 @@ public NewExpression CreateNewExpression(Type resourceType) return Expression.New(resourceType); } - List constructorArguments = new List(); + var constructorArguments = new List(); + + ConstructorInfo longestConstructor = GetLongestConstructor(resourceType); - var longestConstructor = GetLongestConstructor(resourceType); foreach (ParameterInfo constructorParameter in longestConstructor.GetParameters()) { try { - object constructorArgument = - ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, constructorParameter.ParameterType); + object constructorArgument = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, constructorParameter.ParameterType); - var argumentExpression = - CreateTupleAccessExpressionForConstant(constructorArgument, constructorArgument.GetType()); + Expression argumentExpression = CreateTupleAccessExpressionForConstant(constructorArgument, constructorArgument.GetType()); constructorArguments.Add(argumentExpression); } @@ -123,8 +122,9 @@ private static ConstructorInfo GetLongestConstructor(Type type) for (int index = 1; index < constructors.Length; index++) { - var constructor = constructors[index]; + ConstructorInfo constructor = constructors[index]; int length = constructor.GetParameters().Length; + if (length > maxParameterLength) { bestMatch = constructor; diff --git a/src/JsonApiDotNetCore/Resources/ResourceHooksDefinition.cs b/src/JsonApiDotNetCore/Resources/ResourceHooksDefinition.cs index 6b20919de2..60b9e67505 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceHooksDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceHooksDefinition.cs @@ -7,13 +7,16 @@ namespace JsonApiDotNetCore.Resources { /// - /// Provides a resource-specific extensibility point for API developers to be notified of various events and influence behavior using custom code. - /// It is intended to improve the developer experience and reduce boilerplate for commonly required features. - /// The goal of this class is to reduce the frequency with which developers have to override the service and repository layers. + /// Provides a resource-specific extensibility point for API developers to be notified of various events and influence behavior using custom code. It is + /// intended to improve the developer experience and reduce boilerplate for commonly required features. The goal of this class is to reduce the frequency + /// with which developers have to override the service and repository layers. /// - /// The resource type. + /// + /// The resource type. + /// [PublicAPI] - public class ResourceHooksDefinition : IResourceHookContainer where TResource : class, IIdentifiable + public class ResourceHooksDefinition : IResourceHookContainer + where TResource : class, IIdentifiable { protected IResourceGraph ResourceGraph { get; } @@ -25,51 +28,105 @@ public ResourceHooksDefinition(IResourceGraph resourceGraph) } /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual void AfterCreate(HashSet resources, ResourcePipeline pipeline) { } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual void AfterCreate(HashSet resources, ResourcePipeline pipeline) + { + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual void AfterRead(HashSet resources, ResourcePipeline pipeline, bool isIncluded = false) { } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual void AfterRead(HashSet resources, ResourcePipeline pipeline, bool isIncluded = false) + { + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual void AfterUpdate(HashSet resources, ResourcePipeline pipeline) { } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual void AfterUpdate(HashSet resources, ResourcePipeline pipeline) + { + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual void AfterDelete(HashSet resources, ResourcePipeline pipeline, bool succeeded) { } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual void AfterDelete(HashSet resources, ResourcePipeline pipeline, bool succeeded) + { + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual void AfterUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) { } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual void AfterUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) + { + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual IEnumerable BeforeCreate(IResourceHashSet resources, ResourcePipeline pipeline) { return resources; } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual IEnumerable BeforeCreate(IResourceHashSet resources, ResourcePipeline pipeline) + { + return resources; + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null) { } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null) + { + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual IEnumerable BeforeUpdate(IDiffableResourceHashSet resources, ResourcePipeline pipeline) { return resources; } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual IEnumerable BeforeUpdate(IDiffableResourceHashSet resources, ResourcePipeline pipeline) + { + return resources; + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual IEnumerable BeforeDelete(IResourceHashSet resources, ResourcePipeline pipeline) { return resources; } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual IEnumerable BeforeDelete(IResourceHashSet resources, ResourcePipeline pipeline) + { + return resources; + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) { return ids; } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary resourcesByRelationship, + ResourcePipeline pipeline) + { + return ids; + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) { } - + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) + { + } + /// - /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. - public virtual IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline) { return resources; } + /// + /// This method is part of Resource Hooks, which is an experimental feature and subject to change in future versions. + /// + public virtual IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline) + { + return resources; + } } } diff --git a/src/JsonApiDotNetCore/Serialization/AtomicOperationsResponseSerializer.cs b/src/JsonApiDotNetCore/Serialization/AtomicOperationsResponseSerializer.cs index b9692bfac2..aeb773dd88 100644 --- a/src/JsonApiDotNetCore/Serialization/AtomicOperationsResponseSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/AtomicOperationsResponseSerializer.cs @@ -5,13 +5,14 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; using JsonApiDotNetCore.Serialization.Building; using JsonApiDotNetCore.Serialization.Objects; namespace JsonApiDotNetCore.Serialization { /// - /// Server serializer implementation of for atomic:operations responses. + /// Server serializer implementation of for atomic:operations responses. /// [PublicAPI] public sealed class AtomicOperationsResponseSerializer : BaseSerializer, IJsonApiSerializer @@ -25,9 +26,8 @@ public sealed class AtomicOperationsResponseSerializer : BaseSerializer, IJsonAp /// public string ContentType { get; } = HeaderConstants.AtomicOperationsMediaType; - public AtomicOperationsResponseSerializer(IResourceObjectBuilder resourceObjectBuilder, - IMetaBuilder metaBuilder, ILinkBuilder linkBuilder, IFieldsToSerialize fieldsToSerialize, - IJsonApiRequest request, IJsonApiOptions options) + public AtomicOperationsResponseSerializer(IResourceObjectBuilder resourceObjectBuilder, IMetaBuilder metaBuilder, ILinkBuilder linkBuilder, + IFieldsToSerialize fieldsToSerialize, IJsonApiRequest request, IJsonApiOptions options) : base(resourceObjectBuilder) { ArgumentGuard.NotNull(metaBuilder, nameof(metaBuilder)); @@ -79,9 +79,9 @@ private AtomicResultObject SerializeOperation(OperationContainer operation) _request.CopyFrom(operation.Request); _fieldsToSerialize.ResetCache(); - var resourceType = operation.Resource.GetType(); - var attributes = _fieldsToSerialize.GetAttributes(resourceType); - var relationships = _fieldsToSerialize.GetRelationships(resourceType); + Type resourceType = operation.Resource.GetType(); + IReadOnlyCollection attributes = _fieldsToSerialize.GetAttributes(resourceType); + IReadOnlyCollection relationships = _fieldsToSerialize.GetRelationships(resourceType); resourceObject = ResourceObjectBuilder.Build(operation.Resource, attributes, relationships); } @@ -99,7 +99,10 @@ private AtomicResultObject SerializeOperation(OperationContainer operation) private string SerializeErrorDocument(ErrorDocument errorDocument) { - return SerializeObject(errorDocument, _options.SerializerSettings, serializer => { serializer.ApplyErrorSettings(); }); + return SerializeObject(errorDocument, _options.SerializerSettings, serializer => + { + serializer.ApplyErrorSettings(); + }); } } } diff --git a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs index 070f501d01..c5a393efe9 100644 --- a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -14,8 +15,8 @@ namespace JsonApiDotNetCore.Serialization { /// - /// Abstract base class for deserialization. Deserializes JSON content into s - /// and constructs instances of the resource(s) in the document body. + /// Abstract base class for deserialization. Deserializes JSON content into s and constructs instances of the resource(s) + /// in the document body. /// [PublicAPI] public abstract class BaseDeserializer @@ -36,25 +37,30 @@ protected BaseDeserializer(IResourceContextProvider resourceContextProvider, IRe } /// - /// This method is called each time a is constructed - /// from the serialized content, which is used to do additional processing + /// This method is called each time a is constructed from the serialized content, which is used to do additional processing /// depending on the type of deserializer. /// /// - /// See the implementation of this method in - /// and for examples. + /// See the implementation of this method in and for examples. /// - /// The resource that was constructed from the document's body. - /// The metadata for the exposed field. - /// Relationship data for . Is null when is not a . + /// + /// The resource that was constructed from the document's body. + /// + /// + /// The metadata for the exposed field. + /// + /// + /// Relationship data for . Is null when is not a . + /// protected abstract void AfterProcessField(IIdentifiable resource, ResourceFieldAttribute field, RelationshipEntry data = null); protected object DeserializeBody(string body) { ArgumentGuard.NotNull(body, nameof(body)); - var bodyJToken = LoadJToken(body); + JToken bodyJToken = LoadJToken(body); Document = bodyJToken.ToObject(); + if (Document != null) { if (Document.IsManyData) @@ -74,10 +80,17 @@ protected object DeserializeBody(string body) /// /// Sets the attributes on a parsed resource. /// - /// The parsed resource. - /// Attributes and their values, as in the serialized content. - /// Exposed attributes for . - protected IIdentifiable SetAttributes(IIdentifiable resource, IDictionary attributeValues, IReadOnlyCollection attributes) + /// + /// The parsed resource. + /// + /// + /// Attributes and their values, as in the serialized content. + /// + /// + /// Exposed attributes for . + /// + protected IIdentifiable SetAttributes(IIdentifiable resource, IDictionary attributeValues, + IReadOnlyCollection attributes) { ArgumentGuard.NotNull(resource, nameof(resource)); ArgumentGuard.NotNull(attributes, nameof(attributes)); @@ -87,17 +100,17 @@ protected IIdentifiable SetAttributes(IIdentifiable resource, IDictionary /// Sets the relationships on a parsed resource. /// - /// The parsed resource. - /// Relationships and their values, as in the serialized content. - /// Exposed relationships for . - protected virtual IIdentifiable SetRelationships(IIdentifiable resource, IDictionary relationshipValues, IReadOnlyCollection relationshipAttributes) + /// + /// The parsed resource. + /// + /// + /// Relationships and their values, as in the serialized content. + /// + /// + /// Exposed relationships for . + /// + protected virtual IIdentifiable SetRelationships(IIdentifiable resource, IDictionary relationshipValues, + IReadOnlyCollection relationshipAttributes) { ArgumentGuard.NotNull(resource, nameof(resource)); ArgumentGuard.NotNull(relationshipAttributes, nameof(relationshipAttributes)); @@ -122,9 +142,10 @@ protected virtual IIdentifiable SetRelationships(IIdentifiable resource, IDictio return resource; } - foreach (var attr in relationshipAttributes) + foreach (RelationshipAttribute attr in relationshipAttributes) { - var relationshipIsProvided = relationshipValues.TryGetValue(attr.PublicName, out RelationshipEntry relationshipData); + bool relationshipIsProvided = relationshipValues.TryGetValue(attr.PublicName, out RelationshipEntry relationshipData); + if (!relationshipIsProvided || !relationshipData.IsPopulated) { continue; @@ -139,7 +160,7 @@ protected virtual IIdentifiable SetRelationships(IIdentifiable resource, IDictio SetHasManyRelationship(resource, hasManyAttribute, relationshipData); } } - + return resource; } @@ -155,10 +176,11 @@ protected JToken LoadJToken(string body) } /// - /// Creates an instance of the referenced type in - /// and sets its attributes and relationships. + /// Creates an instance of the referenced type in and sets its attributes and relationships. /// - /// The parsed resource. + /// + /// The parsed resource. + /// protected IIdentifiable ParseResourceObject(ResourceObject data) { AssertHasType(data, null); @@ -168,8 +190,8 @@ protected IIdentifiable ParseResourceObject(ResourceObject data) AssertHasNoLid(data); } - var resourceContext = GetExistingResourceContext(data.Type); - var resource = ResourceFactory.CreateInstance(resourceContext.ResourceType); + ResourceContext resourceContext = GetExistingResourceContext(data.Type); + IIdentifiable resource = ResourceFactory.CreateInstance(resourceContext.ResourceType); resource = SetAttributes(resource, data.Attributes, resourceContext.Attributes); resource = SetRelationships(resource, data.Relationships, resourceContext.Relationships); @@ -186,11 +208,12 @@ protected IIdentifiable ParseResourceObject(ResourceObject data) protected ResourceContext GetExistingResourceContext(string publicName) { - var resourceContext = ResourceContextProvider.GetResourceContext(publicName); + ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(publicName); + if (resourceContext == null) { - throw new JsonApiSerializationException("Request body includes unknown resource type.", - $"Resource type '{publicName}' does not exist.", atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Request body includes unknown resource type.", $"Resource type '{publicName}' does not exist.", + atomicOperationIndex: AtomicOperationIndex); } return resourceContext; @@ -203,12 +226,11 @@ private void SetHasOneRelationship(IIdentifiable resource, HasOneAttribute hasOn { if (relationshipData.ManyData != null) { - throw new JsonApiSerializationException("Expected single data element for to-one relationship.", - $"Expected single data element for '{hasOneRelationship.PublicName}' relationship.", - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Expected single data element for to-one relationship.", + $"Expected single data element for '{hasOneRelationship.PublicName}' relationship.", atomicOperationIndex: AtomicOperationIndex); } - var rightResource = CreateRightResource(hasOneRelationship, relationshipData.SingleData); + IIdentifiable rightResource = CreateRightResource(hasOneRelationship, relationshipData.SingleData); hasOneRelationship.SetValue(resource, rightResource); // depending on if this base parser is used client-side or server-side, @@ -219,40 +241,34 @@ private void SetHasOneRelationship(IIdentifiable resource, HasOneAttribute hasOn /// /// Sets a HasMany relationship. /// - private void SetHasManyRelationship( - IIdentifiable resource, - HasManyAttribute hasManyRelationship, - RelationshipEntry relationshipData) + private void SetHasManyRelationship(IIdentifiable resource, HasManyAttribute hasManyRelationship, RelationshipEntry relationshipData) { if (relationshipData.ManyData == null) { - throw new JsonApiSerializationException("Expected data[] element for to-many relationship.", - $"Expected data[] element for '{hasManyRelationship.PublicName}' relationship.", - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Expected data[] element for to-many relationship.", + $"Expected data[] element for '{hasManyRelationship.PublicName}' relationship.", atomicOperationIndex: AtomicOperationIndex); } - var rightResources = relationshipData.ManyData - .Select(rio => CreateRightResource(hasManyRelationship, rio)) + HashSet rightResources = relationshipData.ManyData.Select(rio => CreateRightResource(hasManyRelationship, rio)) .ToHashSet(IdentifiableComparer.Instance); - var convertedCollection = TypeHelper.CopyToTypedCollection(rightResources, hasManyRelationship.Property.PropertyType); + IEnumerable convertedCollection = TypeHelper.CopyToTypedCollection(rightResources, hasManyRelationship.Property.PropertyType); hasManyRelationship.SetValue(resource, convertedCollection); AfterProcessField(resource, hasManyRelationship, relationshipData); } - private IIdentifiable CreateRightResource(RelationshipAttribute relationship, - ResourceIdentifierObject resourceIdentifierObject) + private IIdentifiable CreateRightResource(RelationshipAttribute relationship, ResourceIdentifierObject resourceIdentifierObject) { if (resourceIdentifierObject != null) { AssertHasType(resourceIdentifierObject, relationship); AssertHasIdOrLid(resourceIdentifierObject, relationship); - var rightResourceContext = GetExistingResourceContext(resourceIdentifierObject.Type); + ResourceContext rightResourceContext = GetExistingResourceContext(resourceIdentifierObject.Type); AssertRightTypeIsCompatible(rightResourceContext, relationship); - var rightInstance = ResourceFactory.CreateInstance(rightResourceContext.ResourceType); + IIdentifiable rightInstance = ResourceFactory.CreateInstance(rightResourceContext.ResourceType); rightInstance.StringId = resourceIdentifierObject.Id; rightInstance.LocalId = resourceIdentifierObject.Lid; @@ -267,12 +283,11 @@ private void AssertHasType(ResourceIdentifierObject resourceIdentifierObject, Re { if (resourceIdentifierObject.Type == null) { - var details = relationship != null + string details = relationship != null ? $"Expected 'type' element in '{relationship.PublicName}' relationship." : "Expected 'type' element in 'data' element."; - throw new JsonApiSerializationException("Request body must include 'type' element.", details, - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Request body must include 'type' element.", details, atomicOperationIndex: AtomicOperationIndex); } } @@ -286,8 +301,7 @@ private void AssertHasIdOrLid(ResourceIdentifierObject resourceIdentifierObject, if (hasNone || hasBoth) { throw new JsonApiSerializationException("Request body must include 'id' or 'lid' element.", - $"Expected 'id' or 'lid' element in '{relationship.PublicName}' relationship.", - atomicOperationIndex: AtomicOperationIndex); + $"Expected 'id' or 'lid' element in '{relationship.PublicName}' relationship.", atomicOperationIndex: AtomicOperationIndex); } } else @@ -295,8 +309,7 @@ private void AssertHasIdOrLid(ResourceIdentifierObject resourceIdentifierObject, if (resourceIdentifierObject.Id == null) { throw new JsonApiSerializationException("Request body must include 'id' element.", - $"Expected 'id' element in '{relationship.PublicName}' relationship.", - atomicOperationIndex: AtomicOperationIndex); + $"Expected 'id' element in '{relationship.PublicName}' relationship.", atomicOperationIndex: AtomicOperationIndex); } AssertHasNoLid(resourceIdentifierObject); @@ -308,8 +321,7 @@ private void AssertHasNoLid(ResourceIdentifierObject resourceIdentifierObject) { if (resourceIdentifierObject.Lid != null) { - throw new JsonApiSerializationException("Local IDs cannot be used at this endpoint.", null, - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Local IDs cannot be used at this endpoint.", null, atomicOperationIndex: AtomicOperationIndex); } } @@ -332,7 +344,7 @@ private object ConvertAttrValue(object newValue, Type targetType) } // the attribute value is a native C# type. - var convertedValue = TypeHelper.ConvertType(newValue, targetType); + object convertedValue = TypeHelper.ConvertType(newValue, targetType); return convertedValue; } diff --git a/src/JsonApiDotNetCore/Serialization/BaseSerializer.cs b/src/JsonApiDotNetCore/Serialization/BaseSerializer.cs index d9248c56d7..33ebf99f64 100644 --- a/src/JsonApiDotNetCore/Serialization/BaseSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/BaseSerializer.cs @@ -10,8 +10,8 @@ namespace JsonApiDotNetCore.Serialization { /// - /// Abstract base class for serialization. - /// Uses to convert resources into s and wraps them in a . + /// Abstract base class for serialization. Uses to convert resources into s and wraps + /// them in a . /// public abstract class BaseSerializer { @@ -25,49 +25,74 @@ protected BaseSerializer(IResourceObjectBuilder resourceObjectBuilder) } /// - /// Builds a for . - /// Adds the attributes and relationships that are enlisted in and . + /// Builds a for . Adds the attributes and relationships that are enlisted in + /// and . /// - /// Resource to build a for. - /// Attributes to include in the building process. - /// Relationships to include in the building process. - /// The resource object that was built. - protected Document Build(IIdentifiable resource, IReadOnlyCollection attributes, IReadOnlyCollection relationships) + /// + /// Resource to build a for. + /// + /// + /// Attributes to include in the building process. + /// + /// + /// Relationships to include in the building process. + /// + /// + /// The resource object that was built. + /// + protected Document Build(IIdentifiable resource, IReadOnlyCollection attributes, + IReadOnlyCollection relationships) { if (resource == null) { return new Document(); } - return new Document { Data = ResourceObjectBuilder.Build(resource, attributes, relationships) }; + return new Document + { + Data = ResourceObjectBuilder.Build(resource, attributes, relationships) + }; } /// - /// Builds a for . - /// Adds the attributes and relationships that are enlisted in and . + /// Builds a for . Adds the attributes and relationships that are enlisted in + /// and . /// - /// Resource to build a for. - /// Attributes to include in the building process. - /// Relationships to include in the building process. - /// The resource object that was built. - protected Document Build(IReadOnlyCollection resources, IReadOnlyCollection attributes, IReadOnlyCollection relationships) + /// + /// Resource to build a for. + /// + /// + /// Attributes to include in the building process. + /// + /// + /// Relationships to include in the building process. + /// + /// + /// The resource object that was built. + /// + protected Document Build(IReadOnlyCollection resources, IReadOnlyCollection attributes, + IReadOnlyCollection relationships) { ArgumentGuard.NotNull(resources, nameof(resources)); var data = new List(); + foreach (IIdentifiable resource in resources) { data.Add(ResourceObjectBuilder.Build(resource, attributes, relationships)); } - return new Document { Data = data }; + return new Document + { + Data = data + }; } protected string SerializeObject(object value, JsonSerializerSettings defaultSettings, Action changeSerializer = null) { ArgumentGuard.NotNull(defaultSettings, nameof(defaultSettings)); - JsonSerializer serializer = JsonSerializer.CreateDefault(defaultSettings); + var serializer = JsonSerializer.CreateDefault(defaultSettings); changeSerializer?.Invoke(serializer); using var stringWriter = new StringWriter(); diff --git a/src/JsonApiDotNetCore/Serialization/Building/IIncludedResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/IIncludedResourceObjectBuilder.cs index 88a1b21afc..3a49f0d413 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/IIncludedResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/IIncludedResourceObjectBuilder.cs @@ -13,8 +13,8 @@ public interface IIncludedResourceObjectBuilder IList Build(); /// - /// Extracts the included resources from using the - /// (arbitrarily deeply nested) included relationships in . + /// Extracts the included resources from using the (arbitrarily deeply nested) included relationships in + /// . /// void IncludeRelationshipChain(IReadOnlyCollection inclusionChain, IIdentifiable rootResource); } diff --git a/src/JsonApiDotNetCore/Serialization/Building/ILinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/ILinkBuilder.cs index 39137d2612..17a492f9b2 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/ILinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/ILinkBuilder.cs @@ -20,7 +20,7 @@ public interface ILinkBuilder ResourceLinks GetResourceLinks(string resourceName, string id); /// - /// Builds the links object that is included in the values of the . + /// Builds the links object that is included in the values of the . /// RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable parent); } diff --git a/src/JsonApiDotNetCore/Serialization/Building/IMetaBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/IMetaBuilder.cs index 0a83126407..1e668feca5 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/IMetaBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/IMetaBuilder.cs @@ -10,8 +10,8 @@ namespace JsonApiDotNetCore.Serialization.Building public interface IMetaBuilder { /// - /// Merges the specified dictionary with existing key/value pairs. In the event of a key collision, - /// the value from the specified dictionary will overwrite the existing one. + /// Merges the specified dictionary with existing key/value pairs. In the event of a key collision, the value from the specified dictionary will + /// overwrite the existing one. /// void Add(IReadOnlyDictionary values); diff --git a/src/JsonApiDotNetCore/Serialization/Building/IResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/IResourceObjectBuilder.cs index 4dea18abdb..ff182c2dab 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/IResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/IResourceObjectBuilder.cs @@ -6,19 +6,26 @@ namespace JsonApiDotNetCore.Serialization.Building { /// - /// Responsible for converting resources into s - /// given a collection of attributes and relationships. - /// + /// Responsible for converting resources into s given a collection of attributes and relationships. + /// public interface IResourceObjectBuilder { /// - /// Converts into a . - /// Adds the attributes and relationships that are enlisted in and . + /// Converts into a . Adds the attributes and relationships that are enlisted in + /// and . /// - /// Resource to build a for. - /// Attributes to include in the building process. - /// Relationships to include in the building process. - /// The resource object that was built. + /// + /// Resource to build a for. + /// + /// + /// Attributes to include in the building process. + /// + /// + /// Relationships to include in the building process. + /// + /// + /// The resource object that was built. + /// ResourceObject Build(IIdentifiable resource, IReadOnlyCollection attributes, IReadOnlyCollection relationships); } } diff --git a/src/JsonApiDotNetCore/Serialization/Building/IResourceObjectBuilderSettingsProvider.cs b/src/JsonApiDotNetCore/Serialization/Building/IResourceObjectBuilderSettingsProvider.cs index 5edc8801d4..0fcaefd32a 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/IResourceObjectBuilderSettingsProvider.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/IResourceObjectBuilderSettingsProvider.cs @@ -1,7 +1,7 @@ namespace JsonApiDotNetCore.Serialization.Building { /// - /// Service that provides the server serializer with . + /// Service that provides the server serializer with . /// public interface IResourceObjectBuilderSettingsProvider { diff --git a/src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs index 99ee0c2e7d..7db926c6da 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -20,12 +21,9 @@ public class IncludedResourceObjectBuilder : ResourceObjectBuilder, IIncludedRes private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor; private readonly SparseFieldSetCache _sparseFieldSetCache; - public IncludedResourceObjectBuilder(IFieldsToSerialize fieldsToSerialize, - ILinkBuilder linkBuilder, - IResourceContextProvider resourceContextProvider, - IEnumerable constraintProviders, - IResourceDefinitionAccessor resourceDefinitionAccessor, - IResourceObjectBuilderSettingsProvider settingsProvider) + public IncludedResourceObjectBuilder(IFieldsToSerialize fieldsToSerialize, ILinkBuilder linkBuilder, IResourceContextProvider resourceContextProvider, + IEnumerable constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor, + IResourceObjectBuilderSettingsProvider settingsProvider) : base(resourceContextProvider, settingsProvider.Get()) { ArgumentGuard.NotNull(fieldsToSerialize, nameof(fieldsToSerialize)); @@ -46,7 +44,7 @@ public IList Build() if (_included.Any()) { // cleans relationship dictionaries and adds links of resources. - foreach (var resourceObject in _included) + foreach (ResourceObject resourceObject in _included) { if (resourceObject.Relationships != null) { @@ -55,17 +53,19 @@ public IList Build() resourceObject.Links = _linkBuilder.GetResourceLinks(resourceObject.Type, resourceObject.Id); } + return _included.ToArray(); } + return null; } private void UpdateRelationships(ResourceObject resourceObject) { - foreach (var relationshipName in resourceObject.Relationships.Keys.ToArray()) + foreach (string relationshipName in resourceObject.Relationships.Keys.ToArray()) { - var resourceContext = ResourceContextProvider.GetResourceContext(resourceObject.Type); - var relationship = resourceContext.Relationships.Single(rel => rel.PublicName == relationshipName); + ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(resourceObject.Type); + RelationshipAttribute relationship = resourceContext.Relationships.Single(rel => rel.PublicName == relationshipName); if (!IsRelationshipInSparseFieldSet(relationship)) { @@ -78,26 +78,25 @@ private void UpdateRelationships(ResourceObject resourceObject) private static IDictionary PruneRelationshipEntries(ResourceObject resourceObject) { - var pruned = resourceObject.Relationships - .Where(pair => pair.Value.IsPopulated || pair.Value.Links != null) + Dictionary pruned = resourceObject.Relationships.Where(pair => pair.Value.IsPopulated || pair.Value.Links != null) .ToDictionary(pair => pair.Key, pair => pair.Value); - + return !pruned.Any() ? null : pruned; } private bool IsRelationshipInSparseFieldSet(RelationshipAttribute relationship) { - var resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType); + ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType); - var fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); + IReadOnlyCollection fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); return fieldSet.Contains(relationship); } - /// + /// public override ResourceObject Build(IIdentifiable resource, IReadOnlyCollection attributes = null, IReadOnlyCollection relationships = null) { - var resourceObject = base.Build(resource, attributes, relationships); + ResourceObject resourceObject = base.Build(resource, attributes, relationships); resourceObject.Meta = _resourceDefinitionAccessor.GetMeta(resource.GetType(), resource); @@ -113,9 +112,9 @@ public void IncludeRelationshipChain(IReadOnlyCollection // We don't have to build a resource object for the root resource because // this one is already encoded in the documents primary data, so we process the chain // starting from the first related resource. - var relationship = inclusionChain.First(); - var chainRemainder = ShiftChain(inclusionChain); - var related = relationship.GetValue(rootResource); + RelationshipAttribute relationship = inclusionChain.First(); + IList chainRemainder = ShiftChain(inclusionChain); + object related = relationship.GetValue(rootResource); ProcessChain(related, chainRemainder); } @@ -137,20 +136,22 @@ private void ProcessChain(object related, IList inclusion private void ProcessRelationship(IIdentifiable parent, IList inclusionChain) { // get the resource object for parent. - var resourceObject = GetOrBuildResourceObject(parent); + ResourceObject resourceObject = GetOrBuildResourceObject(parent); + if (!inclusionChain.Any()) { return; } - var nextRelationship = inclusionChain.First(); - var chainRemainder = inclusionChain.ToList(); + RelationshipAttribute nextRelationship = inclusionChain.First(); + List chainRemainder = inclusionChain.ToList(); chainRemainder.RemoveAt(0); - var nextRelationshipName = nextRelationship.PublicName; - var relationshipsObject = resourceObject.Relationships; + string nextRelationshipName = nextRelationship.PublicName; + IDictionary relationshipsObject = resourceObject.Relationships; + // add the relationship entry in the relationship object. - if (!relationshipsObject.TryGetValue(nextRelationshipName, out var relationshipEntry)) + if (!relationshipsObject.TryGetValue(nextRelationshipName, out RelationshipEntry relationshipEntry)) { relationshipEntry = GetRelationshipData(nextRelationship, parent); relationshipsObject[nextRelationshipName] = relationshipEntry; @@ -161,44 +162,48 @@ private void ProcessRelationship(IIdentifiable parent, IList ShiftChain(IReadOnlyCollection chain) { - var chainRemainder = chain.ToList(); + List chainRemainder = chain.ToList(); chainRemainder.RemoveAt(0); return chainRemainder; } /// - /// We only need an empty relationship object entry here. It will be populated in the - /// ProcessRelationships method. + /// We only need an empty relationship object entry here. It will be populated in the ProcessRelationships method. /// protected override RelationshipEntry GetRelationshipData(RelationshipAttribute relationship, IIdentifiable resource) { ArgumentGuard.NotNull(relationship, nameof(relationship)); ArgumentGuard.NotNull(resource, nameof(resource)); - return new RelationshipEntry { Links = _linkBuilder.GetRelationshipLinks(relationship, resource) }; + return new RelationshipEntry + { + Links = _linkBuilder.GetRelationshipLinks(relationship, resource) + }; } /// - /// Gets the resource object for by searching the included list. - /// If it was not already built, it is constructed and added to the inclusion list. + /// Gets the resource object for by searching the included list. If it was not already built, it is constructed and added to + /// the inclusion list. /// private ResourceObject GetOrBuildResourceObject(IIdentifiable parent) { - var type = parent.GetType(); - var resourceName = ResourceContextProvider.GetResourceContext(type).PublicName; - var entry = _included.SingleOrDefault(ro => ro.Type == resourceName && ro.Id == parent.StringId); + Type type = parent.GetType(); + string resourceName = ResourceContextProvider.GetResourceContext(type).PublicName; + ResourceObject entry = _included.SingleOrDefault(ro => ro.Type == resourceName && ro.Id == parent.StringId); + if (entry == null) { entry = Build(parent, _fieldsToSerialize.GetAttributes(type), _fieldsToSerialize.GetRelationships(type)); _included.Add(entry); } + return entry; } } diff --git a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs index db15f31300..50ac9f5851 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs @@ -28,11 +28,8 @@ public class LinkBuilder : ILinkBuilder private readonly IJsonApiRequest _request; private readonly IPaginationContext _paginationContext; - public LinkBuilder(IJsonApiOptions options, - IJsonApiRequest request, - IPaginationContext paginationContext, - IResourceContextProvider provider, - IRequestQueryStringAccessor queryStringAccessor) + public LinkBuilder(IJsonApiOptions options, IJsonApiRequest request, IPaginationContext paginationContext, IResourceContextProvider provider, + IRequestQueryStringAccessor queryStringAccessor) { ArgumentGuard.NotNull(options, nameof(options)); ArgumentGuard.NotNull(request, nameof(request)); @@ -53,19 +50,23 @@ public TopLevelLinks GetTopLevelLinks() ResourceContext resourceContext = _request.PrimaryResource; TopLevelLinks topLevelLinks = null; + if (ShouldAddTopLevelLink(resourceContext, LinkTypes.Self)) { - topLevelLinks = new TopLevelLinks {Self = GetSelfTopLevelLink(resourceContext, null)}; + topLevelLinks = new TopLevelLinks + { + Self = GetSelfTopLevelLink(resourceContext, null) + }; } if (ShouldAddTopLevelLink(resourceContext, LinkTypes.Related) && _request.Kind == EndpointKind.Relationship) - { + { topLevelLinks ??= new TopLevelLinks(); topLevelLinks.Related = GetRelatedRelationshipLink(_request.PrimaryResource.PublicName, _request.PrimaryId, _request.Relationship.PublicName); } if (ShouldAddTopLevelLink(resourceContext, LinkTypes.Paging) && _paginationContext.PageSize != null && _request.IsCollection) - { + { SetPageLinks(resourceContext, topLevelLinks ??= new TopLevelLinks()); } @@ -73,9 +74,8 @@ public TopLevelLinks GetTopLevelLinks() } /// - /// Checks if the top-level should be added by first checking - /// configuration on the , and if not configured, by checking with the - /// global configuration in . + /// Checks if the top-level should be added by first checking configuration on the , and if not + /// configured, by checking with the global configuration in . /// private bool ShouldAddTopLevelLink(ResourceContext resourceContext, LinkTypes link) { @@ -142,7 +142,7 @@ private string GetSelfTopLevelLink(ResourceContext resourceContext, Action> updateAction) { - var parameters = _queryStringAccessor.Query.ToDictionary(pair => pair.Key, pair => pair.Value.ToString()); + Dictionary parameters = _queryStringAccessor.Query.ToDictionary(pair => pair.Key, pair => pair.Value.ToString()); updateAction?.Invoke(parameters); string queryString = QueryString.Create(parameters).Value; @@ -158,13 +158,12 @@ private string GetPageLink(ResourceContext resourceContext, int pageOffset, Page { return GetSelfTopLevelLink(resourceContext, parameters => { - var existingPageSizeParameterValue = parameters.ContainsKey(PageSizeParameterName) - ? parameters[PageSizeParameterName] - : null; + string existingPageSizeParameterValue = parameters.ContainsKey(PageSizeParameterName) ? parameters[PageSizeParameterName] : null; PageSize newTopPageSize = Equals(pageSize, _options.DefaultPageSize) ? null : pageSize; string newPageSizeParameterValue = ChangeTopPageSize(existingPageSizeParameterValue, newTopPageSize); + if (newPageSizeParameterValue == null) { parameters.Remove(PageSizeParameterName); @@ -187,8 +186,8 @@ private string GetPageLink(ResourceContext resourceContext, int pageOffset, Page private string ChangeTopPageSize(string pageSizeParameterValue, PageSize topPageSize) { - var elements = ParsePageSizeExpression(pageSizeParameterValue); - var elementInTopScopeIndex = elements.FindIndex(expression => expression.Scope == null); + List elements = ParsePageSizeExpression(pageSizeParameterValue); + int elementInTopScopeIndex = elements.FindIndex(expression => expression.Scope == null); if (topPageSize != null) { @@ -211,7 +210,7 @@ private string ChangeTopPageSize(string pageSizeParameterValue, PageSize topPage } } - var parameterValue = string.Join(',', + string parameterValue = string.Join(',', elements.Select(expression => expression.Scope == null ? expression.Value.ToString() : $"{expression.Scope}:{expression.Value}")); return parameterValue == string.Empty ? null : parameterValue; @@ -224,10 +223,10 @@ private List ParsePageSizeExpressio return new List(); } - var requestResource = _request.SecondaryResource ?? _request.PrimaryResource; + ResourceContext requestResource = _request.SecondaryResource ?? _request.PrimaryResource; var parser = new PaginationParser(_provider); - var paginationExpression = parser.Parse(pageSizeParameterValue, requestResource); + PaginationQueryStringValueExpression paginationExpression = parser.Parse(pageSizeParameterValue, requestResource); return paginationExpression.Elements.ToList(); } @@ -238,10 +237,14 @@ public ResourceLinks GetResourceLinks(string resourceName, string id) ArgumentGuard.NotNull(resourceName, nameof(resourceName)); ArgumentGuard.NotNull(id, nameof(id)); - var resourceContext = _provider.GetResourceContext(resourceName); + ResourceContext resourceContext = _provider.GetResourceContext(resourceName); + if (ShouldAddResourceLink(resourceContext, LinkTypes.Self)) { - return new ResourceLinks { Self = GetSelfResourceLink(resourceName, id) }; + return new ResourceLinks + { + Self = GetSelfResourceLink(resourceName, id) + }; } return null; @@ -253,12 +256,16 @@ public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship ArgumentGuard.NotNull(relationship, nameof(relationship)); ArgumentGuard.NotNull(parent, nameof(parent)); - var parentResourceContext = _provider.GetResourceContext(parent.GetType()); - var childNavigation = relationship.PublicName; + ResourceContext parentResourceContext = _provider.GetResourceContext(parent.GetType()); + string childNavigation = relationship.PublicName; RelationshipLinks links = null; + if (ShouldAddRelationshipLink(parentResourceContext, relationship, LinkTypes.Related)) { - links = new RelationshipLinks { Related = GetRelatedRelationshipLink(parentResourceContext.PublicName, parent.StringId, childNavigation) }; + links = new RelationshipLinks + { + Related = GetRelatedRelationshipLink(parentResourceContext.PublicName, parent.StringId, childNavigation) + }; } if (ShouldAddRelationshipLink(parentResourceContext, relationship, LinkTypes.Self)) @@ -270,7 +277,6 @@ public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship return links; } - private string GetSelfRelationshipLink(string parent, string parentId, string navigation) { return $"{_request.BasePath}/{parent}/{parentId}/relationships/{navigation}"; @@ -287,9 +293,8 @@ private string GetRelatedRelationshipLink(string parent, string parentId, string } /// - /// Checks if the resource object level should be added by first checking - /// configuration on the , and if not configured, by checking with the - /// global configuration in . + /// Checks if the resource object level should be added by first checking configuration on the , + /// and if not configured, by checking with the global configuration in . /// private bool ShouldAddResourceLink(ResourceContext resourceContext, LinkTypes link) { @@ -302,14 +307,14 @@ private bool ShouldAddResourceLink(ResourceContext resourceContext, LinkTypes li { return resourceContext.ResourceLinks.HasFlag(link); } + return _options.ResourceLinks.HasFlag(link); } /// - /// Checks if the resource object level should be added by first checking - /// configuration on the attribute, if not configured by checking - /// the , and if not configured by checking with the - /// global configuration in . + /// Checks if the resource object level should be added by first checking configuration on the + /// attribute, if not configured by checking the , and if not configured by checking with the global configuration in + /// . /// private bool ShouldAddRelationshipLink(ResourceContext resourceContext, RelationshipAttribute relationship, LinkTypes link) { @@ -317,6 +322,7 @@ private bool ShouldAddRelationshipLink(ResourceContext resourceContext, Relation { return relationship.Links.HasFlag(link); } + if (resourceContext.RelationshipLinks != LinkTypes.NotConfigured) { return resourceContext.RelationshipLinks.HasFlag(link); diff --git a/src/JsonApiDotNetCore/Serialization/Building/MetaBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/MetaBuilder.cs index d641910969..6d35b8385b 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/MetaBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/MetaBuilder.cs @@ -32,9 +32,7 @@ public void Add(IReadOnlyDictionary values) { ArgumentGuard.NotNull(values, nameof(values)); - _meta = values.Keys.Union(_meta.Keys) - .ToDictionary(key => key, - key => values.ContainsKey(key) ? values[key] : _meta[key]); + _meta = values.Keys.Union(_meta.Keys).ToDictionary(key => key, key => values.ContainsKey(key) ? values[key] : _meta[key]); } /// @@ -47,7 +45,8 @@ public IDictionary Build() _meta.Add(key, _paginationContext.TotalResourceCount); } - var extraMeta = _responseMeta.GetMeta(); + IReadOnlyDictionary extraMeta = _responseMeta.GetMeta(); + if (extraMeta != null) { Add(extraMeta); diff --git a/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs index b67e04e410..3e1c782c18 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs @@ -9,12 +9,12 @@ namespace JsonApiDotNetCore.Serialization.Building { - /// + /// [PublicAPI] public class ResourceObjectBuilder : IResourceObjectBuilder { - protected IResourceContextProvider ResourceContextProvider { get; } private readonly ResourceObjectBuilderSettings _settings; + protected IResourceContextProvider ResourceContextProvider { get; } public ResourceObjectBuilder(IResourceContextProvider resourceContextProvider, ResourceObjectBuilderSettings settings) { @@ -25,20 +25,26 @@ public ResourceObjectBuilder(IResourceContextProvider resourceContextProvider, R _settings = settings; } - /// - public virtual ResourceObject Build(IIdentifiable resource, IReadOnlyCollection attributes = null, IReadOnlyCollection relationships = null) + /// + public virtual ResourceObject Build(IIdentifiable resource, IReadOnlyCollection attributes = null, + IReadOnlyCollection relationships = null) { ArgumentGuard.NotNull(resource, nameof(resource)); - var resourceContext = ResourceContextProvider.GetResourceContext(resource.GetType()); + ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(resource.GetType()); // populating the top-level "type" and "id" members. - var resourceObject = new ResourceObject { Type = resourceContext.PublicName, Id = resource.StringId }; + var resourceObject = new ResourceObject + { + Type = resourceContext.PublicName, + Id = resource.StringId + }; // populating the top-level "attribute" member of a resource object. never include "id" as an attribute if (attributes != null) { - var attributesWithoutId = attributes.Where(attr => attr.Property.Name != nameof(Identifiable.Id)).ToArray(); + AttrAttribute[] attributesWithoutId = attributes.Where(attr => attr.Property.Name != nameof(Identifiable.Id)).ToArray(); + if (attributesWithoutId.Any()) { ProcessAttributes(resource, attributesWithoutId, resourceObject); @@ -55,22 +61,23 @@ public virtual ResourceObject Build(IIdentifiable resource, IReadOnlyCollection< } /// - /// Builds the entries of the "relationships - /// objects". The default behavior is to just construct a resource linkage - /// with the "data" field populated with "single" or "many" data. - /// Depending on the requirements of the implementation (server or client serializer), - /// this may be overridden. + /// Builds the entries of the "relationships objects". The default behavior is to just construct a resource linkage with + /// the "data" field populated with "single" or "many" data. Depending on the requirements of the implementation (server or client serializer), this may + /// be overridden. /// protected virtual RelationshipEntry GetRelationshipData(RelationshipAttribute relationship, IIdentifiable resource) { ArgumentGuard.NotNull(relationship, nameof(relationship)); ArgumentGuard.NotNull(resource, nameof(resource)); - return new RelationshipEntry { Data = GetRelatedResourceLinkage(relationship, resource) }; + return new RelationshipEntry + { + Data = GetRelatedResourceLinkage(relationship, resource) + }; } /// - /// Gets the value for the property. + /// Gets the value for the property. /// protected object GetRelatedResourceLinkage(RelationshipAttribute relationship, IIdentifiable resource) { @@ -78,12 +85,12 @@ protected object GetRelatedResourceLinkage(RelationshipAttribute relationship, I ArgumentGuard.NotNull(resource, nameof(resource)); return relationship is HasOneAttribute hasOne - ? (object) GetRelatedResourceLinkageForHasOne(hasOne, resource) - : GetRelatedResourceLinkageForHasMany((HasManyAttribute) relationship, resource); + ? (object)GetRelatedResourceLinkageForHasOne(hasOne, resource) + : GetRelatedResourceLinkageForHasMany((HasManyAttribute)relationship, resource); } /// - /// Builds a for a HasOne relationship. + /// Builds a for a HasOne relationship. /// private ResourceIdentifierObject GetRelatedResourceLinkageForHasOne(HasOneAttribute relationship, IIdentifiable resource) { @@ -98,17 +105,18 @@ private ResourceIdentifierObject GetRelatedResourceLinkageForHasOne(HasOneAttrib } /// - /// Builds the s for a HasMany relationship. + /// Builds the s for a HasMany relationship. /// private IList GetRelatedResourceLinkageForHasMany(HasManyAttribute relationship, IIdentifiable resource) { - var value = relationship.GetValue(resource); - var relatedResources = TypeHelper.ExtractResources(value); - + object value = relationship.GetValue(resource); + ICollection relatedResources = TypeHelper.ExtractResources(value); + var manyData = new List(); + if (relatedResources != null) { - foreach (var relatedResource in relatedResources) + foreach (IIdentifiable relatedResource in relatedResources) { manyData.Add(GetResourceIdentifier(relatedResource)); } @@ -118,11 +126,12 @@ private IList GetRelatedResourceLinkageForHasMany(HasM } /// - /// Creates a from . + /// Creates a from . /// private ResourceIdentifierObject GetResourceIdentifier(IIdentifiable resource) { - var resourceName = ResourceContextProvider.GetResourceContext(resource.GetType()).PublicName; + string resourceName = ResourceContextProvider.GetResourceContext(resource.GetType()).PublicName; + return new ResourceIdentifierObject { Type = resourceName, @@ -135,9 +144,10 @@ private ResourceIdentifierObject GetResourceIdentifier(IIdentifiable resource) /// private void ProcessRelationships(IIdentifiable resource, IEnumerable relationships, ResourceObject ro) { - foreach (var rel in relationships) + foreach (RelationshipAttribute rel in relationships) { - var relData = GetRelationshipData(rel, resource); + RelationshipEntry relData = GetRelationshipData(rel, resource); + if (relData != null) { (ro.Relationships ??= new Dictionary()).Add(rel.PublicName, relData); @@ -151,7 +161,8 @@ private void ProcessRelationships(IIdentifiable resource, IEnumerable attributes, ResourceObject ro) { ro.Attributes = new Dictionary(); - foreach (var attr in attributes) + + foreach (AttrAttribute attr in attributes) { object value = attr.GetValue(resource); @@ -160,7 +171,8 @@ private void ProcessAttributes(IIdentifiable resource, IEnumerable - /// Options used to configure how fields of a model get serialized into - /// a JSON:API . + /// Options used to configure how fields of a model get serialized into a JSON:API . /// [PublicAPI] public sealed class ResourceObjectBuilderSettings @@ -14,8 +13,7 @@ public sealed class ResourceObjectBuilderSettings public NullValueHandling SerializerNullValueHandling { get; } public DefaultValueHandling SerializerDefaultValueHandling { get; } - public ResourceObjectBuilderSettings( - NullValueHandling serializerNullValueHandling = NullValueHandling.Include, + public ResourceObjectBuilderSettings(NullValueHandling serializerNullValueHandling = NullValueHandling.Include, DefaultValueHandling serializerDefaultValueHandling = DefaultValueHandling.Include) { SerializerNullValueHandling = serializerNullValueHandling; diff --git a/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilderSettingsProvider.cs b/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilderSettingsProvider.cs index 83422527ed..d28a7591eb 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilderSettingsProvider.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilderSettingsProvider.cs @@ -4,8 +4,8 @@ namespace JsonApiDotNetCore.Serialization.Building { /// - /// This implementation of the behavior provider reads the defaults/nulls query string parameters that - /// can, if provided, override the settings in . + /// This implementation of the behavior provider reads the defaults/nulls query string parameters that can, if provided, override the settings in + /// . /// public sealed class ResourceObjectBuilderSettingsProvider : IResourceObjectBuilderSettingsProvider { diff --git a/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs index eb3e445c94..e05e4478ee 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs @@ -22,12 +22,9 @@ public class ResponseResourceObjectBuilder : ResourceObjectBuilder private readonly SparseFieldSetCache _sparseFieldSetCache; private RelationshipAttribute _requestRelationship; - public ResponseResourceObjectBuilder(ILinkBuilder linkBuilder, - IIncludedResourceObjectBuilder includedBuilder, - IEnumerable constraintProviders, - IResourceContextProvider resourceContextProvider, - IResourceDefinitionAccessor resourceDefinitionAccessor, - IResourceObjectBuilderSettingsProvider settingsProvider) + public ResponseResourceObjectBuilder(ILinkBuilder linkBuilder, IIncludedResourceObjectBuilder includedBuilder, + IEnumerable constraintProviders, IResourceContextProvider resourceContextProvider, + IResourceDefinitionAccessor resourceDefinitionAccessor, IResourceObjectBuilderSettingsProvider settingsProvider) : base(resourceContextProvider, settingsProvider.Get()) { ArgumentGuard.NotNull(linkBuilder, nameof(linkBuilder)); @@ -51,11 +48,11 @@ public RelationshipEntry Build(IIdentifiable resource, RelationshipAttribute req return GetRelationshipData(requestRelationship, resource); } - /// + /// public override ResourceObject Build(IIdentifiable resource, IReadOnlyCollection attributes = null, IReadOnlyCollection relationships = null) { - var resourceObject = base.Build(resource, attributes, relationships); + ResourceObject resourceObject = base.Build(resource, attributes, relationships); resourceObject.Meta = _resourceDefinitionAccessor.GetMeta(resource.GetType(), resource); @@ -63,11 +60,9 @@ public override ResourceObject Build(IIdentifiable resource, IReadOnlyCollection } /// - /// Builds the values of the relationships object on a resource object. - /// The server serializer only populates the "data" member when the relationship is included, - /// and adds links unless these are turned off. This means that if a relationship is not included - /// and links are turned off, the entry would be completely empty, ie { }, which is not conform - /// JSON:API spec. In that case we return null which will omit the entry from the output. + /// Builds the values of the relationships object on a resource object. The server serializer only populates the "data" member when the relationship is + /// included, and adds links unless these are turned off. This means that if a relationship is not included and links are turned off, the entry would be + /// completely empty, ie { }, which is not conform JSON:API spec. In that case we return null which will omit the entry from the output. /// protected override RelationshipEntry GetRelationshipData(RelationshipAttribute relationship, IIdentifiable resource) { @@ -76,12 +71,14 @@ protected override RelationshipEntry GetRelationshipData(RelationshipAttribute r RelationshipEntry relationshipEntry = null; List> relationshipChains = null; + if (Equals(relationship, _requestRelationship) || ShouldInclude(relationship, out relationshipChains)) { relationshipEntry = base.GetRelationshipData(relationship, resource); + if (relationshipChains != null && relationshipEntry.HasResource) { - foreach (var chain in relationshipChains) + foreach (IReadOnlyCollection chain in relationshipChains) { // traverses (recursively) and extracts all (nested) related resources for the current inclusion chain. _includedBuilder.IncludeRelationshipChain(chain, resource); @@ -94,7 +91,8 @@ protected override RelationshipEntry GetRelationshipData(RelationshipAttribute r return null; } - var links = _linkBuilder.GetRelationshipLinks(relationship, resource); + RelationshipLinks links = _linkBuilder.GetRelationshipLinks(relationship, resource); + if (links != null) { // if relationshipLinks should be built for this entry, populate the "links" field. @@ -109,22 +107,22 @@ protected override RelationshipEntry GetRelationshipData(RelationshipAttribute r private bool IsRelationshipInSparseFieldSet(RelationshipAttribute relationship) { - var resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType); + ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType); - var fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); + IReadOnlyCollection fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); return fieldSet.Contains(relationship); } /// - /// Inspects the included relationship chains (see - /// to see if should be included or not. + /// Inspects the included relationship chains (see to see if should be + /// included or not. /// private bool ShouldInclude(RelationshipAttribute relationship, out List> inclusionChain) { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var chains = _constraintProviders + ResourceFieldChainExpression[] chains = _constraintProviders .SelectMany(provider => provider.GetConstraints()) .Select(expressionInScope => expressionInScope.Expression) .OfType() @@ -136,7 +134,7 @@ private bool ShouldInclude(RelationshipAttribute relationship, out List>(); - foreach (var chain in chains) + foreach (ResourceFieldChainExpression chain in chains) { if (chain.Fields.First().Equals(relationship)) { diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/DeserializedResponseBase.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/DeserializedResponseBase.cs index 14587a1cb2..5f7186ddcc 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/DeserializedResponseBase.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/DeserializedResponseBase.cs @@ -5,7 +5,10 @@ namespace JsonApiDotNetCore.Serialization.Client.Internal { /// Base class for "single data" and "many data" deserialized responses. - /// TODO: Currently and + /// TODO: Currently + /// + /// and + /// /// information is ignored by the serializer. This is out of scope for now because /// it is not considered mission critical for v4. [PublicAPI] diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/IRequestSerializer.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/IRequestSerializer.cs index 86e2b8bcbf..d58d7d9957 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/IRequestSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/IRequestSerializer.cs @@ -7,36 +7,37 @@ namespace JsonApiDotNetCore.Serialization.Client.Internal { /// - /// Interface for client serializer that can be used to register with the DI container, for usage in - /// custom services or repositories. + /// Interface for client serializer that can be used to register with the DI container, for usage in custom services or repositories. /// [PublicAPI] public interface IRequestSerializer { /// - /// Creates and serializes a document for a single resource. + /// Sets the attributes that will be included in the serialized request body. You can use to + /// conveniently access the desired instances. /// - /// The serialized content - string Serialize(IIdentifiable resource); + public IReadOnlyCollection AttributesToSerialize { get; set; } /// - /// Creates and serializes a document for a collection of resources. + /// Sets the relationships that will be included in the serialized request body. You can use to + /// conveniently access the desired instances. /// - /// The serialized content - string Serialize(IReadOnlyCollection resources); + public IReadOnlyCollection RelationshipsToSerialize { get; set; } /// - /// Sets the attributes that will be included in the serialized request body. - /// You can use - /// to conveniently access the desired instances. + /// Creates and serializes a document for a single resource. /// - public IReadOnlyCollection AttributesToSerialize { get; set; } + /// + /// The serialized content + /// + string Serialize(IIdentifiable resource); /// - /// Sets the relationships that will be included in the serialized request body. - /// You can use - /// to conveniently access the desired instances. + /// Creates and serializes a document for a collection of resources. /// - public IReadOnlyCollection RelationshipsToSerialize { get; set; } + /// + /// The serialized content + /// + string Serialize(IReadOnlyCollection resources); } } diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/IResponseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/IResponseDeserializer.cs index 6d29d2779d..0a41306523 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/IResponseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/IResponseDeserializer.cs @@ -4,9 +4,8 @@ namespace JsonApiDotNetCore.Serialization.Client.Internal { /// - /// Client deserializer. Currently not used internally in JsonApiDotNetCore, - /// except for in the tests. Exposed publicly to make testing easier or to implement - /// server-to-server communication. + /// Client deserializer. Currently not used internally in JsonApiDotNetCore, except for in the tests. Exposed publicly to make testing easier or to + /// implement server-to-server communication. /// [PublicAPI] public interface IResponseDeserializer @@ -14,15 +13,25 @@ public interface IResponseDeserializer /// /// Deserializes a response with a single resource (or null) as data. /// - /// The type of the resources in the primary data. - /// The JSON to be deserialized. - SingleResponse DeserializeSingle(string body) where TResource : class, IIdentifiable; + /// + /// The type of the resources in the primary data. + /// + /// + /// The JSON to be deserialized. + /// + SingleResponse DeserializeSingle(string body) + where TResource : class, IIdentifiable; /// /// Deserializes a response with an (empty) collection of resources as data. /// - /// The type of the resources in the primary data. - /// The JSON to be deserialized. - ManyResponse DeserializeMany(string body) where TResource : class, IIdentifiable; + /// + /// The type of the resources in the primary data. + /// + /// + /// The JSON to be deserialized. + /// + ManyResponse DeserializeMany(string body) + where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/ManyResponse.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/ManyResponse.cs index c48a3f54b9..659e33d0dd 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/ManyResponse.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/ManyResponse.cs @@ -7,9 +7,12 @@ namespace JsonApiDotNetCore.Serialization.Client.Internal /// /// Represents a deserialized document with "many data". /// - /// Type of the resource(s) in the primary data. + /// + /// Type of the resource(s) in the primary data. + /// [PublicAPI] - public sealed class ManyResponse : DeserializedResponseBase where TResource : class, IIdentifiable + public sealed class ManyResponse : DeserializedResponseBase + where TResource : class, IIdentifiable { public IReadOnlyCollection Data { get; set; } } diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/RequestSerializer.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/RequestSerializer.cs index 61d3c5a2fe..73f0964a9f 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/RequestSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/RequestSerializer.cs @@ -12,17 +12,22 @@ namespace JsonApiDotNetCore.Serialization.Client.Internal { /// - /// Client serializer implementation of . + /// Client serializer implementation of . /// [PublicAPI] public class RequestSerializer : BaseSerializer, IRequestSerializer { - private Type _currentTargetedResource; private readonly IResourceGraph _resourceGraph; private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings(); + private Type _currentTargetedResource; + + /// + public IReadOnlyCollection AttributesToSerialize { get; set; } + + /// + public IReadOnlyCollection RelationshipsToSerialize { get; set; } - public RequestSerializer(IResourceGraph resourceGraph, - IResourceObjectBuilder resourceObjectBuilder) + public RequestSerializer(IResourceGraph resourceGraph, IResourceObjectBuilder resourceObjectBuilder) : base(resourceObjectBuilder) { ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); @@ -35,12 +40,12 @@ public string Serialize(IIdentifiable resource) { if (resource == null) { - var empty = Build((IIdentifiable) null, Array.Empty(), Array.Empty()); + Document empty = Build((IIdentifiable)null, Array.Empty(), Array.Empty()); return SerializeObject(empty, _jsonSerializerSettings); } _currentTargetedResource = resource.GetType(); - var document = Build(resource, GetAttributesToSerialize(resource), RelationshipsToSerialize); + Document document = Build(resource, GetAttributesToSerialize(resource), RelationshipsToSerialize); _currentTargetedResource = null; return SerializeObject(document, _jsonSerializerSettings); @@ -54,6 +59,7 @@ public string Serialize(IReadOnlyCollection resources) IIdentifiable firstResource = resources.FirstOrDefault(); Document document; + if (firstResource == null) { document = Build(resources, Array.Empty(), Array.Empty()); @@ -61,7 +67,7 @@ public string Serialize(IReadOnlyCollection resources) else { _currentTargetedResource = firstResource.GetType(); - var attributes = GetAttributesToSerialize(firstResource); + IReadOnlyCollection attributes = GetAttributesToSerialize(firstResource); document = Build(resources, attributes, RelationshipsToSerialize); _currentTargetedResource = null; @@ -70,20 +76,14 @@ public string Serialize(IReadOnlyCollection resources) return SerializeObject(document, _jsonSerializerSettings); } - /// - public IReadOnlyCollection AttributesToSerialize { get; set; } - - /// - public IReadOnlyCollection RelationshipsToSerialize { get; set; } - /// - /// By default, the client serializer includes all attributes in the result, - /// unless a list of allowed attributes was supplied using the - /// method. For any related resources, attributes are never exposed. + /// By default, the client serializer includes all attributes in the result, unless a list of allowed attributes was supplied using the + /// method. For any related resources, attributes are never exposed. /// private IReadOnlyCollection GetAttributesToSerialize(IIdentifiable resource) { - var currentResourceType = resource.GetType(); + Type currentResourceType = resource.GetType(); + if (_currentTargetedResource != currentResourceType) { // We're dealing with a relationship that is being serialized, for which diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs index a9e4232076..83c0c94059 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; @@ -10,53 +11,65 @@ namespace JsonApiDotNetCore.Serialization.Client.Internal { /// - /// Client deserializer implementation of the . + /// Client deserializer implementation of the . /// [PublicAPI] public class ResponseDeserializer : BaseDeserializer, IResponseDeserializer { - public ResponseDeserializer(IResourceContextProvider resourceContextProvider, IResourceFactory resourceFactory) : base(resourceContextProvider, resourceFactory) { } + public ResponseDeserializer(IResourceContextProvider resourceContextProvider, IResourceFactory resourceFactory) + : base(resourceContextProvider, resourceFactory) + { + } /// - public SingleResponse DeserializeSingle(string body) where TResource : class, IIdentifiable + public SingleResponse DeserializeSingle(string body) + where TResource : class, IIdentifiable { ArgumentGuard.NotNull(body, nameof(body)); - var resource = DeserializeBody(body); + object resource = DeserializeBody(body); + return new SingleResponse { Links = Document.Links, Meta = Document.Meta, - Data = (TResource) resource, + Data = (TResource)resource, JsonApi = null, Errors = null }; } /// - public ManyResponse DeserializeMany(string body) where TResource : class, IIdentifiable + public ManyResponse DeserializeMany(string body) + where TResource : class, IIdentifiable { ArgumentGuard.NotNull(body, nameof(body)); - var resources = DeserializeBody(body); + object resources = DeserializeBody(body); + return new ManyResponse { Links = Document.Links, Meta = Document.Meta, - Data = ((ICollection) resources)?.Cast().ToArray(), + Data = ((ICollection)resources)?.Cast().ToArray(), JsonApi = null, Errors = null }; } /// - /// Additional processing required for client deserialization, responsible - /// for parsing the property. When a relationship value is parsed, - /// it goes through the included list to set its attributes and relationships. + /// Additional processing required for client deserialization, responsible for parsing the property. When a relationship + /// value is parsed, it goes through the included list to set its attributes and relationships. /// - /// The resource that was constructed from the document's body. - /// The metadata for the exposed field. - /// Relationship data for . Is null when is not a . + /// + /// The resource that was constructed from the document's body. + /// + /// + /// The metadata for the exposed field. + /// + /// + /// Relationship data for . Is null when is not a . + /// protected override void AfterProcessField(IIdentifiable resource, ResourceFieldAttribute field, RelationshipEntry data = null) { ArgumentGuard.NotNull(resource, nameof(resource)); @@ -79,14 +92,14 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA if (field is HasOneAttribute hasOneAttr) { // add attributes and relationships of a parsed HasOne relationship - var rio = data.SingleData; + ResourceIdentifierObject rio = data.SingleData; hasOneAttr.SetValue(resource, rio == null ? null : ParseIncludedRelationship(rio)); } else if (field is HasManyAttribute hasManyAttr) { // add attributes and relationships of a parsed HasMany relationship - var items = data.ManyData.Select(ParseIncludedRelationship); - var values = TypeHelper.CopyToTypedCollection(items, hasManyAttr.Property.PropertyType); + IEnumerable items = data.ManyData.Select(ParseIncludedRelationship); + IEnumerable values = TypeHelper.CopyToTypedCollection(items, hasManyAttr.Property.PropertyType); hasManyAttr.SetValue(resource, values); } } @@ -97,24 +110,24 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA /// private IIdentifiable ParseIncludedRelationship(ResourceIdentifierObject relatedResourceIdentifier) { - var relatedResourceContext = ResourceContextProvider.GetResourceContext(relatedResourceIdentifier.Type); + ResourceContext relatedResourceContext = ResourceContextProvider.GetResourceContext(relatedResourceIdentifier.Type); if (relatedResourceContext == null) { throw new InvalidOperationException($"Included type '{relatedResourceIdentifier.Type}' is not a registered JSON:API resource."); } - - var relatedInstance = ResourceFactory.CreateInstance(relatedResourceContext.ResourceType); + + IIdentifiable relatedInstance = ResourceFactory.CreateInstance(relatedResourceContext.ResourceType); relatedInstance.StringId = relatedResourceIdentifier.Id; - var includedResource = GetLinkedResource(relatedResourceIdentifier); + ResourceObject includedResource = GetLinkedResource(relatedResourceIdentifier); if (includedResource != null) { SetAttributes(relatedInstance, includedResource.Attributes, relatedResourceContext.Attributes); SetRelationships(relatedInstance, includedResource.Relationships, relatedResourceContext.Relationships); } - + return relatedInstance; } @@ -126,8 +139,9 @@ private ResourceObject GetLinkedResource(ResourceIdentifierObject relatedResourc } catch (InvalidOperationException e) { - throw new InvalidOperationException("A compound document MUST NOT include more than one resource object for each type and ID pair." - + $"The duplicate pair was '{relatedResourceIdentifier.Type}, {relatedResourceIdentifier.Id}'", e); + throw new InvalidOperationException( + "A compound document MUST NOT include more than one resource object for each type and ID pair." + + $"The duplicate pair was '{relatedResourceIdentifier.Type}, {relatedResourceIdentifier.Id}'", e); } } } diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/SingleResponse.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/SingleResponse.cs index d40421567b..3359cafae6 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/SingleResponse.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/SingleResponse.cs @@ -6,10 +6,13 @@ namespace JsonApiDotNetCore.Serialization.Client.Internal /// /// Represents a deserialized document with "single data". /// - /// Type of the resource in the primary data. + /// + /// Type of the resource in the primary data. + /// [PublicAPI] - public sealed class SingleResponse : DeserializedResponseBase where TResource : class, IIdentifiable - { - public TResource Data { get; set; } + public sealed class SingleResponse : DeserializedResponseBase + where TResource : class, IIdentifiable + { + public TResource Data { get; set; } } } diff --git a/src/JsonApiDotNetCore/Serialization/FieldsToSerialize.cs b/src/JsonApiDotNetCore/Serialization/FieldsToSerialize.cs index aea6a2d1e7..763c41020a 100644 --- a/src/JsonApiDotNetCore/Serialization/FieldsToSerialize.cs +++ b/src/JsonApiDotNetCore/Serialization/FieldsToSerialize.cs @@ -19,11 +19,8 @@ public class FieldsToSerialize : IFieldsToSerialize private readonly IJsonApiRequest _request; private readonly SparseFieldSetCache _sparseFieldSetCache; - public FieldsToSerialize( - IResourceContextProvider resourceContextProvider, - IEnumerable constraintProviders, - IResourceDefinitionAccessor resourceDefinitionAccessor, - IJsonApiRequest request) + public FieldsToSerialize(IResourceContextProvider resourceContextProvider, IEnumerable constraintProviders, + IResourceDefinitionAccessor resourceDefinitionAccessor, IJsonApiRequest request) { ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); ArgumentGuard.NotNull(request, nameof(request)); @@ -43,18 +40,17 @@ public IReadOnlyCollection GetAttributes(Type resourceType) return Array.Empty(); } - var resourceContext = _resourceContextProvider.GetResourceContext(resourceType); - var fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType); + IReadOnlyCollection fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); return fieldSet.OfType().ToArray(); } /// /// - /// Note: this method does NOT check if a relationship is included to determine - /// if it should be serialized. This is because completely hiding a relationship - /// is not the same as not including. In the case of the latter, - /// we may still want to add the relationship to expose the navigation link to the client. + /// Note: this method does NOT check if a relationship is included to determine if it should be serialized. This is because completely hiding a + /// relationship is not the same as not including. In the case of the latter, we may still want to add the relationship to expose the navigation link to + /// the client. /// public IReadOnlyCollection GetRelationships(Type resourceType) { @@ -65,7 +61,7 @@ public IReadOnlyCollection GetRelationships(Type resource return Array.Empty(); } - var resourceContext = _resourceContextProvider.GetResourceContext(resourceType); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType); return resourceContext.Relationships; } diff --git a/src/JsonApiDotNetCore/Serialization/IFieldsToSerialize.cs b/src/JsonApiDotNetCore/Serialization/IFieldsToSerialize.cs index aca991a49c..7f2775c021 100644 --- a/src/JsonApiDotNetCore/Serialization/IFieldsToSerialize.cs +++ b/src/JsonApiDotNetCore/Serialization/IFieldsToSerialize.cs @@ -5,19 +5,18 @@ namespace JsonApiDotNetCore.Serialization { /// - /// Responsible for getting the set of fields that are to be included for a - /// given type in the serialization result. Typically combines various sources - /// of information, like application-wide and request-wide sparse fieldsets. + /// Responsible for getting the set of fields that are to be included for a given type in the serialization result. Typically combines various sources of + /// information, like application-wide and request-wide sparse fieldsets. /// public interface IFieldsToSerialize { /// - /// Gets the collection of attributes that are to be serialized for resources of type . + /// Gets the collection of attributes that are to be serialized for resources of type . /// IReadOnlyCollection GetAttributes(Type resourceType); /// - /// Gets the collection of relationships that are to be serialized for resources of type . + /// Gets the collection of relationships that are to be serialized for resources of type . /// IReadOnlyCollection GetRelationships(Type resourceType); diff --git a/src/JsonApiDotNetCore/Serialization/IJsonApiDeserializer.cs b/src/JsonApiDotNetCore/Serialization/IJsonApiDeserializer.cs index b8dea76109..3c363cbd4b 100644 --- a/src/JsonApiDotNetCore/Serialization/IJsonApiDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/IJsonApiDeserializer.cs @@ -8,10 +8,12 @@ namespace JsonApiDotNetCore.Serialization public interface IJsonApiDeserializer { /// - /// Deserializes JSON into a or and constructs resources - /// from . + /// Deserializes JSON into a or and constructs resources from + /// . /// - /// The JSON to be deserialized. + /// + /// The JSON to be deserialized. + /// object Deserialize(string body); } } diff --git a/src/JsonApiDotNetCore/Serialization/IJsonApiReader.cs b/src/JsonApiDotNetCore/Serialization/IJsonApiReader.cs index 9313d5b8c6..dbc851a492 100644 --- a/src/JsonApiDotNetCore/Serialization/IJsonApiReader.cs +++ b/src/JsonApiDotNetCore/Serialization/IJsonApiReader.cs @@ -5,8 +5,7 @@ namespace JsonApiDotNetCore.Serialization { /// - /// The deserializer of the body, used in ASP.NET Core internally - /// to process `FromBody`. + /// The deserializer of the body, used in ASP.NET Core internally to process `FromBody`. /// [PublicAPI] public interface IJsonApiReader diff --git a/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs index ebdf401213..97f0a15747 100644 --- a/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs @@ -6,13 +6,13 @@ namespace JsonApiDotNetCore.Serialization public interface IJsonApiSerializer { /// - /// Serializes a single resource or a collection of resources. + /// Gets the Content-Type HTTP header value. /// - string Serialize(object content); + string ContentType { get; } /// - /// Gets the Content-Type HTTP header value. + /// Serializes a single resource or a collection of resources. /// - string ContentType { get; } + string Serialize(object content); } } diff --git a/src/JsonApiDotNetCore/Serialization/IResponseMeta.cs b/src/JsonApiDotNetCore/Serialization/IResponseMeta.cs index 57e6304fc3..2561da2543 100644 --- a/src/JsonApiDotNetCore/Serialization/IResponseMeta.cs +++ b/src/JsonApiDotNetCore/Serialization/IResponseMeta.cs @@ -5,8 +5,8 @@ namespace JsonApiDotNetCore.Serialization { /// - /// Provides a method to obtain global JSON:API meta, which is added at top-level to a response . - /// Use to specify nested metadata per individual resource. + /// Provides a method to obtain global JSON:API meta, which is added at top-level to a response . Use + /// to specify nested metadata per individual resource. /// public interface IResponseMeta { diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs index 91d4fc80b9..f6b09d0b1d 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs @@ -29,9 +29,7 @@ public class JsonApiReader : IJsonApiReader private readonly IResourceContextProvider _resourceContextProvider; private readonly TraceLogWriter _traceWriter; - public JsonApiReader(IJsonApiDeserializer deserializer, - IJsonApiRequest request, - IResourceContextProvider resourceContextProvider, + public JsonApiReader(IJsonApiDeserializer deserializer, IJsonApiRequest request, IResourceContextProvider resourceContextProvider, ILoggerFactory loggerFactory) { ArgumentGuard.NotNull(deserializer, nameof(deserializer)); @@ -55,6 +53,7 @@ public async Task ReadAsync(InputFormatterContext context) _traceWriter.LogMessage(() => $"Received request at '{url}' with body: <<{body}>>"); object model = null; + if (!string.IsNullOrWhiteSpace(body)) { try @@ -93,17 +92,15 @@ private InvalidRequestBodyException ToInvalidRequestBodyException(JsonApiSeriali { if (_request.Kind != EndpointKind.AtomicOperations) { - return new InvalidRequestBodyException(exception.GenericMessage, exception.SpecificMessage, body, - exception); + return new InvalidRequestBodyException(exception.GenericMessage, exception.SpecificMessage, body, exception); } // In contrast to resource endpoints, we don't include the request body for operations because they are usually very long. - var requestException = - new InvalidRequestBodyException(exception.GenericMessage, exception.SpecificMessage, null, exception.InnerException); + var requestException = new InvalidRequestBodyException(exception.GenericMessage, exception.SpecificMessage, null, exception.InnerException); if (exception.AtomicOperationIndex != null) { - foreach (var error in requestException.Errors) + foreach (Error error in requestException.Errors) { error.Source.Pointer = $"/atomic:operations[{exception.AtomicOperationIndex}]"; } @@ -111,7 +108,7 @@ private InvalidRequestBodyException ToInvalidRequestBodyException(JsonApiSeriali return requestException; } - + private bool RequiresRequestBody(string requestMethod) { if (requestMethod == HttpMethods.Post || requestMethod == HttpMethods.Patch) @@ -154,31 +151,30 @@ private static void AssertHasRequestBody(object model, string body) private void ValidateIncomingResourceType(object model, HttpRequest httpRequest) { - var endpointResourceType = GetResourceTypeFromEndpoint(); + Type endpointResourceType = GetResourceTypeFromEndpoint(); + if (endpointResourceType == null) { return; } - var bodyResourceTypes = GetResourceTypesFromRequestBody(model); - foreach (var bodyResourceType in bodyResourceTypes) + IEnumerable bodyResourceTypes = GetResourceTypesFromRequestBody(model); + + foreach (Type bodyResourceType in bodyResourceTypes) { if (!endpointResourceType.IsAssignableFrom(bodyResourceType)) { - var resourceFromEndpoint = _resourceContextProvider.GetResourceContext(endpointResourceType); - var resourceFromBody = _resourceContextProvider.GetResourceContext(bodyResourceType); + ResourceContext resourceFromEndpoint = _resourceContextProvider.GetResourceContext(endpointResourceType); + ResourceContext resourceFromBody = _resourceContextProvider.GetResourceContext(bodyResourceType); - throw new ResourceTypeMismatchException(new HttpMethod(httpRequest.Method), - httpRequest.Path, resourceFromEndpoint, resourceFromBody); + throw new ResourceTypeMismatchException(new HttpMethod(httpRequest.Method), httpRequest.Path, resourceFromEndpoint, resourceFromBody); } } } private Type GetResourceTypeFromEndpoint() { - return _request.Kind == EndpointKind.Primary - ? _request.PrimaryResource.ResourceType - : _request.SecondaryResource?.ResourceType; + return _request.Kind == EndpointKind.Primary ? _request.PrimaryResource.ResourceType : _request.SecondaryResource?.ResourceType; } private IEnumerable GetResourceTypesFromRequestBody(object model) @@ -194,6 +190,7 @@ private IEnumerable GetResourceTypesFromRequestBody(object model) private void ValidateRequestIncludesId(object model, string body) { bool hasMissingId = model is IEnumerable list ? HasMissingId(list) : HasMissingId(model); + if (hasMissingId) { throw new InvalidRequestBodyException("Request body must include 'id' element.", null, body); @@ -204,7 +201,7 @@ private void ValidatePrimaryIdValue(object model, PathString requestPath) { if (_request.Kind == EndpointKind.Primary) { - if (TryGetId(model, out var bodyId) && bodyId != _request.PrimaryId) + if (TryGetId(model, out string bodyId) && bodyId != _request.PrimaryId) { throw new ResourceIdMismatchException(bodyId, _request.PrimaryId, requestPath); } @@ -224,7 +221,7 @@ private bool HasMissingId(object model) /// private bool HasMissingId(IEnumerable models) { - foreach (var model in models) + foreach (object? model in models) { if (TryGetId(model, out string id) && id == null) { diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializationException.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializationException.cs index a575ce59d8..189f2ede64 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializationException.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializationException.cs @@ -13,8 +13,7 @@ public class JsonApiSerializationException : Exception public string SpecificMessage { get; } public int? AtomicOperationIndex { get; } - public JsonApiSerializationException(string genericMessage, string specificMessage, - Exception innerException = null, int? atomicOperationIndex = null) + public JsonApiSerializationException(string genericMessage, string specificMessage, Exception innerException = null, int? atomicOperationIndex = null) : base(genericMessage, innerException) { GenericMessage = genericMessage; diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs b/src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs index c925825a80..bb37d29087 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net; using System.Net.Http; using System.Text; @@ -8,6 +9,7 @@ using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Serialization.Objects; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Formatters; @@ -16,8 +18,8 @@ namespace JsonApiDotNetCore.Serialization { /// - /// Formats the response data used (see https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/formatting?view=aspnetcore-3.0). - /// It was intended to have as little dependencies as possible in formatting layer for greater extensibility. + /// Formats the response data used (see https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/formatting?view=aspnetcore-3.0). It was intended to + /// have as little dependencies as possible in formatting layer for greater extensibility. /// [PublicAPI] public class JsonApiWriter : IJsonApiWriter @@ -41,24 +43,25 @@ public async Task WriteAsync(OutputFormatterWriteContext context) { ArgumentGuard.NotNull(context, nameof(context)); - var response = context.HttpContext.Response; + HttpResponse response = context.HttpContext.Response; response.ContentType = _serializer.ContentType; - await using var writer = context.WriterFactory(response.Body, Encoding.UTF8); + await using TextWriter writer = context.WriterFactory(response.Body, Encoding.UTF8); string responseContent; + try { - responseContent = SerializeResponse(context.Object, (HttpStatusCode) response.StatusCode); + responseContent = SerializeResponse(context.Object, (HttpStatusCode)response.StatusCode); } catch (Exception exception) { - var errorDocument = _exceptionHandler.HandleException(exception); + ErrorDocument errorDocument = _exceptionHandler.HandleException(exception); responseContent = _serializer.Serialize(errorDocument); - response.StatusCode = (int) errorDocument.GetErrorStatusCode(); + response.StatusCode = (int)errorDocument.GetErrorStatusCode(); } - var url = context.HttpContext.Request.GetEncodedUrl(); + string url = context.HttpContext.Request.GetEncodedUrl(); _traceWriter.LogMessage(() => $"Sending {response.StatusCode} response for request at '{url}' with body: <<{responseContent}>>"); await writer.WriteAsync(responseContent); @@ -79,15 +82,14 @@ private string SerializeResponse(object contextObject, HttpStatusCode statusCode throw new UnsuccessfulActionResultException(statusCode); } - if (statusCode == HttpStatusCode.NoContent || statusCode == HttpStatusCode.ResetContent || - statusCode == HttpStatusCode.NotModified) + if (statusCode == HttpStatusCode.NoContent || statusCode == HttpStatusCode.ResetContent || statusCode == HttpStatusCode.NotModified) { // Prevent exception from Kestrel server, caused by writing data:null json response. return null; } } - var contextObjectWrapped = WrapErrors(contextObject); + object contextObjectWrapped = WrapErrors(contextObject); return _serializer.Serialize(contextObjectWrapped); } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/Error.cs b/src/JsonApiDotNetCore/Serialization/Objects/Error.cs index 545c71da54..b33f79bba8 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/Error.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/Error.cs @@ -7,17 +7,12 @@ namespace JsonApiDotNetCore.Serialization.Objects { /// - /// Provides additional information about a problem encountered while performing an operation. - /// Error objects MUST be returned as an array keyed by errors in the top level of a JSON:API document. + /// Provides additional information about a problem encountered while performing an operation. Error objects MUST be returned as an array keyed by errors + /// in the top level of a JSON:API document. /// [PublicAPI] public sealed class Error { - public Error(HttpStatusCode statusCode) - { - StatusCode = statusCode; - } - /// /// A unique identifier for this particular occurrence of the problem. /// @@ -30,8 +25,6 @@ public Error(HttpStatusCode statusCode) [JsonProperty] public ErrorLinks Links { get; set; } = new ErrorLinks(); - public bool ShouldSerializeLinks() => Links?.About != null; - /// /// The HTTP status code applicable to this problem. /// @@ -52,7 +45,8 @@ public string Status public string Code { get; set; } /// - /// A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization. + /// A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of + /// localization. /// [JsonProperty] public string Title { get; set; } @@ -69,14 +63,30 @@ public string Status [JsonProperty] public ErrorSource Source { get; set; } = new ErrorSource(); - public bool ShouldSerializeSource() => Source != null && (Source.Pointer != null || Source.Parameter != null); - /// /// An object containing non-standard meta-information (key/value pairs) about the error. /// [JsonProperty] public ErrorMeta Meta { get; set; } = new ErrorMeta(); - public bool ShouldSerializeMeta() => Meta != null && Meta.Data.Any(); + public Error(HttpStatusCode statusCode) + { + StatusCode = statusCode; + } + + public bool ShouldSerializeLinks() + { + return Links?.About != null; + } + + public bool ShouldSerializeSource() + { + return Source != null && (Source.Pointer != null || Source.Parameter != null); + } + + public bool ShouldSerializeMeta() + { + return Meta != null && Meta.Data.Any(); + } } } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ErrorDocument.cs b/src/JsonApiDotNetCore/Serialization/Objects/ErrorDocument.cs index 67d0e82a27..d558b94186 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ErrorDocument.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ErrorDocument.cs @@ -30,17 +30,14 @@ public ErrorDocument(IEnumerable errors) public HttpStatusCode GetErrorStatusCode() { - var statusCodes = Errors - .Select(e => (int)e.StatusCode) - .Distinct() - .ToArray(); + int[] statusCodes = Errors.Select(e => (int)e.StatusCode).Distinct().ToArray(); if (statusCodes.Length == 1) { return (HttpStatusCode)statusCodes[0]; } - var statusCode = int.Parse(statusCodes.Max().ToString()[0] + "00"); + int statusCode = int.Parse(statusCodes.Max().ToString()[0] + "00"); return (HttpStatusCode)statusCode; } } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ErrorMeta.cs b/src/JsonApiDotNetCore/Serialization/Objects/ErrorMeta.cs index 9c4477dd02..1589089719 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ErrorMeta.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ErrorMeta.cs @@ -22,8 +22,7 @@ public void IncludeExceptionStackTrace(Exception exception) } else { - Data["StackTrace"] = exception.ToString() - .Split("\n", int.MaxValue, StringSplitOptions.RemoveEmptyEntries); + Data["StackTrace"] = exception.ToString().Split("\n", int.MaxValue, StringSplitOptions.RemoveEmptyEntries); } } } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ErrorSource.cs b/src/JsonApiDotNetCore/Serialization/Objects/ErrorSource.cs index 0e1686fbdf..88cdc1812d 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ErrorSource.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ErrorSource.cs @@ -5,7 +5,8 @@ namespace JsonApiDotNetCore.Serialization.Objects public sealed class ErrorSource { /// - /// Optional. A JSON Pointer [RFC6901] to the associated resource in the request document [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute]. + /// Optional. A JSON Pointer [RFC6901] to the associated resource in the request document [e.g. "/data" for a primary data object, or + /// "/data/attributes/title" for a specific attribute]. /// [JsonProperty] public string Pointer { get; set; } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ExposableData.cs b/src/JsonApiDotNetCore/Serialization/Objects/ExposableData.cs index 40292cfb36..27ef8d0690 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ExposableData.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ExposableData.cs @@ -7,8 +7,21 @@ namespace JsonApiDotNetCore.Serialization.Objects { [PublicAPI] - public abstract class ExposableData where TResource : class + public abstract class ExposableData + where TResource : class { + private bool IsEmpty => !HasManyData && SingleData == null; + + private bool HasManyData => IsManyData && ManyData.Any(); + + /// + /// Internally used to indicate if the document's primary data should still be serialized when it's value is null. This is used when a single resource is + /// requested but not present (eg /articles/1/author). + /// + internal bool IsPopulated { get; private set; } + + internal bool HasResource => IsPopulated && !IsEmpty; + /// /// See "primary data" in https://jsonapi.org/format/#document-top-level. /// @@ -19,24 +32,6 @@ public object Data set => SetPrimaryData(value); } - /// - /// See https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm. - /// - /// - /// Moving this method to the derived class where it is needed only in the - /// case of would make more sense, but - /// Newtonsoft does not support this. - /// - public bool ShouldSerializeData() - { - if (GetType() == typeof(RelationshipEntry)) - { - return IsPopulated; - } - - return true; - } - /// /// Internally used for "single" primary data. /// @@ -56,21 +51,24 @@ public bool ShouldSerializeData() public bool IsManyData { get; private set; } /// - /// Internally used to indicate if the document's primary data - /// should still be serialized when it's value is null. This is used when - /// a single resource is requested but not present (eg /articles/1/author). + /// See https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm. /// - internal bool IsPopulated { get; private set; } - - internal bool HasResource => IsPopulated && !IsEmpty; - - private bool IsEmpty => !HasManyData && SingleData == null; + /// + /// Moving this method to the derived class where it is needed only in the case of would make more sense, but Newtonsoft + /// does not support this. + /// + public bool ShouldSerializeData() + { + if (GetType() == typeof(RelationshipEntry)) + { + return IsPopulated; + } - private bool HasManyData => IsManyData && ManyData.Any(); + return true; + } /// - /// Gets the "single" or "many" data depending on which one was - /// assigned in this document. + /// Gets the "single" or "many" data depending on which one was assigned in this document. /// protected object GetPrimaryData() { @@ -88,6 +86,7 @@ protected object GetPrimaryData() protected void SetPrimaryData(object value) { IsPopulated = true; + if (value is JObject jObject) { SingleData = jObject.ToObject(); @@ -99,6 +98,7 @@ protected void SetPrimaryData(object value) else if (value != null) { IsManyData = true; + if (value is JArray jArray) { ManyData = jArray.ToObject>(); diff --git a/src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs b/src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs index eb868ab837..98eb1a07df 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs @@ -29,12 +29,39 @@ public sealed class TopLevelLinks public string Next { get; set; } // http://www.newtonsoft.com/json/help/html/ConditionalProperties.htm - public bool ShouldSerializeSelf() => !string.IsNullOrEmpty(Self); - public bool ShouldSerializeRelated() => !string.IsNullOrEmpty(Related); - public bool ShouldSerializeDescribedBy() => !string.IsNullOrEmpty(DescribedBy); - public bool ShouldSerializeFirst() => !string.IsNullOrEmpty(First); - public bool ShouldSerializeLast() => !string.IsNullOrEmpty(Last); - public bool ShouldSerializePrev() => !string.IsNullOrEmpty(Prev); - public bool ShouldSerializeNext() => !string.IsNullOrEmpty(Next); + public bool ShouldSerializeSelf() + { + return !string.IsNullOrEmpty(Self); + } + + public bool ShouldSerializeRelated() + { + return !string.IsNullOrEmpty(Related); + } + + public bool ShouldSerializeDescribedBy() + { + return !string.IsNullOrEmpty(DescribedBy); + } + + public bool ShouldSerializeFirst() + { + return !string.IsNullOrEmpty(First); + } + + public bool ShouldSerializeLast() + { + return !string.IsNullOrEmpty(Last); + } + + public bool ShouldSerializePrev() + { + return !string.IsNullOrEmpty(Prev); + } + + public bool ShouldSerializeNext() + { + return !string.IsNullOrEmpty(Next); + } } } diff --git a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs index 746e12654b..e325b4b62e 100644 --- a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -15,7 +16,7 @@ namespace JsonApiDotNetCore.Serialization { /// - /// Server deserializer implementation of the . + /// Server deserializer implementation of the . /// [PublicAPI] public class RequestDeserializer : BaseDeserializer, IJsonApiDeserializer @@ -25,13 +26,8 @@ public class RequestDeserializer : BaseDeserializer, IJsonApiDeserializer private readonly IJsonApiRequest _request; private readonly IJsonApiOptions _options; - public RequestDeserializer( - IResourceContextProvider resourceContextProvider, - IResourceFactory resourceFactory, - ITargetedFields targetedFields, - IHttpContextAccessor httpContextAccessor, - IJsonApiRequest request, - IJsonApiOptions options) + public RequestDeserializer(IResourceContextProvider resourceContextProvider, IResourceFactory resourceFactory, ITargetedFields targetedFields, + IHttpContextAccessor httpContextAccessor, IJsonApiRequest request, IJsonApiOptions options) : base(resourceContextProvider, resourceFactory) { ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); @@ -60,7 +56,7 @@ public object Deserialize(string body) return DeserializeOperationsDocument(body); } - var instance = DeserializeBody(body); + object instance = DeserializeBody(body); AssertResourceIdIsNotTargeted(_targetedFields); @@ -86,9 +82,9 @@ private object DeserializeOperationsDocument(string body) var operations = new List(); AtomicOperationIndex = 0; - foreach (var operation in document.Operations) + foreach (AtomicOperationObject operation in document.Operations) { - var container = DeserializeOperation(operation); + OperationContainer container = DeserializeOperation(operation); operations.Add(container); AtomicOperationIndex++; @@ -104,7 +100,8 @@ private OperationContainer DeserializeOperation(AtomicOperationObject operation) AssertHasNoHref(operation); - var kind = GetOperationKind(operation); + OperationKind kind = GetOperationKind(operation); + switch (kind) { case OperationKind.CreateResource: @@ -118,8 +115,7 @@ private OperationContainer DeserializeOperation(AtomicOperationObject operation) } } - bool requireToManyRelationship = - kind == OperationKind.AddToRelationship || kind == OperationKind.RemoveFromRelationship; + bool requireToManyRelationship = kind == OperationKind.AddToRelationship || kind == OperationKind.RemoveFromRelationship; return ParseForRelationshipOperation(operation, kind, requireToManyRelationship); } @@ -129,8 +125,7 @@ private void AssertHasNoHref(AtomicOperationObject operation) { if (operation.Href != null) { - throw new JsonApiSerializationException("Usage of the 'href' element is not supported.", null, - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Usage of the 'href' element is not supported.", null, atomicOperationIndex: AtomicOperationIndex); } } @@ -150,36 +145,30 @@ private OperationKind GetOperationKind(AtomicOperationObject operation) } case AtomicOperationCode.Update: { - return operation.Ref?.Relationship != null - ? OperationKind.SetRelationship - : OperationKind.UpdateResource; + return operation.Ref?.Relationship != null ? OperationKind.SetRelationship : OperationKind.UpdateResource; } case AtomicOperationCode.Remove: { if (operation.Ref == null) { - throw new JsonApiSerializationException("The 'ref' element is required.", null, - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("The 'ref' element is required.", null, atomicOperationIndex: AtomicOperationIndex); } - return operation.Ref.Relationship != null - ? OperationKind.RemoveFromRelationship - : OperationKind.DeleteResource; + return operation.Ref.Relationship != null ? OperationKind.RemoveFromRelationship : OperationKind.DeleteResource; } } throw new NotSupportedException($"Unknown operation code '{operation.Code}'."); } - private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperationObject operation, - OperationKind kind) + private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperationObject operation, OperationKind kind) { - var resourceObject = GetRequiredSingleDataForResourceOperation(operation); + ResourceObject resourceObject = GetRequiredSingleDataForResourceOperation(operation); AssertElementHasType(resourceObject, "data"); AssertElementHasIdOrLid(resourceObject, "data", kind != OperationKind.CreateResource); - var primaryResourceContext = GetExistingResourceContext(resourceObject.Type); + ResourceContext primaryResourceContext = GetExistingResourceContext(resourceObject.Type); AssertCompatibleId(resourceObject, primaryResourceContext.IdentityType); @@ -190,12 +179,11 @@ private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperati AssertElementHasType(operation.Ref, "ref"); AssertElementHasIdOrLid(operation.Ref, "ref", true); - var resourceContextInRef = GetExistingResourceContext(operation.Ref.Type); + ResourceContext resourceContextInRef = GetExistingResourceContext(operation.Ref.Type); if (resourceContextInRef != primaryResourceContext) { - throw new JsonApiSerializationException( - "Resource type mismatch between 'ref.type' and 'data.type' element.", + throw new JsonApiSerializationException("Resource type mismatch between 'ref.type' and 'data.type' element.", $"Expected resource of type '{resourceContextInRef.PublicName}' in 'data.type', instead of '{primaryResourceContext.PublicName}'.", atomicOperationIndex: AtomicOperationIndex); } @@ -210,9 +198,10 @@ private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperati PrimaryResource = primaryResourceContext, OperationKind = kind }; + _request.CopyFrom(request); - var primaryResource = ParseResourceObject(operation.SingleData); + IIdentifiable primaryResource = ParseResourceObject(operation.SingleData); request.PrimaryId = primaryResource.StringId; _request.CopyFrom(request); @@ -232,15 +221,13 @@ private ResourceObject GetRequiredSingleDataForResourceOperation(AtomicOperation { if (operation.Data == null) { - throw new JsonApiSerializationException("The 'data' element is required.", null, - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("The 'data' element is required.", null, atomicOperationIndex: AtomicOperationIndex); } if (operation.SingleData == null) { - throw new JsonApiSerializationException( - "Expected single data element for create/update resource operation.", - null, atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Expected single data element for create/update resource operation.", null, + atomicOperationIndex: AtomicOperationIndex); } return operation.SingleData; @@ -251,21 +238,18 @@ private void AssertElementHasType(ResourceIdentifierObject resourceIdentifierObj { if (resourceIdentifierObject.Type == null) { - throw new JsonApiSerializationException($"The '{elementPath}.type' element is required.", null, - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException($"The '{elementPath}.type' element is required.", null, atomicOperationIndex: AtomicOperationIndex); } } - private void AssertElementHasIdOrLid(ResourceIdentifierObject resourceIdentifierObject, string elementPath, - bool isRequired) + private void AssertElementHasIdOrLid(ResourceIdentifierObject resourceIdentifierObject, string elementPath, bool isRequired) { bool hasNone = resourceIdentifierObject.Id == null && resourceIdentifierObject.Lid == null; bool hasBoth = resourceIdentifierObject.Id != null && resourceIdentifierObject.Lid != null; if (isRequired ? hasNone || hasBoth : hasBoth) { - throw new JsonApiSerializationException( - $"The '{elementPath}.id' or '{elementPath}.lid' element is required.", null, + throw new JsonApiSerializationException($"The '{elementPath}.id' or '{elementPath}.lid' element is required.", null, atomicOperationIndex: AtomicOperationIndex); } } @@ -285,39 +269,32 @@ private void AssertCompatibleId(ResourceIdentifierObject resourceIdentifierObjec } } - private void AssertSameIdentityInRefData(AtomicOperationObject operation, - ResourceIdentifierObject resourceIdentifierObject) + private void AssertSameIdentityInRefData(AtomicOperationObject operation, ResourceIdentifierObject resourceIdentifierObject) { - if (operation.Ref.Id != null && resourceIdentifierObject.Id != null && - resourceIdentifierObject.Id != operation.Ref.Id) + if (operation.Ref.Id != null && resourceIdentifierObject.Id != null && resourceIdentifierObject.Id != operation.Ref.Id) { - throw new JsonApiSerializationException( - "Resource ID mismatch between 'ref.id' and 'data.id' element.", + throw new JsonApiSerializationException("Resource ID mismatch between 'ref.id' and 'data.id' element.", $"Expected resource with ID '{operation.Ref.Id}' in 'data.id', instead of '{resourceIdentifierObject.Id}'.", atomicOperationIndex: AtomicOperationIndex); } - if (operation.Ref.Lid != null && resourceIdentifierObject.Lid != null && - resourceIdentifierObject.Lid != operation.Ref.Lid) + if (operation.Ref.Lid != null && resourceIdentifierObject.Lid != null && resourceIdentifierObject.Lid != operation.Ref.Lid) { - throw new JsonApiSerializationException( - "Resource local ID mismatch between 'ref.lid' and 'data.lid' element.", + throw new JsonApiSerializationException("Resource local ID mismatch between 'ref.lid' and 'data.lid' element.", $"Expected resource with local ID '{operation.Ref.Lid}' in 'data.lid', instead of '{resourceIdentifierObject.Lid}'.", atomicOperationIndex: AtomicOperationIndex); } if (operation.Ref.Id != null && resourceIdentifierObject.Lid != null) { - throw new JsonApiSerializationException( - "Resource identity mismatch between 'ref.id' and 'data.lid' element.", + throw new JsonApiSerializationException("Resource identity mismatch between 'ref.id' and 'data.lid' element.", $"Expected resource with ID '{operation.Ref.Id}' in 'data.id', instead of '{resourceIdentifierObject.Lid}' in 'data.lid'.", atomicOperationIndex: AtomicOperationIndex); } if (operation.Ref.Lid != null && resourceIdentifierObject.Id != null) { - throw new JsonApiSerializationException( - "Resource identity mismatch between 'ref.lid' and 'data.id' element.", + throw new JsonApiSerializationException("Resource identity mismatch between 'ref.lid' and 'data.id' element.", $"Expected resource with local ID '{operation.Ref.Lid}' in 'data.lid', instead of '{resourceIdentifierObject.Id}' in 'data.id'.", atomicOperationIndex: AtomicOperationIndex); } @@ -328,11 +305,11 @@ private OperationContainer ParseForDeleteResourceOperation(AtomicOperationObject AssertElementHasType(operation.Ref, "ref"); AssertElementHasIdOrLid(operation.Ref, "ref", true); - var primaryResourceContext = GetExistingResourceContext(operation.Ref.Type); + ResourceContext primaryResourceContext = GetExistingResourceContext(operation.Ref.Type); AssertCompatibleId(operation.Ref, primaryResourceContext.IdentityType); - var primaryResource = ResourceFactory.CreateInstance(primaryResourceContext.ResourceType); + IIdentifiable primaryResource = ResourceFactory.CreateInstance(primaryResourceContext.ResourceType); primaryResource.StringId = operation.Ref.Id; primaryResource.LocalId = operation.Ref.Lid; @@ -348,31 +325,28 @@ private OperationContainer ParseForDeleteResourceOperation(AtomicOperationObject return new OperationContainer(kind, primaryResource, new TargetedFields(), request); } - private OperationContainer ParseForRelationshipOperation(AtomicOperationObject operation, OperationKind kind, - bool requireToMany) + private OperationContainer ParseForRelationshipOperation(AtomicOperationObject operation, OperationKind kind, bool requireToMany) { AssertElementHasType(operation.Ref, "ref"); AssertElementHasIdOrLid(operation.Ref, "ref", true); - var primaryResourceContext = GetExistingResourceContext(operation.Ref.Type); + ResourceContext primaryResourceContext = GetExistingResourceContext(operation.Ref.Type); AssertCompatibleId(operation.Ref, primaryResourceContext.IdentityType); - var primaryResource = ResourceFactory.CreateInstance(primaryResourceContext.ResourceType); + IIdentifiable primaryResource = ResourceFactory.CreateInstance(primaryResourceContext.ResourceType); primaryResource.StringId = operation.Ref.Id; primaryResource.LocalId = operation.Ref.Lid; - var relationship = GetExistingRelationship(operation.Ref, primaryResourceContext); + RelationshipAttribute relationship = GetExistingRelationship(operation.Ref, primaryResourceContext); if (requireToMany && relationship is HasOneAttribute) { - throw new JsonApiSerializationException( - $"Only to-many relationships can be targeted in '{operation.Code.ToString().Camelize()}' operations.", - $"Relationship '{operation.Ref.Relationship}' must be a to-many relationship.", - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException($"Only to-many relationships can be targeted in '{operation.Code.ToString().Camelize()}' operations.", + $"Relationship '{operation.Ref.Relationship}' must be a to-many relationship.", atomicOperationIndex: AtomicOperationIndex); } - var secondaryResourceContext = ResourceContextProvider.GetResourceContext(relationship.RightType); + ResourceContext secondaryResourceContext = ResourceContextProvider.GetResourceContext(relationship.RightType); var request = new JsonApiRequest { @@ -385,6 +359,7 @@ private OperationContainer ParseForRelationshipOperation(AtomicOperationObject o IsCollection = relationship is HasManyAttribute, OperationKind = kind }; + _request.CopyFrom(request); _targetedFields.Relationships.Add(relationship); @@ -400,16 +375,13 @@ private OperationContainer ParseForRelationshipOperation(AtomicOperationObject o return new OperationContainer(kind, primaryResource, targetedFields, request); } - private RelationshipAttribute GetExistingRelationship(AtomicReference reference, - ResourceContext resourceContext) + private RelationshipAttribute GetExistingRelationship(AtomicReference reference, ResourceContext resourceContext) { - var relationship = resourceContext.Relationships.FirstOrDefault(attribute => - attribute.PublicName == reference.Relationship); + RelationshipAttribute relationship = resourceContext.Relationships.FirstOrDefault(attribute => attribute.PublicName == reference.Relationship); if (relationship == null) { - throw new JsonApiSerializationException( - "The referenced relationship does not exist.", + throw new JsonApiSerializationException("The referenced relationship does not exist.", $"Resource of type '{reference.Type}' does not contain a relationship named '{reference.Relationship}'.", atomicOperationIndex: AtomicOperationIndex); } @@ -417,25 +389,22 @@ private RelationshipAttribute GetExistingRelationship(AtomicReference reference, return relationship; } - private void ParseDataForRelationship(RelationshipAttribute relationship, - ResourceContext secondaryResourceContext, - AtomicOperationObject operation, IIdentifiable primaryResource) + private void ParseDataForRelationship(RelationshipAttribute relationship, ResourceContext secondaryResourceContext, AtomicOperationObject operation, + IIdentifiable primaryResource) { if (relationship is HasOneAttribute) { if (operation.ManyData != null) { - throw new JsonApiSerializationException( - "Expected single data element for to-one relationship.", - $"Expected single data element for '{relationship.PublicName}' relationship.", - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Expected single data element for to-one relationship.", + $"Expected single data element for '{relationship.PublicName}' relationship.", atomicOperationIndex: AtomicOperationIndex); } if (operation.SingleData != null) { ValidateSingleDataForRelationship(operation.SingleData, secondaryResourceContext, "data"); - var secondaryResource = ParseResourceObject(operation.SingleData); + IIdentifiable secondaryResource = ParseResourceObject(operation.SingleData); relationship.SetValue(primaryResource, secondaryResource); } } @@ -443,47 +412,41 @@ private void ParseDataForRelationship(RelationshipAttribute relationship, { if (operation.ManyData == null) { - throw new JsonApiSerializationException( - "Expected data[] element for to-many relationship.", - $"Expected data[] element for '{relationship.PublicName}' relationship.", - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Expected data[] element for to-many relationship.", + $"Expected data[] element for '{relationship.PublicName}' relationship.", atomicOperationIndex: AtomicOperationIndex); } var secondaryResources = new List(); - foreach (var resourceObject in operation.ManyData) + foreach (ResourceObject resourceObject in operation.ManyData) { ValidateSingleDataForRelationship(resourceObject, secondaryResourceContext, "data[]"); - var secondaryResource = ParseResourceObject(resourceObject); + IIdentifiable secondaryResource = ParseResourceObject(resourceObject); secondaryResources.Add(secondaryResource); } - var rightResources = - TypeHelper.CopyToTypedCollection(secondaryResources, relationship.Property.PropertyType); + IEnumerable rightResources = TypeHelper.CopyToTypedCollection(secondaryResources, relationship.Property.PropertyType); relationship.SetValue(primaryResource, rightResources); } } - private void ValidateSingleDataForRelationship(ResourceObject dataResourceObject, - ResourceContext resourceContext, string elementPath) + private void ValidateSingleDataForRelationship(ResourceObject dataResourceObject, ResourceContext resourceContext, string elementPath) { AssertElementHasType(dataResourceObject, elementPath); AssertElementHasIdOrLid(dataResourceObject, elementPath, true); - var resourceContextInData = GetExistingResourceContext(dataResourceObject.Type); + ResourceContext resourceContextInData = GetExistingResourceContext(dataResourceObject.Type); AssertCompatibleType(resourceContextInData, resourceContext, elementPath); AssertCompatibleId(dataResourceObject, resourceContextInData.IdentityType); } - private void AssertCompatibleType(ResourceContext resourceContextInData, ResourceContext resourceContextInRef, - string elementPath) + private void AssertCompatibleType(ResourceContext resourceContextInData, ResourceContext resourceContextInRef, string elementPath) { if (!resourceContextInData.ResourceType.IsAssignableFrom(resourceContextInRef.ResourceType)) { - throw new JsonApiSerializationException( - $"Resource type mismatch between 'ref.relationship' and '{elementPath}.type' element.", + throw new JsonApiSerializationException($"Resource type mismatch between 'ref.relationship' and '{elementPath}.type' element.", $"Expected resource of type '{resourceContextInRef.PublicName}' in '{elementPath}.type', instead of '{resourceContextInData.PublicName}'.", atomicOperationIndex: AtomicOperationIndex); } @@ -491,23 +454,26 @@ private void AssertCompatibleType(ResourceContext resourceContextInData, Resourc private void AssertResourceIdIsNotTargeted(ITargetedFields targetedFields) { - if (!_request.IsReadOnly && - targetedFields.Attributes.Any(attribute => attribute.Property.Name == nameof(Identifiable.Id))) + if (!_request.IsReadOnly && targetedFields.Attributes.Any(attribute => attribute.Property.Name == nameof(Identifiable.Id))) { - throw new JsonApiSerializationException("Resource ID is read-only.", null, - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Resource ID is read-only.", null, atomicOperationIndex: AtomicOperationIndex); } } /// - /// Additional processing required for server deserialization. Flags a - /// processed attribute or relationship as updated using . + /// Additional processing required for server deserialization. Flags a processed attribute or relationship as updated using + /// . /// - /// The resource that was constructed from the document's body. - /// The metadata for the exposed field. - /// Relationship data for . Is null when is not a . - protected override void AfterProcessField(IIdentifiable resource, ResourceFieldAttribute field, - RelationshipEntry data = null) + /// + /// The resource that was constructed from the document's body. + /// + /// + /// The metadata for the exposed field. + /// + /// + /// Relationship data for . Is null when is not a . + /// + protected override void AfterProcessField(IIdentifiable resource, ResourceFieldAttribute field, RelationshipEntry data = null) { bool isCreatingResource = IsCreatingResource(); bool isUpdatingResource = IsUpdatingResource(); @@ -516,18 +482,14 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA { if (isCreatingResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowCreate)) { - throw new JsonApiSerializationException( - "Setting the initial value of the requested attribute is not allowed.", - $"Setting the initial value of '{attr.PublicName}' is not allowed.", - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Setting the initial value of the requested attribute is not allowed.", + $"Setting the initial value of '{attr.PublicName}' is not allowed.", atomicOperationIndex: AtomicOperationIndex); } if (isUpdatingResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowChange)) { - throw new JsonApiSerializationException( - "Changing the value of the requested attribute is not allowed.", - $"Changing the value of '{attr.PublicName}' is not allowed.", - atomicOperationIndex: AtomicOperationIndex); + throw new JsonApiSerializationException("Changing the value of the requested attribute is not allowed.", + $"Changing the value of '{attr.PublicName}' is not allowed.", atomicOperationIndex: AtomicOperationIndex); } _targetedFields.Attributes.Add(attr); @@ -542,16 +504,14 @@ private bool IsCreatingResource() { return _request.Kind == EndpointKind.AtomicOperations ? _request.OperationKind == OperationKind.CreateResource - : _request.Kind == EndpointKind.Primary && - _httpContextAccessor.HttpContext.Request.Method == HttpMethod.Post.Method; + : _request.Kind == EndpointKind.Primary && _httpContextAccessor.HttpContext.Request.Method == HttpMethod.Post.Method; } private bool IsUpdatingResource() { return _request.Kind == EndpointKind.AtomicOperations ? _request.OperationKind == OperationKind.UpdateResource - : _request.Kind == EndpointKind.Primary && - _httpContextAccessor.HttpContext.Request.Method == HttpMethod.Patch.Method; + : _request.Kind == EndpointKind.Primary && _httpContextAccessor.HttpContext.Request.Method == HttpMethod.Patch.Method; } } } diff --git a/src/JsonApiDotNetCore/Serialization/ResponseSerializer.cs b/src/JsonApiDotNetCore/Serialization/ResponseSerializer.cs index 70f5fca56b..fb0b60f5fd 100644 --- a/src/JsonApiDotNetCore/Serialization/ResponseSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/ResponseSerializer.cs @@ -5,6 +5,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; using JsonApiDotNetCore.Serialization.Building; using JsonApiDotNetCore.Serialization.Objects; using Newtonsoft.Json; @@ -12,16 +13,16 @@ namespace JsonApiDotNetCore.Serialization { /// - /// Server serializer implementation of for resources of a specific type. + /// Server serializer implementation of for resources of a specific type. /// /// - /// Because in JsonApiDotNetCore every JSON:API request is associated with exactly one - /// resource (the primary resource, see ), - /// the serializer can leverage this information using generics. - /// See for how this is instantiated. + /// Because in JsonApiDotNetCore every JSON:API request is associated with exactly one resource (the primary resource, see + /// ), the serializer can leverage this information using generics. See + /// for how this is instantiated. /// - /// Type of the resource associated with the scope of the request - /// for which this serializer is used. + /// + /// Type of the resource associated with the scope of the request for which this serializer is used. + /// [PublicAPI] public class ResponseSerializer : BaseSerializer, IJsonApiSerializer where TResource : class, IIdentifiable @@ -36,12 +37,8 @@ public class ResponseSerializer : BaseSerializer, IJsonApiSerializer /// public string ContentType { get; } = HeaderConstants.MediaType; - public ResponseSerializer(IMetaBuilder metaBuilder, - ILinkBuilder linkBuilder, - IIncludedResourceObjectBuilder includedBuilder, - IFieldsToSerialize fieldsToSerialize, - IResourceObjectBuilder resourceObjectBuilder, - IJsonApiOptions options) + public ResponseSerializer(IMetaBuilder metaBuilder, ILinkBuilder linkBuilder, IIncludedResourceObjectBuilder includedBuilder, + IFieldsToSerialize fieldsToSerialize, IResourceObjectBuilder resourceObjectBuilder, IJsonApiOptions options) : base(resourceObjectBuilder) { ArgumentGuard.NotNull(metaBuilder, nameof(metaBuilder)); @@ -81,22 +78,26 @@ public string Serialize(object content) private string SerializeErrorDocument(ErrorDocument errorDocument) { - return SerializeObject(errorDocument, _options.SerializerSettings, serializer => { serializer.ApplyErrorSettings(); }); + return SerializeObject(errorDocument, _options.SerializerSettings, serializer => + { + serializer.ApplyErrorSettings(); + }); } /// - /// Converts a single resource into a serialized . + /// Converts a single resource into a serialized . /// /// /// This method is internal instead of private for easier testability. /// internal string SerializeSingle(IIdentifiable resource) { - var attributes = _fieldsToSerialize.GetAttributes(_primaryResourceType); - var relationships = _fieldsToSerialize.GetRelationships(_primaryResourceType); + IReadOnlyCollection attributes = _fieldsToSerialize.GetAttributes(_primaryResourceType); + IReadOnlyCollection relationships = _fieldsToSerialize.GetRelationships(_primaryResourceType); + + Document document = Build(resource, attributes, relationships); + ResourceObject resourceObject = document.SingleData; - var document = Build(resource, attributes, relationships); - var resourceObject = document.SingleData; if (resourceObject != null) { resourceObject.Links = _linkBuilder.GetResourceLinks(resourceObject.Type, resourceObject.Id); @@ -104,24 +105,29 @@ internal string SerializeSingle(IIdentifiable resource) AddTopLevelObjects(document); - return SerializeObject(document, _options.SerializerSettings, serializer => { serializer.NullValueHandling = NullValueHandling.Include; }); + return SerializeObject(document, _options.SerializerSettings, serializer => + { + serializer.NullValueHandling = NullValueHandling.Include; + }); } /// - /// Converts a collection of resources into a serialized . + /// Converts a collection of resources into a serialized . /// /// /// This method is internal instead of private for easier testability. /// internal string SerializeMany(IReadOnlyCollection resources) { - var attributes = _fieldsToSerialize.GetAttributes(_primaryResourceType); - var relationships = _fieldsToSerialize.GetRelationships(_primaryResourceType); + IReadOnlyCollection attributes = _fieldsToSerialize.GetAttributes(_primaryResourceType); + IReadOnlyCollection relationships = _fieldsToSerialize.GetRelationships(_primaryResourceType); + + Document document = Build(resources, attributes, relationships); - var document = Build(resources, attributes, relationships); foreach (ResourceObject resourceObject in document.ManyData) { - var links = _linkBuilder.GetResourceLinks(resourceObject.Type, resourceObject.Id); + ResourceLinks links = _linkBuilder.GetResourceLinks(resourceObject.Type, resourceObject.Id); + if (links == null) { break; @@ -132,12 +138,14 @@ internal string SerializeMany(IReadOnlyCollection resources) AddTopLevelObjects(document); - return SerializeObject(document, _options.SerializerSettings, serializer => { serializer.NullValueHandling = NullValueHandling.Include; }); + return SerializeObject(document, _options.SerializerSettings, serializer => + { + serializer.NullValueHandling = NullValueHandling.Include; + }); } /// - /// Adds top-level objects that are only added to a document in the case - /// of server-side serialization. + /// Adds top-level objects that are only added to a document in the case of server-side serialization. /// private void AddTopLevelObjects(Document document) { diff --git a/src/JsonApiDotNetCore/Serialization/ResponseSerializerFactory.cs b/src/JsonApiDotNetCore/Serialization/ResponseSerializerFactory.cs index 4b6b1c12b5..5ddc248a4d 100644 --- a/src/JsonApiDotNetCore/Serialization/ResponseSerializerFactory.cs +++ b/src/JsonApiDotNetCore/Serialization/ResponseSerializerFactory.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCore.Serialization { /// - /// A factory class to abstract away the initialization of the serializer from the - /// ASP.NET Core formatter pipeline. + /// A factory class to abstract away the initialization of the serializer from the ASP.NET Core formatter pipeline. /// [PublicAPI] public class ResponseSerializerFactory : IJsonApiSerializerFactory @@ -26,7 +25,7 @@ public ResponseSerializerFactory(IJsonApiRequest request, IRequestScopedServiceP } /// - /// Initializes the server serializer using the associated with the current request. + /// Initializes the server serializer using the associated with the current request. /// public IJsonApiSerializer GetSerializer() { @@ -35,17 +34,17 @@ public IJsonApiSerializer GetSerializer() return (IJsonApiSerializer)_provider.GetRequiredService(typeof(AtomicOperationsResponseSerializer)); } - var targetType = GetDocumentType(); + Type targetType = GetDocumentType(); - var serializerType = typeof(ResponseSerializer<>).MakeGenericType(targetType); - var serializer = _provider.GetRequiredService(serializerType); + Type serializerType = typeof(ResponseSerializer<>).MakeGenericType(targetType); + object serializer = _provider.GetRequiredService(serializerType); return (IJsonApiSerializer)serializer; } private Type GetDocumentType() { - var resourceContext = _request.SecondaryResource ?? _request.PrimaryResource; + ResourceContext resourceContext = _request.SecondaryResource ?? _request.PrimaryResource; return resourceContext.ResourceType; } } diff --git a/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs b/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs index be11ea908e..e93b294a22 100644 --- a/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs @@ -13,7 +13,7 @@ public static async Task AddRangeAsync(this ICollection source, IAsyncEnum ArgumentGuard.NotNull(source, nameof(source)); ArgumentGuard.NotNull(elementsToAdd, nameof(elementsToAdd)); - await foreach (var missingResource in elementsToAdd.WithCancellation(cancellationToken)) + await foreach (T missingResource in elementsToAdd.WithCancellation(cancellationToken)) { source.Add(missingResource); } @@ -25,7 +25,7 @@ public static async Task> ToListAsync(this IAsyncEnumerable source var list = new List(); - await foreach (var element in source.WithCancellation(cancellationToken)) + await foreach (T element in source.WithCancellation(cancellationToken)) { list.Add(element); } diff --git a/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs b/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs index ca1e968767..5bbc74ae71 100644 --- a/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs @@ -11,7 +11,8 @@ namespace JsonApiDotNetCore.Services /// public interface IAddToRelationshipService : IAddToRelationshipService where TResource : class, IIdentifiable - { } + { + } /// [PublicAPI] @@ -21,10 +22,19 @@ public interface IAddToRelationshipService /// /// Handles a JSON:API request to add resources to a to-many relationship. /// - /// The identifier of the primary resource. - /// The relationship to add resources to. - /// The set of resources to add to the relationship. - /// Propagates notification that request handling should be canceled. - Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken); + /// + /// The identifier of the primary resource. + /// + /// + /// The relationship to add resources to. + /// + /// + /// The set of resources to add to the relationship. + /// + /// + /// Propagates notification that request handling should be canceled. + /// + Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/ICreateService.cs b/src/JsonApiDotNetCore/Services/ICreateService.cs index 5022604cb3..af735de513 100644 --- a/src/JsonApiDotNetCore/Services/ICreateService.cs +++ b/src/JsonApiDotNetCore/Services/ICreateService.cs @@ -7,7 +7,8 @@ namespace JsonApiDotNetCore.Services /// public interface ICreateService : ICreateService where TResource : class, IIdentifiable - { } + { + } /// public interface ICreateService diff --git a/src/JsonApiDotNetCore/Services/IDeleteService.cs b/src/JsonApiDotNetCore/Services/IDeleteService.cs index 5dcaebdf19..b3a801208d 100644 --- a/src/JsonApiDotNetCore/Services/IDeleteService.cs +++ b/src/JsonApiDotNetCore/Services/IDeleteService.cs @@ -9,7 +9,8 @@ namespace JsonApiDotNetCore.Services /// public interface IDeleteService : IDeleteService where TResource : class, IIdentifiable - { } + { + } /// public interface IDeleteService diff --git a/src/JsonApiDotNetCore/Services/IGetAllService.cs b/src/JsonApiDotNetCore/Services/IGetAllService.cs index eee7963a18..bab5aeab31 100644 --- a/src/JsonApiDotNetCore/Services/IGetAllService.cs +++ b/src/JsonApiDotNetCore/Services/IGetAllService.cs @@ -8,7 +8,8 @@ namespace JsonApiDotNetCore.Services /// public interface IGetAllService : IGetAllService where TResource : class, IIdentifiable - { } + { + } /// public interface IGetAllService diff --git a/src/JsonApiDotNetCore/Services/IGetByIdService.cs b/src/JsonApiDotNetCore/Services/IGetByIdService.cs index 42a23351d5..d383cf7afc 100644 --- a/src/JsonApiDotNetCore/Services/IGetByIdService.cs +++ b/src/JsonApiDotNetCore/Services/IGetByIdService.cs @@ -7,7 +7,8 @@ namespace JsonApiDotNetCore.Services /// public interface IGetByIdService : IGetByIdService where TResource : class, IIdentifiable - { } + { + } /// public interface IGetByIdService diff --git a/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs b/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs index 575f8ca6bf..191457172d 100644 --- a/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs @@ -9,7 +9,8 @@ namespace JsonApiDotNetCore.Services /// public interface IGetRelationshipService : IGetRelationshipService where TResource : class, IIdentifiable - { } + { + } /// public interface IGetRelationshipService diff --git a/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs b/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs index b035aa6327..949de5a5ac 100644 --- a/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs +++ b/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs @@ -9,14 +9,16 @@ namespace JsonApiDotNetCore.Services /// public interface IGetSecondaryService : IGetSecondaryService where TResource : class, IIdentifiable - { } + { + } /// public interface IGetSecondaryService where TResource : class, IIdentifiable { /// - /// Handles a JSON:API request to retrieve a single resource or a collection of resources for a secondary endpoint, such as /articles/1/author or /articles/1/revisions. + /// Handles a JSON:API request to retrieve a single resource or a collection of resources for a secondary endpoint, such as /articles/1/author or + /// /articles/1/revisions. /// Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs index 56b3d5137b..c322732cc7 100644 --- a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs @@ -10,7 +10,8 @@ namespace JsonApiDotNetCore.Services /// public interface IRemoveFromRelationshipService : IRemoveFromRelationshipService where TResource : class, IIdentifiable - { } + { + } /// public interface IRemoveFromRelationshipService @@ -19,10 +20,19 @@ public interface IRemoveFromRelationshipService /// /// Handles a JSON:API request to remove resources from a to-many relationship. /// - /// The identifier of the primary resource. - /// The relationship to remove resources from. - /// The set of resources to remove from the relationship. - /// Propagates notification that request handling should be canceled. - Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken); + /// + /// The identifier of the primary resource. + /// + /// + /// The relationship to remove resources from. + /// + /// + /// The set of resources to remove from the relationship. + /// + /// + /// Propagates notification that request handling should be canceled. + /// + Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IResourceCommandService.cs b/src/JsonApiDotNetCore/Services/IResourceCommandService.cs index a769f90f4c..334fdb26fa 100644 --- a/src/JsonApiDotNetCore/Services/IResourceCommandService.cs +++ b/src/JsonApiDotNetCore/Services/IResourceCommandService.cs @@ -5,30 +5,29 @@ namespace JsonApiDotNetCore.Services /// /// Groups write operations. /// - /// The resource type. - public interface IResourceCommandService : - ICreateService, - IAddToRelationshipService, - IUpdateService, - ISetRelationshipService, - IDeleteService, - IRemoveFromRelationshipService, - IResourceCommandService + /// + /// The resource type. + /// + public interface IResourceCommandService + : ICreateService, IAddToRelationshipService, IUpdateService, ISetRelationshipService, + IDeleteService, IRemoveFromRelationshipService, IResourceCommandService where TResource : class, IIdentifiable - { } + { + } /// /// Groups write operations. /// - /// The resource type. - /// The resource identifier type. - public interface IResourceCommandService : - ICreateService, - IAddToRelationshipService, - IUpdateService, - ISetRelationshipService, - IDeleteService, - IRemoveFromRelationshipService + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// + public interface IResourceCommandService + : ICreateService, IAddToRelationshipService, IUpdateService, ISetRelationshipService, + IDeleteService, IRemoveFromRelationshipService where TResource : class, IIdentifiable - { } + { + } } diff --git a/src/JsonApiDotNetCore/Services/IResourceQueryService.cs b/src/JsonApiDotNetCore/Services/IResourceQueryService.cs index dd337a6bfc..07c89e8643 100644 --- a/src/JsonApiDotNetCore/Services/IResourceQueryService.cs +++ b/src/JsonApiDotNetCore/Services/IResourceQueryService.cs @@ -5,26 +5,28 @@ namespace JsonApiDotNetCore.Services /// /// Groups read operations. /// - /// The resource type. - public interface IResourceQueryService : - IGetAllService, - IGetByIdService, - IGetRelationshipService, - IGetSecondaryService, - IResourceQueryService + /// + /// The resource type. + /// + public interface IResourceQueryService + : IGetAllService, IGetByIdService, IGetRelationshipService, IGetSecondaryService, + IResourceQueryService where TResource : class, IIdentifiable - { } + { + } /// /// Groups read operations. /// - /// The resource type. - /// The resource identifier type. - public interface IResourceQueryService : - IGetAllService, - IGetByIdService, - IGetRelationshipService, - IGetSecondaryService + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// + public interface IResourceQueryService + : IGetAllService, IGetByIdService, IGetRelationshipService, IGetSecondaryService where TResource : class, IIdentifiable - { } + { + } } diff --git a/src/JsonApiDotNetCore/Services/IResourceService.cs b/src/JsonApiDotNetCore/Services/IResourceService.cs index 126f6b43b1..eb1a744c1b 100644 --- a/src/JsonApiDotNetCore/Services/IResourceService.cs +++ b/src/JsonApiDotNetCore/Services/IResourceService.cs @@ -5,19 +5,25 @@ namespace JsonApiDotNetCore.Services /// /// Represents the foundational Resource Service layer in the JsonApiDotNetCore architecture that uses a Resource Repository for data access. /// - /// The resource type. - public interface IResourceService - : IResourceCommandService, IResourceQueryService, IResourceService + /// + /// The resource type. + /// + public interface IResourceService : IResourceCommandService, IResourceQueryService, IResourceService where TResource : class, IIdentifiable - { } + { + } /// /// Represents the foundational Resource Service layer in the JsonApiDotNetCore architecture that uses a Resource Repository for data access. /// - /// The resource type. - /// The resource identifier type. - public interface IResourceService - : IResourceCommandService, IResourceQueryService + /// + /// The resource type. + /// + /// + /// The resource identifier type. + /// + public interface IResourceService : IResourceCommandService, IResourceQueryService where TResource : class, IIdentifiable - { } + { + } } diff --git a/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs b/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs index 46ec2153f1..28904f1a28 100644 --- a/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs @@ -9,7 +9,8 @@ namespace JsonApiDotNetCore.Services /// public interface ISetRelationshipService : ISetRelationshipService where TResource : class, IIdentifiable - { } + { + } /// public interface ISetRelationshipService @@ -18,10 +19,18 @@ public interface ISetRelationshipService /// /// Handles a JSON:API request to perform a complete replacement of a relationship on an existing resource. /// - /// The identifier of the primary resource. - /// The relationship for which to perform a complete replacement. - /// The resource or set of resources to assign to the relationship. - /// Propagates notification that request handling should be canceled. + /// + /// The identifier of the primary resource. + /// + /// + /// The relationship for which to perform a complete replacement. + /// + /// + /// The resource or set of resources to assign to the relationship. + /// + /// + /// Propagates notification that request handling should be canceled. + /// Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IUpdateService.cs b/src/JsonApiDotNetCore/Services/IUpdateService.cs index 13d3b590b1..83e88f6e96 100644 --- a/src/JsonApiDotNetCore/Services/IUpdateService.cs +++ b/src/JsonApiDotNetCore/Services/IUpdateService.cs @@ -7,15 +7,16 @@ namespace JsonApiDotNetCore.Services /// public interface IUpdateService : IUpdateService where TResource : class, IIdentifiable - { } + { + } /// public interface IUpdateService where TResource : class, IIdentifiable { /// - /// Handles a JSON:API request to update the attributes and/or relationships of an existing resource. - /// Only the values of sent attributes are replaced. And only the values of sent relationships are replaced. + /// Handles a JSON:API request to update the attributes and/or relationships of an existing resource. Only the values of sent attributes are replaced. + /// And only the values of sent relationships are replaced. /// Task UpdateAsync(TId id, TResource resource, CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs index 6b08585445..0b472d9d6e 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs @@ -12,6 +12,7 @@ using JsonApiDotNetCore.Hooks.Internal.Execution; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Queries; +using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Repositories; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; @@ -21,8 +22,7 @@ namespace JsonApiDotNetCore.Services { /// [PublicAPI] - public class JsonApiResourceService : - IResourceService + public class JsonApiResourceService : IResourceService where TResource : class, IIdentifiable { private readonly IResourceRepositoryAccessor _repositoryAccessor; @@ -34,15 +34,9 @@ public class JsonApiResourceService : private readonly IResourceChangeTracker _resourceChangeTracker; private readonly IResourceHookExecutorFacade _hookExecutor; - public JsonApiResourceService( - IResourceRepositoryAccessor repositoryAccessor, - IQueryLayerComposer queryLayerComposer, - IPaginationContext paginationContext, - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IJsonApiRequest request, - IResourceChangeTracker resourceChangeTracker, - IResourceHookExecutorFacade hookExecutor) + public JsonApiResourceService(IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, + IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, + IResourceChangeTracker resourceChangeTracker, IResourceHookExecutorFacade hookExecutor) { ArgumentGuard.NotNull(repositoryAccessor, nameof(repositoryAccessor)); ArgumentGuard.NotNull(queryLayerComposer, nameof(queryLayerComposer)); @@ -72,7 +66,7 @@ public virtual async Task> GetAsync(CancellationT if (_options.IncludeTotalResourceCount) { - var topFilter = _queryLayerComposer.GetTopFilterFromConstraints(_request.PrimaryResource); + FilterExpression topFilter = _queryLayerComposer.GetTopFilterFromConstraints(_request.PrimaryResource); _paginationContext.TotalResourceCount = await _repositoryAccessor.CountAsync(topFilter, cancellationToken); if (_paginationContext.TotalResourceCount == 0) @@ -81,8 +75,8 @@ public virtual async Task> GetAsync(CancellationT } } - var queryLayer = _queryLayerComposer.ComposeFromConstraints(_request.PrimaryResource); - var resources = await _repositoryAccessor.GetAsync(queryLayer, cancellationToken); + QueryLayer queryLayer = _queryLayerComposer.ComposeFromConstraints(_request.PrimaryResource); + IReadOnlyCollection resources = await _repositoryAccessor.GetAsync(queryLayer, cancellationToken); if (queryLayer.Pagination?.PageSize != null && queryLayer.Pagination.PageSize.Value == resources.Count) { @@ -96,11 +90,14 @@ public virtual async Task> GetAsync(CancellationT /// public virtual async Task GetAsync(TId id, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id}); + _traceWriter.LogMethodStart(new + { + id + }); _hookExecutor.BeforeReadSingle(id, ResourcePipeline.GetSingle); - var primaryResource = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.PreserveExisting, cancellationToken); + TResource primaryResource = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.PreserveExisting, cancellationToken); AssertPrimaryResourceExists(primaryResource); _hookExecutor.AfterReadSingle(primaryResource, ResourcePipeline.GetSingle); @@ -112,13 +109,18 @@ public virtual async Task GetAsync(TId id, CancellationToken cancella /// public virtual async Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id, relationshipName}); + _traceWriter.LogMethodStart(new + { + id, + relationshipName + }); + AssertHasRelationship(_request.Relationship, relationshipName); _hookExecutor.BeforeReadSingle(id, ResourcePipeline.GetRelationship); - var secondaryLayer = _queryLayerComposer.ComposeFromConstraints(_request.SecondaryResource); - var primaryLayer = _queryLayerComposer.WrapLayerForSecondaryEndpoint(secondaryLayer, _request.PrimaryResource, id, _request.Relationship); + QueryLayer secondaryLayer = _queryLayerComposer.ComposeFromConstraints(_request.SecondaryResource); + QueryLayer primaryLayer = _queryLayerComposer.WrapLayerForSecondaryEndpoint(secondaryLayer, _request.PrimaryResource, id, _request.Relationship); if (_request.IsCollection && _options.IncludeTotalResourceCount) { @@ -128,17 +130,16 @@ public virtual async Task GetSecondaryAsync(TId id, string relationshipN // And we should call BlogResourceDefinition.OnApplyFilter to filter out soft-deleted blogs and translate from equals('IsDeleted','false') to equals('Blog.IsDeleted','false') } - var primaryResources = await _repositoryAccessor.GetAsync(primaryLayer, cancellationToken); + IReadOnlyCollection primaryResources = await _repositoryAccessor.GetAsync(primaryLayer, cancellationToken); - var primaryResource = primaryResources.SingleOrDefault(); + TResource primaryResource = primaryResources.SingleOrDefault(); AssertPrimaryResourceExists(primaryResource); _hookExecutor.AfterReadSingle(primaryResource, ResourcePipeline.GetRelationship); - var secondaryResourceOrResources = _request.Relationship.GetValue(primaryResource); + object secondaryResourceOrResources = _request.Relationship.GetValue(primaryResource); - if (secondaryResourceOrResources is ICollection secondaryResources && - secondaryLayer.Pagination?.PageSize?.Value == secondaryResources.Count) + if (secondaryResourceOrResources is ICollection secondaryResources && secondaryLayer.Pagination?.PageSize?.Value == secondaryResources.Count) { _paginationContext.IsPageFull = true; } @@ -149,7 +150,11 @@ public virtual async Task GetSecondaryAsync(TId id, string relationshipN /// public virtual async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id, relationshipName}); + _traceWriter.LogMethodStart(new + { + id, + relationshipName + }); ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); @@ -157,17 +162,17 @@ public virtual async Task GetRelationshipAsync(TId id, string relationsh _hookExecutor.BeforeReadSingle(id, ResourcePipeline.GetRelationship); - var secondaryLayer = _queryLayerComposer.ComposeSecondaryLayerForRelationship(_request.SecondaryResource); - var primaryLayer = _queryLayerComposer.WrapLayerForSecondaryEndpoint(secondaryLayer, _request.PrimaryResource, id, _request.Relationship); + QueryLayer secondaryLayer = _queryLayerComposer.ComposeSecondaryLayerForRelationship(_request.SecondaryResource); + QueryLayer primaryLayer = _queryLayerComposer.WrapLayerForSecondaryEndpoint(secondaryLayer, _request.PrimaryResource, id, _request.Relationship); - var primaryResources = await _repositoryAccessor.GetAsync(primaryLayer, cancellationToken); + IReadOnlyCollection primaryResources = await _repositoryAccessor.GetAsync(primaryLayer, cancellationToken); - var primaryResource = primaryResources.SingleOrDefault(); + TResource primaryResource = primaryResources.SingleOrDefault(); AssertPrimaryResourceExists(primaryResource); _hookExecutor.AfterReadSingle(primaryResource, ResourcePipeline.GetRelationship); - var secondaryResourceOrResources = _request.Relationship.GetValue(primaryResource); + object secondaryResourceOrResources = _request.Relationship.GetValue(primaryResource); return _hookExecutor.OnReturnRelationship(secondaryResourceOrResources); } @@ -175,11 +180,14 @@ public virtual async Task GetRelationshipAsync(TId id, string relationsh /// public virtual async Task CreateAsync(TResource resource, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {resource}); + _traceWriter.LogMethodStart(new + { + resource + }); ArgumentGuard.NotNull(resource, nameof(resource)); - var resourceFromRequest = resource; + TResource resourceFromRequest = resource; _resourceChangeTracker.SetRequestedAttributeValues(resourceFromRequest); _hookExecutor.BeforeCreate(resourceFromRequest); @@ -196,7 +204,9 @@ public virtual async Task CreateAsync(TResource resource, Cancellatio { if (!Equals(resourceFromRequest.Id, default(TId))) { - var existingResource = await TryGetPrimaryResourceByIdAsync(resourceFromRequest.Id, TopFieldSelection.OnlyIdAttribute, cancellationToken); + TResource existingResource = + await TryGetPrimaryResourceByIdAsync(resourceFromRequest.Id, TopFieldSelection.OnlyIdAttribute, cancellationToken); + if (existingResource != null) { throw new ResourceAlreadyExistsException(resourceFromRequest.StringId, _request.PrimaryResource.PublicName); @@ -207,7 +217,9 @@ public virtual async Task CreateAsync(TResource resource, Cancellatio throw; } - var resourceFromDatabase = await TryGetPrimaryResourceByIdAsync(resourceForDatabase.Id, TopFieldSelection.WithAllAttributes, cancellationToken); + TResource resourceFromDatabase = + await TryGetPrimaryResourceByIdAsync(resourceForDatabase.Id, TopFieldSelection.WithAllAttributes, cancellationToken); + AssertPrimaryResourceExists(resourceFromDatabase); _hookExecutor.AfterCreate(resourceFromDatabase); @@ -215,6 +227,7 @@ public virtual async Task CreateAsync(TResource resource, Cancellatio _resourceChangeTracker.SetFinallyStoredAttributeValues(resourceFromDatabase); bool hasImplicitChanges = _resourceChangeTracker.HasImplicitChanges(); + if (!hasImplicitChanges) { return null; @@ -228,12 +241,14 @@ private async Task AssertResourcesToAssignInRelationshipsExistAsync(TResource re { var missingResources = new List(); - foreach (var (queryLayer, relationship) in _queryLayerComposer.ComposeForGetTargetedSecondaryResourceIds(resource)) + foreach ((QueryLayer queryLayer, RelationshipAttribute relationship) in _queryLayerComposer.ComposeForGetTargetedSecondaryResourceIds(resource)) { object rightValue = relationship.GetValue(resource); ICollection rightResourceIds = TypeHelper.ExtractResources(rightValue); - var missingResourcesInRelationship = GetMissingRightResourcesAsync(queryLayer, relationship, rightResourceIds, cancellationToken); + IAsyncEnumerable missingResourcesInRelationship = + GetMissingRightResourcesAsync(queryLayer, relationship, rightResourceIds, cancellationToken); + await missingResources.AddRangeAsync(missingResourcesInRelationship, cancellationToken); } @@ -243,29 +258,33 @@ private async Task AssertResourcesToAssignInRelationshipsExistAsync(TResource re } } - private async IAsyncEnumerable GetMissingRightResourcesAsync( - QueryLayer existingRightResourceIdsQueryLayer, RelationshipAttribute relationship, - ICollection rightResourceIds, [EnumeratorCancellation] CancellationToken cancellationToken) + private async IAsyncEnumerable GetMissingRightResourcesAsync(QueryLayer existingRightResourceIdsQueryLayer, + RelationshipAttribute relationship, ICollection rightResourceIds, [EnumeratorCancellation] CancellationToken cancellationToken) { - var existingResources = await _repositoryAccessor.GetAsync( + IReadOnlyCollection existingResources = await _repositoryAccessor.GetAsync( existingRightResourceIdsQueryLayer.ResourceContext.ResourceType, existingRightResourceIdsQueryLayer, cancellationToken); - var existingResourceIds = existingResources.Select(resource => resource.StringId).ToArray(); + string[] existingResourceIds = existingResources.Select(resource => resource.StringId).ToArray(); - foreach (var rightResourceId in rightResourceIds) + foreach (IIdentifiable rightResourceId in rightResourceIds) { if (!existingResourceIds.Contains(rightResourceId.StringId)) { - yield return new MissingResourceInRelationship(relationship.PublicName, - existingRightResourceIdsQueryLayer.ResourceContext.PublicName, rightResourceId.StringId); + yield return new MissingResourceInRelationship(relationship.PublicName, existingRightResourceIdsQueryLayer.ResourceContext.PublicName, + rightResourceId.StringId); } } } /// - public async Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) + public async Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {primaryId, secondaryResourceIds}); + _traceWriter.LogMethodStart(new + { + primaryId, + secondaryResourceIds + }); ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); @@ -287,7 +306,7 @@ public async Task AddToToManyRelationshipAsync(TId primaryId, string relationshi } catch (DataStoreUpdateException) { - var primaryResource = await TryGetPrimaryResourceByIdAsync(primaryId, TopFieldSelection.OnlyIdAttribute, cancellationToken); + TResource primaryResource = await TryGetPrimaryResourceByIdAsync(primaryId, TopFieldSelection.OnlyIdAttribute, cancellationToken); AssertPrimaryResourceExists(primaryResource); await AssertResourcesExistAsync(secondaryResourceIds, cancellationToken); @@ -299,23 +318,25 @@ public async Task AddToToManyRelationshipAsync(TId primaryId, string relationshi private async Task RemoveExistingIdsFromSecondarySetAsync(TId primaryId, ISet secondaryResourceIds, HasManyThroughAttribute hasManyThrough, CancellationToken cancellationToken) { - var queryLayer = _queryLayerComposer.ComposeForHasMany(hasManyThrough, primaryId, secondaryResourceIds); - var primaryResources = await _repositoryAccessor.GetAsync(queryLayer, cancellationToken); - - var primaryResource = primaryResources.FirstOrDefault(); + QueryLayer queryLayer = _queryLayerComposer.ComposeForHasMany(hasManyThrough, primaryId, secondaryResourceIds); + IReadOnlyCollection primaryResources = await _repositoryAccessor.GetAsync(queryLayer, cancellationToken); + + TResource primaryResource = primaryResources.FirstOrDefault(); AssertPrimaryResourceExists(primaryResource); - var rightValue = _request.Relationship.GetValue(primaryResource); - var existingRightResourceIds = TypeHelper.ExtractResources(rightValue); + object rightValue = _request.Relationship.GetValue(primaryResource); + ICollection existingRightResourceIds = TypeHelper.ExtractResources(rightValue); secondaryResourceIds.ExceptWith(existingRightResourceIds); } private async Task AssertResourcesExistAsync(ICollection secondaryResourceIds, CancellationToken cancellationToken) { - var queryLayer = _queryLayerComposer.ComposeForGetRelationshipRightIds(_request.Relationship, secondaryResourceIds); + QueryLayer queryLayer = _queryLayerComposer.ComposeForGetRelationshipRightIds(_request.Relationship, secondaryResourceIds); + + List missingResources = + await GetMissingRightResourcesAsync(queryLayer, _request.Relationship, secondaryResourceIds, cancellationToken).ToListAsync(cancellationToken); - var missingResources = await GetMissingRightResourcesAsync(queryLayer, _request.Relationship, secondaryResourceIds, cancellationToken).ToListAsync(cancellationToken); if (missingResources.Any()) { throw new ResourcesInRelationshipsNotFoundException(missingResources); @@ -325,11 +346,15 @@ private async Task AssertResourcesExistAsync(ICollection secondar /// public virtual async Task UpdateAsync(TId id, TResource resource, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id, resource}); + _traceWriter.LogMethodStart(new + { + id, + resource + }); ArgumentGuard.NotNull(resource, nameof(resource)); - var resourceFromRequest = resource; + TResource resourceFromRequest = resource; _resourceChangeTracker.SetRequestedAttributeValues(resourceFromRequest); _hookExecutor.BeforeUpdateResource(resourceFromRequest); @@ -356,6 +381,7 @@ public virtual async Task UpdateAsync(TId id, TResource resource, Can _resourceChangeTracker.SetFinallyStoredAttributeValues(afterResourceFromDatabase); bool hasImplicitChanges = _resourceChangeTracker.HasImplicitChanges(); + if (!hasImplicitChanges) { return null; @@ -368,7 +394,12 @@ public virtual async Task UpdateAsync(TId id, TResource resource, Can /// public virtual async Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {primaryId, relationshipName, secondaryResourceIds}); + _traceWriter.LogMethodStart(new + { + primaryId, + relationshipName, + secondaryResourceIds + }); ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); @@ -394,7 +425,10 @@ public virtual async Task SetRelationshipAsync(TId primaryId, string relationshi /// public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {id}); + _traceWriter.LogMethodStart(new + { + id + }); _hookExecutor.BeforeDelete(id); @@ -404,7 +438,7 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke } catch (DataStoreUpdateException) { - var primaryResource = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.OnlyIdAttribute, cancellationToken); + TResource primaryResource = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.OnlyIdAttribute, cancellationToken); AssertPrimaryResourceExists(primaryResource); throw; } @@ -413,9 +447,15 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke } /// - public async Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) + public async Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + CancellationToken cancellationToken) { - _traceWriter.LogMethodStart(new {primaryId, relationshipName, secondaryResourceIds}); + _traceWriter.LogMethodStart(new + { + primaryId, + relationshipName, + secondaryResourceIds + }); ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); @@ -433,15 +473,15 @@ public async Task RemoveFromToManyRelationshipAsync(TId primaryId, string relati private async Task TryGetPrimaryResourceByIdAsync(TId id, TopFieldSelection fieldSelection, CancellationToken cancellationToken) { - var primaryLayer = _queryLayerComposer.ComposeForGetById(id, _request.PrimaryResource, fieldSelection); + QueryLayer primaryLayer = _queryLayerComposer.ComposeForGetById(id, _request.PrimaryResource, fieldSelection); - var primaryResources = await _repositoryAccessor.GetAsync(primaryLayer, cancellationToken); + IReadOnlyCollection primaryResources = await _repositoryAccessor.GetAsync(primaryLayer, cancellationToken); return primaryResources.SingleOrDefault(); } private async Task GetPrimaryResourceForUpdateAsync(TId id, CancellationToken cancellationToken) { - var queryLayer = _queryLayerComposer.ComposeForUpdate(id, _request.PrimaryResource); + QueryLayer queryLayer = _queryLayerComposer.ComposeForUpdate(id, _request.PrimaryResource); var resource = await _repositoryAccessor.GetForUpdateAsync(queryLayer, cancellationToken); AssertPrimaryResourceExists(resource); @@ -470,23 +510,17 @@ private void AssertHasRelationship(RelationshipAttribute relationship, string na /// /// Represents the foundational Resource Service layer in the JsonApiDotNetCore architecture that uses a Resource Repository for data access. /// - /// The resource type. + /// + /// The resource type. + /// [PublicAPI] - public class JsonApiResourceService : JsonApiResourceService, - IResourceService + public class JsonApiResourceService : JsonApiResourceService, IResourceService where TResource : class, IIdentifiable { - public JsonApiResourceService( - IResourceRepositoryAccessor repositoryAccessor, - IQueryLayerComposer queryLayerComposer, - IPaginationContext paginationContext, - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IJsonApiRequest request, - IResourceChangeTracker resourceChangeTracker, - IResourceHookExecutorFacade hookExecutor) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, - resourceChangeTracker, hookExecutor) + public JsonApiResourceService(IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, + IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, + IResourceChangeTracker resourceChangeTracker, IResourceHookExecutorFacade hookExecutor) + : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, hookExecutor) { } } diff --git a/src/JsonApiDotNetCore/TypeHelper.cs b/src/JsonApiDotNetCore/TypeHelper.cs index c3b004979a..6ace45bfc0 100644 --- a/src/JsonApiDotNetCore/TypeHelper.cs +++ b/src/JsonApiDotNetCore/TypeHelper.cs @@ -13,7 +13,11 @@ internal static class TypeHelper { private static readonly Type[] HashSetCompatibleCollectionTypes = { - typeof(HashSet<>), typeof(ICollection<>), typeof(ISet<>), typeof(IEnumerable<>), typeof(IReadOnlyCollection<>) + typeof(HashSet<>), + typeof(ICollection<>), + typeof(ISet<>), + typeof(IEnumerable<>), + typeof(IReadOnlyCollection<>) }; public static object ConvertType(object value, Type type) @@ -31,12 +35,14 @@ public static object ConvertType(object value, Type type) } Type runtimeType = value.GetType(); + if (type == runtimeType || type.IsAssignableFrom(runtimeType)) { return value; } string stringValue = value.ToString(); + if (string.IsNullOrEmpty(stringValue)) { return GetDefaultValue(type); @@ -50,19 +56,19 @@ public static object ConvertType(object value, Type type) if (nonNullableType == typeof(Guid)) { Guid convertedValue = Guid.Parse(stringValue); - return isNullableTypeRequested ? (Guid?) convertedValue : convertedValue; + return isNullableTypeRequested ? (Guid?)convertedValue : convertedValue; } if (nonNullableType == typeof(DateTimeOffset)) { DateTimeOffset convertedValue = DateTimeOffset.Parse(stringValue); - return isNullableTypeRequested ? (DateTimeOffset?) convertedValue : convertedValue; + return isNullableTypeRequested ? (DateTimeOffset?)convertedValue : convertedValue; } if (nonNullableType == typeof(TimeSpan)) { TimeSpan convertedValue = TimeSpan.Parse(stringValue); - return isNullableTypeRequested ? (TimeSpan?) convertedValue : convertedValue; + return isNullableTypeRequested ? (TimeSpan?)convertedValue : convertedValue; } if (nonNullableType.IsEnum) @@ -76,11 +82,10 @@ public static object ConvertType(object value, Type type) // https://bradwilson.typepad.com/blog/2008/07/creating-nullab.html return Convert.ChangeType(stringValue, nonNullableType); } - catch (Exception exception) when (exception is FormatException || exception is OverflowException || - exception is InvalidCastException || exception is ArgumentException) + catch (Exception exception) when (exception is FormatException || exception is OverflowException || exception is InvalidCastException || + exception is ArgumentException) { - throw new FormatException( - $"Failed to convert '{value}' of type '{runtimeType.Name}' to type '{type.Name}'.", exception); + throw new FormatException($"Failed to convert '{value}' of type '{runtimeType.Name}' to type '{type.Name}'.", exception); } } @@ -111,8 +116,7 @@ public static Type TryGetCollectionElementType(Type type) } /// - /// Gets the property info that is referenced in the NavigationAction expression. - /// Credits: https://stackoverflow.com/a/17116267/4441216 + /// Gets the property info that is referenced in the NavigationAction expression. Credits: https://stackoverflow.com/a/17116267/4441216 /// public static PropertyInfo ParseNavigationExpression(Expression> navigationExpression) { @@ -147,20 +151,29 @@ public static PropertyInfo ParseNavigationExpression(Expression /// Creates an instance of the specified generic type /// - /// The instance of the parameterized generic type - /// Generic type parameters to be used in open type. - /// Constructor arguments to be provided in instantiation. - /// Open generic type + /// + /// The instance of the parameterized generic type + /// + /// + /// Generic type parameters to be used in open type. + /// + /// + /// Constructor arguments to be provided in instantiation. + /// + /// + /// Open generic type + /// private static object CreateInstanceOfOpenType(Type openType, Type[] parameters, params object[] constructorArguments) { - var parameterizedType = openType.MakeGenericType(parameters); + Type parameterizedType = openType.MakeGenericType(parameters); return Activator.CreateInstance(parameterizedType, constructorArguments); } /// - /// Helper method that "unboxes" the TValue from the relationship dictionary into + /// Helper method that "unboxes" the TValue from the relationship dictionary into /// - public static Dictionary> ConvertRelationshipDictionary(Dictionary relationships) + public static Dictionary> ConvertRelationshipDictionary( + Dictionary relationships) { return relationships.ToDictionary(pair => pair.Key, pair => (HashSet)pair.Value); } @@ -168,7 +181,8 @@ public static Dictionary> ConvertRelat /// /// Converts a dictionary of AttrAttributes to the underlying PropertyInfo that is referenced /// - public static Dictionary> ConvertAttributeDictionary(IEnumerable attributes, HashSet resources) + public static Dictionary> ConvertAttributeDictionary(IEnumerable attributes, + HashSet resources) { return attributes.ToDictionary(attr => attr.Property, _ => resources); } @@ -176,10 +190,18 @@ public static Dictionary> ConvertAttributeDicti /// /// Creates an instance of the specified generic type /// - /// The instance of the parameterized generic type - /// Generic type parameter to be used in open type. - /// Constructor arguments to be provided in instantiation. - /// Open generic type + /// + /// The instance of the parameterized generic type + /// + /// + /// Generic type parameter to be used in open type. + /// + /// + /// Constructor arguments to be provided in instantiation. + /// + /// + /// Open generic type + /// public static object CreateInstanceOfOpenType(Type openType, Type parameter, params object[] constructorArguments) { return CreateInstanceOfOpenType(openType, parameter.AsArray(), constructorArguments); @@ -190,13 +212,17 @@ public static object CreateInstanceOfOpenType(Type openType, Type parameter, par /// public static object CreateInstanceOfOpenType(Type openType, Type parameter, bool hasInternalConstructor, params object[] constructorArguments) { - Type[] parameters = {parameter}; + Type[] parameters = + { + parameter + }; + if (!hasInternalConstructor) { return CreateInstanceOfOpenType(openType, parameters, constructorArguments); } - var parameterizedType = openType.MakeGenericType(parameters); + Type parameterizedType = openType.MakeGenericType(parameters); // note that if for whatever reason the constructor of AffectedResource is set from // internal to public, this will throw an error, as it is looking for a non-public one. return Activator.CreateInstance(parameterizedType, BindingFlags.NonPublic | BindingFlags.Instance, null, constructorArguments, null); @@ -205,15 +231,19 @@ public static object CreateInstanceOfOpenType(Type openType, Type parameter, boo /// /// Reflectively instantiates a list of a certain type. /// - /// The list of the target type - /// The target type + /// + /// The list of the target type + /// + /// + /// The target type + /// public static IList CreateListFor(Type elementType) { return (IList)CreateInstanceOfOpenType(typeof(List<>), elementType); } /// - /// Reflectively instantiates a hashset of a certain type. + /// Reflectively instantiates a hashset of a certain type. /// public static IEnumerable CreateHashSetFor(Type type, object elements) { @@ -245,14 +275,14 @@ public static Type ToConcreteCollectionType(Type collectionType) } /// - /// Indicates whether a instance can be assigned to the specified type, - /// for example IList{Article} -> false or ISet{Article} -> true. + /// Indicates whether a instance can be assigned to the specified type, for example IList{Article} -> false or ISet{Article} -> + /// true. /// public static bool TypeCanContainHashSet(Type collectionType) { if (collectionType.IsGenericType) { - var openCollectionType = collectionType.GetGenericTypeDefinition(); + Type openCollectionType = collectionType.GetGenericTypeDefinition(); return HashSetCompatibleCollectionTypes.Contains(openCollectionType); } @@ -260,11 +290,12 @@ public static bool TypeCanContainHashSet(Type collectionType) } /// - /// Gets the type (such as Guid or int) of the Id property on a type that implements . + /// Gets the type (such as Guid or int) of the Id property on a type that implements . /// public static Type GetIdType(Type resourceType) { - var property = resourceType.GetProperty(nameof(Identifiable.Id)); + PropertyInfo? property = resourceType.GetProperty(nameof(Identifiable.Id)); + if (property == null) { throw new ArgumentException($"Type '{resourceType.Name}' does not have 'Id' property."); @@ -303,8 +334,7 @@ public static object CreateInstance(Type type) } catch (Exception exception) { - throw new InvalidOperationException( - $"Failed to create an instance of '{type.FullName}' using its default constructor.", exception); + throw new InvalidOperationException($"Failed to create an instance of '{type.FullName}' using its default constructor.", exception); } } @@ -321,8 +351,8 @@ public static IList CopyToList(IEnumerable copyFrom, Type elementType, Converter if (elementConverter != null) { - var converted = copyFrom.Cast().Select(element => elementConverter(element)); - return (IList) CopyToTypedCollection(converted, collectionType); + IEnumerable converted = copyFrom.Cast().Select(element => elementConverter(element)); + return (IList)CopyToTypedCollection(converted, collectionType); } return (IList)CopyToTypedCollection(copyFrom, collectionType); @@ -331,19 +361,23 @@ public static IList CopyToList(IEnumerable copyFrom, Type elementType, Converter /// /// Creates a collection instance based on the specified collection type and copies the specified elements into it. /// - /// Source to copy from. - /// Target collection type, for example: typeof(List{Article}) or typeof(ISet{Person}). + /// + /// Source to copy from. + /// + /// + /// Target collection type, for example: typeof(List{Article}) or typeof(ISet{Person}). + /// public static IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType) { ArgumentGuard.NotNull(source, nameof(source)); ArgumentGuard.NotNull(collectionType, nameof(collectionType)); - var concreteCollectionType = ToConcreteCollectionType(collectionType); + Type concreteCollectionType = ToConcreteCollectionType(collectionType); dynamic concreteCollectionInstance = CreateInstance(concreteCollectionType); - foreach (var item in source) + foreach (object? item in source) { - concreteCollectionInstance.Add((dynamic) item); + concreteCollectionInstance.Add((dynamic)item); } return concreteCollectionInstance; diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 4f568d9555..ecdc3569d6 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -57,14 +57,14 @@ public void Can_add_resources_from_assembly_to_graph() // Act facade.DiscoverResources(); - + // Assert - var resourceGraph = _resourceGraphBuilder.Build(); - - var personResource = resourceGraph.GetResourceContext(typeof(Person)); + IResourceGraph resourceGraph = _resourceGraphBuilder.Build(); + + ResourceContext personResource = resourceGraph.GetResourceContext(typeof(Person)); personResource.Should().NotBeNull(); - var articleResource = resourceGraph.GetResourceContext(typeof(Article)); + ResourceContext articleResource = resourceGraph.GetResourceContext(typeof(Article)); articleResource.Should().NotBeNull(); } @@ -77,11 +77,11 @@ public void Can_add_resource_from_current_assembly_to_graph() // Act facade.DiscoverResources(); - + // Assert - var resourceGraph = _resourceGraphBuilder.Build(); + IResourceGraph resourceGraph = _resourceGraphBuilder.Build(); - var resource = resourceGraph.GetResourceContext(typeof(TestResource)); + ResourceContext resource = resourceGraph.GetResourceContext(typeof(TestResource)); resource.Should().NotBeNull(); } @@ -91,12 +91,12 @@ public void Can_add_resource_service_from_current_assembly_to_container() // Arrange var facade = new ServiceDiscoveryFacade(_services, _resourceGraphBuilder, _options, LoggerFactory); facade.AddCurrentAssembly(); - + // Act facade.DiscoverInjectables(); // Assert - var services = _services.BuildServiceProvider(); + ServiceProvider services = _services.BuildServiceProvider(); var resourceService = services.GetRequiredService>(); resourceService.Should().BeOfType(); @@ -113,7 +113,7 @@ public void Can_add_resource_repository_from_current_assembly_to_container() facade.DiscoverInjectables(); // Assert - var services = _services.BuildServiceProvider(); + ServiceProvider services = _services.BuildServiceProvider(); var resourceRepository = services.GetRequiredService>(); resourceRepository.Should().BeOfType(); @@ -130,7 +130,7 @@ public void Can_add_resource_definition_from_current_assembly_to_container() facade.DiscoverInjectables(); // Assert - var services = _services.BuildServiceProvider(); + ServiceProvider services = _services.BuildServiceProvider(); var resourceDefinition = services.GetRequiredService>(); resourceDefinition.Should().BeOfType(); @@ -149,29 +149,24 @@ public void Can_add_resource_hooks_definition_from_current_assembly_to_container facade.DiscoverInjectables(); // Assert - var services = _services.BuildServiceProvider(); + ServiceProvider services = _services.BuildServiceProvider(); var resourceHooksDefinition = services.GetRequiredService>(); resourceHooksDefinition.Should().BeOfType(); } [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class TestResource : Identifiable { } + public sealed class TestResource : Identifiable + { + } [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class TestResourceService : JsonApiResourceService { - public TestResourceService( - IResourceRepositoryAccessor repositoryAccessor, - IQueryLayerComposer queryLayerComposer, - IPaginationContext paginationContext, - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IJsonApiRequest request, - IResourceChangeTracker resourceChangeTracker, - IResourceHookExecutorFacade hookExecutor) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, - resourceChangeTracker, hookExecutor) + public TestResourceService(IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, + IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, + IResourceChangeTracker resourceChangeTracker, IResourceHookExecutorFacade hookExecutor) + : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, hookExecutor) { } } @@ -179,27 +174,29 @@ public TestResourceService( [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class TestResourceRepository : EntityFrameworkCoreRepository { - public TestResourceRepository( - ITargetedFields targetedFields, - IDbContextResolver contextResolver, - IResourceGraph resourceGraph, - IResourceFactory resourceFactory, - IEnumerable constraintProviders, - ILoggerFactory loggerFactory) + public TestResourceRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) - { } + { + } } [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class TestResourceHooksDefinition : ResourceHooksDefinition { - public TestResourceHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public TestResourceHooksDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) + { + } } [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class TestResourceDefinition : JsonApiResourceDefinition { - public TestResourceDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public TestResourceDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) + { + } } } } diff --git a/test/JsonApiDotNetCoreExampleTests/ExampleIntegrationTestContext.cs b/test/JsonApiDotNetCoreExampleTests/ExampleIntegrationTestContext.cs index 311d18b4d1..50e796e08d 100644 --- a/test/JsonApiDotNetCoreExampleTests/ExampleIntegrationTestContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/ExampleIntegrationTestContext.cs @@ -7,8 +7,12 @@ namespace JsonApiDotNetCoreExampleTests /// /// A test context for tests that reference the JsonApiDotNetCoreExample project. /// - /// The server Startup class, which can be defined in the test project. - /// The EF Core database context, which can be defined in the test project. + /// + /// The server Startup class, which can be defined in the test project. + /// + /// + /// The EF Core database context, which can be defined in the test project. + /// public sealed class ExampleIntegrationTestContext : BaseIntegrationTestContext where TStartup : class where TDbContext : DbContext diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs index bcd75da764..ba68a661b0 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -25,8 +26,8 @@ public AtomicConstrainedOperationsControllerTests(ExampleIntegrationTestContext< public async Task Can_create_resources_for_matching_resource_type() { // Arrange - var newTitle1 = _fakers.MusicTrack.Generate().Title; - var newTitle2 = _fakers.MusicTrack.Generate().Title; + string newTitle1 = _fakers.MusicTrack.Generate().Title; + string newTitle2 = _fakers.MusicTrack.Generate().Title; var requestBody = new { @@ -62,7 +63,8 @@ public async Task Can_create_resources_for_matching_resource_type() const string route = "/operations/musicTracks/create"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -95,14 +97,14 @@ public async Task Cannot_create_resource_for_mismatching_resource_type() const string route = "/operations/musicTracks/create"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Unsupported combination of operation code and resource type at this endpoint."); error.Detail.Should().Be("This endpoint can only be used to create resources of type 'musicTracks'."); @@ -113,7 +115,7 @@ public async Task Cannot_create_resource_for_mismatching_resource_type() public async Task Cannot_update_resources_for_matching_resource_type() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -143,14 +145,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations/musicTracks/create"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Unsupported combination of operation code and resource type at this endpoint."); error.Detail.Should().Be("This endpoint can only be used to create resources of type 'musicTracks'."); @@ -161,8 +163,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_add_to_ToMany_relationship_for_matching_resource_type() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); - var existingPerformer = _fakers.Performer.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -198,14 +200,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations/musicTracks/create"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Unsupported combination of operation code and resource type at this endpoint."); error.Detail.Should().Be("This endpoint can only be used to create resources of type 'musicTracks'."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs index d4a69eb31a..21b4fd4ac5 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs @@ -19,8 +19,8 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Contro [Route("/operations/musicTracks/create")] public sealed class CreateMusicTrackOperationsController : JsonApiOperationsController { - public CreateMusicTrackOperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IOperationsProcessor processor, IJsonApiRequest request, ITargetedFields targetedFields) + public CreateMusicTrackOperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, + IJsonApiRequest request, ITargetedFields targetedFields) : base(options, loggerFactory, processor, request, targetedFields) { } @@ -35,7 +35,8 @@ public override async Task PostOperationsAsync(IList operations) { int index = 0; - foreach (var operation in operations) + + foreach (OperationContainer operation in operations) { if (operation.Kind != OperationKind.CreateResource || operation.Resource.GetType() != typeof(MusicTrack)) { diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs index 7c008892e3..d40d65b836 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Extensions; @@ -13,8 +14,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Creating { - public sealed class AtomicCreateResourceTests - : IClassFixture, OperationsDbContext>> + public sealed class AtomicCreateResourceTests : IClassFixture, OperationsDbContext>> { private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); @@ -30,8 +30,8 @@ public AtomicCreateResourceTests(ExampleIntegrationTestContext(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -68,11 +69,11 @@ public async Task Can_create_resource() responseDocument.Results[0].SingleData.Attributes["bornAt"].Should().BeCloseTo(newBornAt); responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - var newPerformerId = int.Parse(responseDocument.Results[0].SingleData.Id); + int newPerformerId = int.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var performerInDatabase = await dbContext.Performers.FirstWithIdAsync(newPerformerId); + Performer performerInDatabase = await dbContext.Performers.FirstWithIdAsync(newPerformerId); performerInDatabase.ArtistName.Should().Be(newArtistName); performerInDatabase.BornAt.Should().BeCloseTo(newBornAt); @@ -85,9 +86,10 @@ public async Task Can_create_resources() // Arrange const int elementCount = 5; - var newTracks = _fakers.MusicTrack.Generate(elementCount); + List newTracks = _fakers.MusicTrack.Generate(elementCount); var operationElements = new List(elementCount); + for (int index = 0; index < elementCount; index++) { operationElements.Add(new @@ -115,7 +117,8 @@ public async Task Can_create_resources() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -124,7 +127,7 @@ public async Task Can_create_resources() for (int index = 0; index < elementCount; index++) { - var singleData = responseDocument.Results[index].SingleData; + ResourceObject singleData = responseDocument.Results[index].SingleData; singleData.Should().NotBeNull(); singleData.Type.Should().Be("musicTracks"); @@ -135,19 +138,18 @@ public async Task Can_create_resources() singleData.Relationships.Should().NotBeEmpty(); } - var newTrackIds = responseDocument.Results.Select(result => Guid.Parse(result.SingleData.Id)); + IEnumerable newTrackIds = responseDocument.Results.Select(result => Guid.Parse(result.SingleData.Id)); await _testContext.RunOnDatabaseAsync(async dbContext => { - var tracksInDatabase = await dbContext.MusicTracks - .Where(musicTrack => newTrackIds.Contains(musicTrack.Id)) - .ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.Where(musicTrack => newTrackIds.Contains(musicTrack.Id)).ToListAsync(); tracksInDatabase.Should().HaveCount(elementCount); for (int index = 0; index < elementCount; index++) { - var trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == Guid.Parse(responseDocument.Results[index].SingleData.Id)); + MusicTrack trackInDatabase = + tracksInDatabase.Single(musicTrack => musicTrack.Id == Guid.Parse(responseDocument.Results[index].SingleData.Id)); trackInDatabase.Title.Should().Be(newTracks[index].Title); trackInDatabase.LengthInSeconds.Should().BeApproximately(newTracks[index].LengthInSeconds); @@ -185,7 +187,8 @@ public async Task Can_create_resource_without_attributes_or_relationships() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -197,11 +200,11 @@ public async Task Can_create_resource_without_attributes_or_relationships() responseDocument.Results[0].SingleData.Attributes["bornAt"].Should().BeCloseTo(default(DateTimeOffset)); responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - var newPerformerId = int.Parse(responseDocument.Results[0].SingleData.Id); + int newPerformerId = int.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var performerInDatabase = await dbContext.Performers.FirstWithIdAsync(newPerformerId); + Performer performerInDatabase = await dbContext.Performers.FirstWithIdAsync(newPerformerId); performerInDatabase.ArtistName.Should().BeNull(); performerInDatabase.BornAt.Should().Be(default); @@ -212,7 +215,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource_with_unknown_attribute() { // Arrange - var newName = _fakers.Playlist.Generate().Name; + string newName = _fakers.Playlist.Generate().Name; var requestBody = new { @@ -237,7 +240,8 @@ public async Task Can_create_resource_with_unknown_attribute() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -248,11 +252,11 @@ public async Task Can_create_resource_with_unknown_attribute() responseDocument.Results[0].SingleData.Attributes["name"].Should().Be(newName); responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty(); - var newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); + long newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var performerInDatabase = await dbContext.Playlists.FirstWithIdAsync(newPlaylistId); + Playlist performerInDatabase = await dbContext.Playlists.FirstWithIdAsync(newPlaylistId); performerInDatabase.Name.Should().Be(newName); }); @@ -291,7 +295,8 @@ public async Task Can_create_resource_with_unknown_relationship() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -302,11 +307,11 @@ public async Task Can_create_resource_with_unknown_relationship() responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty(); responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty(); - var newLyricId = long.Parse(responseDocument.Results[0].SingleData.Id); + long newLyricId = long.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var lyricInDatabase = await dbContext.Lyrics.FirstWithIdAsync(newLyricId); + Lyric lyricInDatabase = await dbContext.Lyrics.FirstWithIdAsync(newLyricId); lyricInDatabase.Should().NotBeNull(); }); @@ -316,7 +321,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_resource_with_client_generated_ID() { // Arrange - var newTitle = _fakers.MusicTrack.Generate().Title; + string newTitle = _fakers.MusicTrack.Generate().Title; var requestBody = new { @@ -341,14 +346,14 @@ public async Task Cannot_create_resource_with_client_generated_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("Specifying the resource ID in operations that create a resource is not allowed."); error.Detail.Should().BeNull(); @@ -374,14 +379,14 @@ public async Task Cannot_create_resource_for_href_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Usage of the 'href' element is not supported."); error.Detail.Should().BeNull(); @@ -410,14 +415,14 @@ public async Task Cannot_create_resource_for_ref_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.relationship' element is required."); error.Detail.Should().BeNull(); @@ -442,14 +447,14 @@ public async Task Cannot_create_resource_for_missing_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data' element is required."); error.Detail.Should().BeNull(); @@ -480,14 +485,14 @@ public async Task Cannot_create_resource_for_missing_type() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data.type' element is required."); error.Detail.Should().BeNull(); @@ -516,14 +521,14 @@ public async Task Cannot_create_resource_for_unknown_type() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -534,7 +539,7 @@ public async Task Cannot_create_resource_for_unknown_type() public async Task Cannot_create_resource_for_array() { // Arrange - var newArtistName = _fakers.Performer.Generate().ArtistName; + string newArtistName = _fakers.Performer.Generate().ArtistName; var requestBody = new { @@ -561,14 +566,14 @@ public async Task Cannot_create_resource_for_array() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected single data element for create/update resource operation."); error.Detail.Should().BeNull(); @@ -601,14 +606,14 @@ public async Task Cannot_create_resource_attribute_with_blocked_capability() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Setting the initial value of the requested attribute is not allowed."); error.Detail.Should().Be("Setting the initial value of 'createdAt' is not allowed."); @@ -619,7 +624,7 @@ public async Task Cannot_create_resource_attribute_with_blocked_capability() public async Task Cannot_create_resource_with_readonly_attribute() { // Arrange - var newPlaylistName = _fakers.Playlist.Generate().Name; + string newPlaylistName = _fakers.Playlist.Generate().Name; var requestBody = new { @@ -644,14 +649,14 @@ public async Task Cannot_create_resource_with_readonly_attribute() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Attribute is read-only."); error.Detail.Should().Be("Attribute 'isArchived' is read-only."); @@ -684,14 +689,14 @@ public async Task Cannot_create_resource_with_incompatible_attribute_value() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().StartWith("Failed to convert 'not-a-valid-time' of type 'String' to type 'DateTimeOffset'. - Request body:"); @@ -702,11 +707,11 @@ public async Task Cannot_create_resource_with_incompatible_attribute_value() public async Task Can_create_resource_with_attributes_and_multiple_relationship_types() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); - var existingCompany = _fakers.RecordCompany.Generate(); - var existingPerformer = _fakers.Performer.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); - var newTitle = _fakers.MusicTrack.Generate().Title; + string newTitle = _fakers.MusicTrack.Generate().Title; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -766,7 +771,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -777,14 +783,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[0].SingleData.Attributes["title"].Should().Be(newTitle); responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var trackInDatabase = await dbContext.MusicTracks + MusicTrack trackInDatabase = await dbContext.MusicTracks .Include(musicTrack => musicTrack.Lyric) .Include(musicTrack => musicTrack.OwnedBy) .Include(musicTrack => musicTrack.Performers) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs index 8e11640cfe..0bb4edb8e0 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -17,13 +18,14 @@ public sealed class AtomicCreateResourceWithClientGeneratedIdTests private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); - public AtomicCreateResourceWithClientGeneratedIdTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) + public AtomicCreateResourceWithClientGeneratedIdTests( + ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - var options = (JsonApiOptions) testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.AllowClientGeneratedIds = true; } @@ -31,7 +33,7 @@ public AtomicCreateResourceWithClientGeneratedIdTests(ExampleIntegrationTestCont public async Task Can_create_resource_with_client_generated_guid_ID_having_side_effects() { // Arrange - var newLanguage = _fakers.TextLanguage.Generate(); + TextLanguage newLanguage = _fakers.TextLanguage.Generate(); newLanguage.Id = Guid.NewGuid(); var requestBody = new @@ -57,7 +59,8 @@ public async Task Can_create_resource_with_client_generated_guid_ID_having_side_ const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -71,7 +74,7 @@ public async Task Can_create_resource_with_client_generated_guid_ID_having_side_ await _testContext.RunOnDatabaseAsync(async dbContext => { - var languageInDatabase = await dbContext.TextLanguages.FirstWithIdAsync(newLanguage.Id); + TextLanguage languageInDatabase = await dbContext.TextLanguages.FirstWithIdAsync(newLanguage.Id); languageInDatabase.IsoCode.Should().Be(newLanguage.IsoCode); }); @@ -81,7 +84,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource_with_client_generated_string_ID_having_no_side_effects() { // Arrange - var newTrack = _fakers.MusicTrack.Generate(); + MusicTrack newTrack = _fakers.MusicTrack.Generate(); newTrack.Id = Guid.NewGuid(); var requestBody = new @@ -108,7 +111,7 @@ public async Task Can_create_resource_with_client_generated_string_ID_having_no_ const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -117,7 +120,7 @@ public async Task Can_create_resource_with_client_generated_string_ID_having_no_ await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(newTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(newTrack.Id); trackInDatabase.Title.Should().Be(newTrack.Title); trackInDatabase.LengthInSeconds.Should().BeApproximately(newTrack.LengthInSeconds); @@ -128,10 +131,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_resource_for_existing_client_generated_ID() { // Arrange - var existingLanguage = _fakers.TextLanguage.Generate(); + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); existingLanguage.Id = Guid.NewGuid(); - var languageToCreate = _fakers.TextLanguage.Generate(); + TextLanguage languageToCreate = _fakers.TextLanguage.Generate(); languageToCreate.Id = existingLanguage.Id; await _testContext.RunOnDatabaseAsync(async dbContext => @@ -163,14 +166,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Conflict); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.Conflict); error.Title.Should().Be("Another resource with the specified ID already exists."); error.Detail.Should().Be($"Another resource of type 'textLanguages' with ID '{languageToCreate.StringId}' already exists."); @@ -181,7 +184,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_resource_for_incompatible_ID() { // Arrange - var guid = Guid.NewGuid().ToString(); + string guid = Guid.NewGuid().ToString(); var requestBody = new { @@ -205,14 +208,14 @@ public async Task Cannot_create_resource_for_incompatible_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().Be($"Failed to convert '{guid}' of type 'String' to type 'Int32'."); @@ -243,14 +246,14 @@ public async Task Cannot_create_resource_for_ID_and_local_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data.id' or 'data.lid' element is required."); error.Detail.Should().BeNull(); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs index bbaedf4faa..dd6a0e90c6 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -16,7 +18,8 @@ public sealed class AtomicCreateResourceWithToManyRelationshipTests private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); - public AtomicCreateResourceWithToManyRelationshipTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) + public AtomicCreateResourceWithToManyRelationshipTests( + ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; @@ -27,8 +30,8 @@ public AtomicCreateResourceWithToManyRelationshipTests(ExampleIntegrationTestCon public async Task Can_create_HasMany_relationship() { // Arrange - var existingPerformers = _fakers.Performer.Generate(2); - var newTitle = _fakers.MusicTrack.Generate().Title; + List existingPerformers = _fakers.Performer.Generate(2); + string newTitle = _fakers.MusicTrack.Generate().Title; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -77,7 +80,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -88,13 +92,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty(); responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(newTrackId); trackInDatabase.Performers.Should().HaveCount(2); trackInDatabase.Performers.Should().ContainSingle(performer => performer.Id == existingPerformers[0].Id); @@ -106,8 +108,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_HasManyThrough_relationship() { // Arrange - var existingTracks = _fakers.MusicTrack.Generate(3); - var newName = _fakers.Playlist.Generate().Name; + List existingTracks = _fakers.MusicTrack.Generate(3); + string newName = _fakers.Playlist.Generate().Name; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -161,7 +163,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -172,14 +175,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty(); responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty(); - var newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); + long newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(newPlaylistId); @@ -229,14 +232,14 @@ public async Task Cannot_create_for_missing_relationship_type() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'type' element."); error.Detail.Should().Be("Expected 'type' element in 'performers' relationship."); @@ -279,14 +282,14 @@ public async Task Cannot_create_for_unknown_relationship_type() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -328,14 +331,14 @@ public async Task Cannot_create_for_missing_relationship_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'id' or 'lid' element."); error.Detail.Should().Be("Expected 'id' or 'lid' element in 'performers' relationship."); @@ -346,7 +349,7 @@ public async Task Cannot_create_for_missing_relationship_ID() public async Task Cannot_create_for_unknown_relationship_IDs() { // Arrange - var newTitle = _fakers.MusicTrack.Generate().Title; + string newTitle = _fakers.MusicTrack.Generate().Title; var requestBody = new { @@ -389,20 +392,20 @@ public async Task Cannot_create_for_unknown_relationship_IDs() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(2); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.NotFound); error1.Title.Should().Be("A related resource does not exist."); error1.Detail.Should().Be("Related resource of type 'performers' with ID '12345678' in relationship 'performers' does not exist."); error1.Source.Pointer.Should().Be("/atomic:operations[0]"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.NotFound); error2.Title.Should().Be("A related resource does not exist."); error2.Detail.Should().Be("Related resource of type 'performers' with ID '87654321' in relationship 'performers' does not exist."); @@ -445,14 +448,14 @@ public async Task Cannot_create_on_relationship_type_mismatch() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Relationship contains incompatible resource type."); error.Detail.Should().Be("Relationship 'performers' contains incompatible resource type 'playlists'."); @@ -463,8 +466,8 @@ public async Task Cannot_create_on_relationship_type_mismatch() public async Task Can_create_with_duplicates() { // Arrange - var existingPerformer = _fakers.Performer.Generate(); - var newTitle = _fakers.MusicTrack.Generate().Title; + Performer existingPerformer = _fakers.Performer.Generate(); + string newTitle = _fakers.MusicTrack.Generate().Title; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -513,7 +516,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -524,13 +528,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty(); responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(newTrackId); trackInDatabase.Performers.Should().HaveCount(1); trackInDatabase.Performers[0].Id.Should().Be(existingPerformer.Id); @@ -555,7 +557,7 @@ public async Task Cannot_create_with_null_data_in_HasMany_relationship() { performers = new { - data = (object) null + data = (object)null } } } @@ -566,14 +568,14 @@ public async Task Cannot_create_with_null_data_in_HasMany_relationship() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected data[] element for to-many relationship."); error.Detail.Should().Be("Expected data[] element for 'performers' relationship."); @@ -598,7 +600,7 @@ public async Task Cannot_create_with_null_data_in_HasManyThrough_relationship() { tracks = new { - data = (object) null + data = (object)null } } } @@ -609,14 +611,14 @@ public async Task Cannot_create_with_null_data_in_HasManyThrough_relationship() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected data[] element for to-many relationship."); error.Detail.Should().Be("Expected data[] element for 'tracks' relationship."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs index 999e214e5c..84107d9b63 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -19,7 +20,8 @@ public sealed class AtomicCreateResourceWithToOneRelationshipTests private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); - public AtomicCreateResourceWithToOneRelationshipTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) + public AtomicCreateResourceWithToOneRelationshipTests( + ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; @@ -30,7 +32,7 @@ public AtomicCreateResourceWithToOneRelationshipTests(ExampleIntegrationTestCont public async Task Can_create_OneToOne_relationship_from_principal_side() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -67,7 +69,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -78,13 +81,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty(); responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty(); - var newLyricId = long.Parse(responseDocument.Results[0].SingleData.Id); + long newLyricId = long.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var lyricInDatabase = await dbContext.Lyrics - .Include(lyric => lyric.Track) - .FirstWithIdAsync(newLyricId); + Lyric lyricInDatabase = await dbContext.Lyrics.Include(lyric => lyric.Track).FirstWithIdAsync(newLyricId); lyricInDatabase.Track.Should().NotBeNull(); lyricInDatabase.Track.Id.Should().Be(existingTrack.Id); @@ -95,8 +96,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_OneToOne_relationship_from_dependent_side() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); - var newTrackTitle = _fakers.MusicTrack.Generate().Title; + Lyric existingLyric = _fakers.Lyric.Generate(); + string newTrackTitle = _fakers.MusicTrack.Generate().Title; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -137,7 +138,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -148,13 +150,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty(); responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Lyric) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Lyric).FirstWithIdAsync(newTrackId); trackInDatabase.Lyric.Should().NotBeNull(); trackInDatabase.Lyric.Id.Should().Be(existingLyric.Id); @@ -167,8 +167,8 @@ public async Task Can_create_resources_with_ToOne_relationship() // Arrange const int elementCount = 5; - var existingCompany = _fakers.RecordCompany.Generate(); - var newTrackTitles = _fakers.MusicTrack.Generate(elementCount).Select(musicTrack => musicTrack.Title).ToArray(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + string[] newTrackTitles = _fakers.MusicTrack.Generate(elementCount).Select(musicTrack => musicTrack.Title).ToArray(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -177,6 +177,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => }); var operationElements = new List(elementCount); + for (int index = 0; index < elementCount; index++) { operationElements.Add(new @@ -212,7 +213,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -226,14 +228,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[index].SingleData.Attributes["title"].Should().Be(newTrackTitles[index]); } - var newTrackIds = responseDocument.Results.Select(result => Guid.Parse(result.SingleData.Id)); + IEnumerable newTrackIds = responseDocument.Results.Select(result => Guid.Parse(result.SingleData.Id)); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var tracksInDatabase = await dbContext.MusicTracks + List tracksInDatabase = await dbContext.MusicTracks .Include(musicTrack => musicTrack.OwnedBy) .Where(musicTrack => newTrackIds.Contains(musicTrack.Id)) .ToListAsync(); @@ -245,7 +247,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => for (int index = 0; index < elementCount; index++) { - var trackInDatabase = tracksInDatabase.Single(musicTrack => + MusicTrack trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == Guid.Parse(responseDocument.Results[index].SingleData.Id)); trackInDatabase.Title.Should().Be(newTrackTitles[index]); @@ -288,14 +290,14 @@ public async Task Cannot_create_for_missing_relationship_type() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'type' element."); error.Detail.Should().Be("Expected 'type' element in 'lyric' relationship."); @@ -335,14 +337,14 @@ public async Task Cannot_create_for_unknown_relationship_type() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -381,14 +383,14 @@ public async Task Cannot_create_for_missing_relationship_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'id' or 'lid' element."); error.Detail.Should().Be("Expected 'id' or 'lid' element in 'lyric' relationship."); @@ -428,14 +430,14 @@ public async Task Cannot_create_with_unknown_relationship_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("A related resource does not exist."); error.Detail.Should().Be("Related resource of type 'lyrics' with ID '12345678' in relationship 'lyric' does not exist."); @@ -475,14 +477,14 @@ public async Task Cannot_create_on_relationship_type_mismatch() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Relationship contains incompatible resource type."); error.Detail.Should().Be("Relationship 'lyric' contains incompatible resource type 'playlists'."); @@ -493,8 +495,8 @@ public async Task Cannot_create_on_relationship_type_mismatch() public async Task Can_create_resource_with_duplicate_relationship() { // Arrange - var existingCompany = _fakers.RecordCompany.Generate(); - var newTrackTitle = _fakers.MusicTrack.Generate().Title; + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + string newTrackTitle = _fakers.MusicTrack.Generate().Title; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -540,12 +542,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var requestBodyText = JsonConvert.SerializeObject(requestBody).Replace("ownedBy_duplicate", "ownedBy"); + string requestBodyText = JsonConvert.SerializeObject(requestBody).Replace("ownedBy_duplicate", "ownedBy"); const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBodyText); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBodyText); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -556,13 +559,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty(); responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(newTrackId); trackInDatabase.OwnedBy.Should().NotBeNull(); trackInDatabase.OwnedBy.Id.Should().Be(existingCompany.Id); @@ -605,14 +606,14 @@ public async Task Cannot_create_with_data_array_in_relationship() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected single data element for to-one relationship."); error.Detail.Should().Be("Expected single data element for 'lyric' relationship."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs index 14464a777b..ffb3342364 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -12,8 +13,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Deleting { - public sealed class AtomicDeleteResourceTests - : IClassFixture, OperationsDbContext>> + public sealed class AtomicDeleteResourceTests : IClassFixture, OperationsDbContext>> { private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); @@ -29,7 +29,7 @@ public AtomicDeleteResourceTests(ExampleIntegrationTestContext { @@ -56,7 +56,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -65,7 +65,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var performerInDatabase = await dbContext.Performers.FirstWithIdOrDefaultAsync(existingPerformer.Id); + Performer performerInDatabase = await dbContext.Performers.FirstWithIdOrDefaultAsync(existingPerformer.Id); performerInDatabase.Should().BeNull(); }); @@ -77,7 +77,7 @@ public async Task Can_delete_existing_resources() // Arrange const int elementCount = 5; - var existingTracks = _fakers.MusicTrack.Generate(elementCount); + List existingTracks = _fakers.MusicTrack.Generate(elementCount); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -87,6 +87,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => }); var operationElements = new List(elementCount); + for (int index = 0; index < elementCount; index++) { operationElements.Add(new @@ -108,7 +109,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -117,8 +118,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var tracksInDatabase = await dbContext.MusicTracks - .ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().BeEmpty(); }); @@ -128,7 +128,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_delete_resource_with_OneToOne_relationship_from_principal_side() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); existingLyric.Track = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -156,7 +156,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -165,11 +165,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var lyricsInDatabase = await dbContext.Lyrics.FirstWithIdOrDefaultAsync(existingLyric.Id); + Lyric lyricsInDatabase = await dbContext.Lyrics.FirstWithIdOrDefaultAsync(existingLyric.Id); lyricsInDatabase.Should().BeNull(); - var trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(existingLyric.Track.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(existingLyric.Track.Id); trackInDatabase.Lyric.Should().BeNull(); }); @@ -179,7 +179,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_delete_resource_with_OneToOne_relationship_from_dependent_side() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Lyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -207,7 +207,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -216,11 +216,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var tracksInDatabase = await dbContext.MusicTracks.FirstWithIdOrDefaultAsync(existingTrack.Id); + MusicTrack tracksInDatabase = await dbContext.MusicTracks.FirstWithIdOrDefaultAsync(existingTrack.Id); tracksInDatabase.Should().BeNull(); - var lyricInDatabase = await dbContext.Lyrics.FirstWithIdAsync(existingTrack.Lyric.Id); + Lyric lyricInDatabase = await dbContext.Lyrics.FirstWithIdAsync(existingTrack.Lyric.Id); lyricInDatabase.Track.Should().BeNull(); }); @@ -230,7 +230,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_delete_existing_resource_with_HasMany_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Performers = _fakers.Performer.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -258,7 +258,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -267,11 +267,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks.FirstWithIdOrDefaultAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.FirstWithIdOrDefaultAsync(existingTrack.Id); trackInDatabase.Should().BeNull(); - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().ContainSingle(userAccount => userAccount.Id == existingTrack.Performers.ElementAt(0).Id); performersInDatabase.Should().ContainSingle(userAccount => userAccount.Id == existingTrack.Performers.ElementAt(1).Id); @@ -313,7 +313,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -322,12 +322,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var playlistInDatabase = await dbContext.Playlists.FirstWithIdOrDefaultAsync(existingPlaylistMusicTrack.Playlist.Id); + Playlist playlistInDatabase = await dbContext.Playlists.FirstWithIdOrDefaultAsync(existingPlaylistMusicTrack.Playlist.Id); playlistInDatabase.Should().BeNull(); - var playlistTracksInDatabase = await dbContext.PlaylistMusicTracks - .FirstOrDefaultAsync(playlistMusicTrack => playlistMusicTrack.Playlist.Id == existingPlaylistMusicTrack.Playlist.Id); + PlaylistMusicTrack playlistTracksInDatabase = await dbContext.PlaylistMusicTracks.FirstOrDefaultAsync(playlistMusicTrack => + playlistMusicTrack.Playlist.Id == existingPlaylistMusicTrack.Playlist.Id); playlistTracksInDatabase.Should().BeNull(); }); @@ -352,14 +352,14 @@ public async Task Cannot_delete_resource_for_href_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Usage of the 'href' element is not supported."); error.Detail.Should().BeNull(); @@ -384,14 +384,14 @@ public async Task Cannot_delete_resource_for_missing_ref_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref' element is required."); error.Detail.Should().BeNull(); @@ -420,14 +420,14 @@ public async Task Cannot_delete_resource_for_missing_type() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.type' element is required."); error.Detail.Should().BeNull(); @@ -457,14 +457,14 @@ public async Task Cannot_delete_resource_for_unknown_type() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -493,14 +493,14 @@ public async Task Cannot_delete_resource_for_missing_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -530,14 +530,14 @@ public async Task Cannot_delete_resource_for_unknown_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("The requested resource does not exist."); error.Detail.Should().Be("Resource of type 'performers' with ID '99999999' does not exist."); @@ -548,7 +548,7 @@ public async Task Cannot_delete_resource_for_unknown_ID() public async Task Cannot_delete_resource_for_incompatible_ID() { // Arrange - var guid = Guid.NewGuid().ToString(); + string guid = Guid.NewGuid().ToString(); var requestBody = new { @@ -569,14 +569,14 @@ public async Task Cannot_delete_resource_for_incompatible_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().Be($"Failed to convert '{guid}' of type 'String' to type 'Int64'."); @@ -607,14 +607,14 @@ public async Task Cannot_delete_resource_for_ID_and_local_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicAbsoluteLinksTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicAbsoluteLinksTests.cs index f2f9ccdc25..15ebcab128 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicAbsoluteLinksTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicAbsoluteLinksTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Resources; @@ -10,8 +11,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Links { - public sealed class AtomicAbsoluteLinksTests - : IClassFixture, OperationsDbContext>> + public sealed class AtomicAbsoluteLinksTests : IClassFixture, OperationsDbContext>> { private const string HostPrefix = "http://localhost"; @@ -34,8 +34,8 @@ public AtomicAbsoluteLinksTests(ExampleIntegrationTestContext { @@ -77,7 +77,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -86,7 +87,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string languageLink = HostPrefix + "/textLanguages/" + existingLanguage.StringId; - var singleData1 = responseDocument.Results[0].SingleData; + ResourceObject singleData1 = responseDocument.Results[0].SingleData; singleData1.Should().NotBeNull(); singleData1.Links.Should().NotBeNull(); singleData1.Links.Self.Should().Be(languageLink); @@ -97,7 +98,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string companyLink = HostPrefix + "/recordCompanies/" + existingCompany.StringId; - var singleData2 = responseDocument.Results[1].SingleData; + ResourceObject singleData2 = responseDocument.Results[1].SingleData; singleData2.Should().NotBeNull(); singleData2.Links.Should().NotBeNull(); singleData2.Links.Self.Should().Be(companyLink); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicRelativeLinksWithNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicRelativeLinksWithNamespaceTests.cs index 1931b4725d..3fea795b67 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicRelativeLinksWithNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicRelativeLinksWithNamespaceTests.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Resources; @@ -16,7 +17,8 @@ public sealed class AtomicRelativeLinksWithNamespaceTests { private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; - public AtomicRelativeLinksWithNamespaceTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) + public AtomicRelativeLinksWithNamespaceTests( + ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; @@ -64,7 +66,8 @@ public async Task Create_resource_with_side_effects_returns_relative_links() const string route = "/api/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs index 197c8f51ed..af3c5ab285 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -10,8 +12,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.LocalIds { - public sealed class AtomicLocalIdTests - : IClassFixture, OperationsDbContext>> + public sealed class AtomicLocalIdTests : IClassFixture, OperationsDbContext>> { private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); @@ -27,8 +28,8 @@ public AtomicLocalIdTests(ExampleIntegrationTestContext(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -97,14 +99,12 @@ public async Task Can_create_resource_with_ToOne_relationship_using_local_ID() responseDocument.Results[1].SingleData.Lid.Should().BeNull(); responseDocument.Results[1].SingleData.Attributes["title"].Should().Be(newTrackTitle); - var newCompanyId = short.Parse(responseDocument.Results[0].SingleData.Id); - var newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); + short newCompanyId = short.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(newTrackId); trackInDatabase.Title.Should().Be(newTrackTitle); @@ -119,8 +119,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource_with_OneToMany_relationship_using_local_ID() { // Arrange - var newPerformer = _fakers.Performer.Generate(); - var newTrackTitle = _fakers.MusicTrack.Generate().Title; + Performer newPerformer = _fakers.Performer.Generate(); + string newTrackTitle = _fakers.MusicTrack.Generate().Title; const string performerLocalId = "performer-1"; @@ -174,7 +174,8 @@ public async Task Can_create_resource_with_OneToMany_relationship_using_local_ID const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -192,14 +193,12 @@ public async Task Can_create_resource_with_OneToMany_relationship_using_local_ID responseDocument.Results[1].SingleData.Lid.Should().BeNull(); responseDocument.Results[1].SingleData.Attributes["title"].Should().Be(newTrackTitle); - var newPerformerId = int.Parse(responseDocument.Results[0].SingleData.Id); - var newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); + int newPerformerId = int.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(newTrackId); trackInDatabase.Title.Should().Be(newTrackTitle); @@ -214,8 +213,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource_with_ManyToMany_relationship_using_local_ID() { // Arrange - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newPlaylistName = _fakers.Playlist.Generate().Name; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newPlaylistName = _fakers.Playlist.Generate().Name; const string trackLocalId = "track-1"; @@ -268,7 +267,8 @@ public async Task Can_create_resource_with_ManyToMany_relationship_using_local_I const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -285,15 +285,15 @@ public async Task Can_create_resource_with_ManyToMany_relationship_using_local_I responseDocument.Results[1].SingleData.Lid.Should().BeNull(); responseDocument.Results[1].SingleData.Attributes["name"].Should().Be(newPlaylistName); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); - var newPlaylistId = long.Parse(responseDocument.Results[1].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + long newPlaylistId = long.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(newPlaylistId); @@ -354,14 +354,14 @@ public async Task Cannot_consume_local_ID_that_is_assigned_in_same_operation() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Local ID cannot be both defined and used within the same operation."); error.Detail.Should().Be("Local ID 'company-1' cannot be both defined and used within the same operation."); @@ -372,7 +372,7 @@ public async Task Cannot_consume_local_ID_that_is_assigned_in_same_operation() public async Task Cannot_reassign_local_ID() { // Arrange - var newPlaylistName = _fakers.Playlist.Generate().Name; + string newPlaylistName = _fakers.Playlist.Generate().Name; const string playlistLocalId = "playlist-1"; var requestBody = new @@ -420,14 +420,14 @@ public async Task Cannot_reassign_local_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Another local ID with the same name is already defined at this point."); error.Detail.Should().Be("Another local ID with name 'playlist-1' is already defined at this point."); @@ -438,8 +438,8 @@ public async Task Cannot_reassign_local_ID() public async Task Can_update_resource_using_local_ID() { // Arrange - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newTrackGenre = _fakers.MusicTrack.Generate().Genre; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newTrackGenre = _fakers.MusicTrack.Generate().Genre; const string trackLocalId = "track-1"; @@ -479,7 +479,8 @@ public async Task Can_update_resource_using_local_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -494,11 +495,11 @@ public async Task Can_update_resource_using_local_ID() responseDocument.Results[1].Data.Should().BeNull(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(newTrackId); trackInDatabase.Title.Should().Be(newTrackTitle); trackInDatabase.Genre.Should().Be(newTrackGenre); @@ -509,9 +510,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_relationships_using_local_ID() { // Arrange - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newArtistName = _fakers.Performer.Generate().ArtistName; - var newCompanyName = _fakers.RecordCompany.Generate().Name; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newArtistName = _fakers.Performer.Generate().ArtistName; + string newCompanyName = _fakers.RecordCompany.Generate().Name; const string trackLocalId = "track-1"; const string performerLocalId = "performer-1"; @@ -597,7 +598,8 @@ public async Task Can_update_resource_with_relationships_using_local_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -621,16 +623,16 @@ public async Task Can_update_resource_with_relationships_using_local_ID() responseDocument.Results[3].Data.Should().BeNull(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); - var newPerformerId = int.Parse(responseDocument.Results[1].SingleData.Id); - var newCompanyId = short.Parse(responseDocument.Results[2].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + int newPerformerId = int.Parse(responseDocument.Results[1].SingleData.Id); + short newCompanyId = short.Parse(responseDocument.Results[2].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var trackInDatabase = await dbContext.MusicTracks + MusicTrack trackInDatabase = await dbContext.MusicTracks .Include(musicTrack => musicTrack.OwnedBy) .Include(musicTrack => musicTrack.Performers) .FirstWithIdAsync(newTrackId); @@ -653,8 +655,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_ToOne_relationship_using_local_ID() { // Arrange - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newCompanyName = _fakers.RecordCompany.Generate().Name; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newCompanyName = _fakers.RecordCompany.Generate().Name; const string trackLocalId = "track-1"; const string companyLocalId = "company-1"; @@ -710,7 +712,8 @@ public async Task Can_create_ToOne_relationship_using_local_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -729,14 +732,12 @@ public async Task Can_create_ToOne_relationship_using_local_ID() responseDocument.Results[2].Data.Should().BeNull(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); - var newCompanyId = short.Parse(responseDocument.Results[1].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + short newCompanyId = short.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(newTrackId); trackInDatabase.Title.Should().Be(newTrackTitle); @@ -750,8 +751,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_OneToMany_relationship_using_local_ID() { // Arrange - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newArtistName = _fakers.Performer.Generate().ArtistName; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newArtistName = _fakers.Performer.Generate().ArtistName; const string trackLocalId = "track-1"; const string performerLocalId = "performer-1"; @@ -810,7 +811,8 @@ public async Task Can_create_OneToMany_relationship_using_local_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -829,14 +831,12 @@ public async Task Can_create_OneToMany_relationship_using_local_ID() responseDocument.Results[2].Data.Should().BeNull(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); - var newPerformerId = int.Parse(responseDocument.Results[1].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + int newPerformerId = int.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(newTrackId); trackInDatabase.Title.Should().Be(newTrackTitle); @@ -850,8 +850,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_ManyToMany_relationship_using_local_ID() { // Arrange - var newPlaylistName = _fakers.Playlist.Generate().Name; - var newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newPlaylistName = _fakers.Playlist.Generate().Name; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; const string playlistLocalId = "playlist-1"; const string trackLocalId = "track-1"; @@ -910,7 +910,8 @@ public async Task Can_create_ManyToMany_relationship_using_local_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -929,15 +930,15 @@ public async Task Can_create_ManyToMany_relationship_using_local_ID() responseDocument.Results[2].Data.Should().BeNull(); - var newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); - var newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); + long newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(newPlaylistId); @@ -957,10 +958,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_OneToMany_relationship_using_local_ID() { // Arrange - var existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newArtistName = _fakers.Performer.Generate().ArtistName; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newArtistName = _fakers.Performer.Generate().ArtistName; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1039,7 +1040,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -1058,14 +1060,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[2].Data.Should().BeNull(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); - var newPerformerId = int.Parse(responseDocument.Results[1].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + int newPerformerId = int.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(newTrackId); trackInDatabase.Title.Should().Be(newTrackTitle); @@ -1079,10 +1079,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_ManyToMany_relationship_using_local_ID() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - var newPlaylistName = _fakers.Playlist.Generate().Name; - var newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newPlaylistName = _fakers.Playlist.Generate().Name; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1161,7 +1161,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -1180,15 +1181,15 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[2].Data.Should().BeNull(); - var newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); - var newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); + long newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(newPlaylistId); @@ -1208,10 +1209,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_add_to_OneToMany_relationship_using_local_ID() { // Arrange - var existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newArtistName = _fakers.Performer.Generate().ArtistName; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newArtistName = _fakers.Performer.Generate().ArtistName; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1290,7 +1291,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -1309,14 +1311,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[2].Data.Should().BeNull(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); - var newPerformerId = int.Parse(responseDocument.Results[1].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + int newPerformerId = int.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(newTrackId); trackInDatabase.Title.Should().Be(newTrackTitle); @@ -1334,10 +1334,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_add_to_ManyToMany_relationship_using_local_ID() { // Arrange - var existingTracks = _fakers.MusicTrack.Generate(2); + List existingTracks = _fakers.MusicTrack.Generate(2); - var newPlaylistName = _fakers.Playlist.Generate().Name; - var newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newPlaylistName = _fakers.Playlist.Generate().Name; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; const string playlistLocalId = "playlist-1"; const string trackLocalId = "track-1"; @@ -1434,7 +1434,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -1455,15 +1456,15 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[3].Data.Should().BeNull(); - var newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); - var newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); + long newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[1].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(newPlaylistId); @@ -1484,11 +1485,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_remove_from_OneToMany_relationship_using_local_ID() { // Arrange - var existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newArtistName1 = _fakers.Performer.Generate().ArtistName; - var newArtistName2 = _fakers.Performer.Generate().ArtistName; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newArtistName1 = _fakers.Performer.Generate().ArtistName; + string newArtistName2 = _fakers.Performer.Generate().ArtistName; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1596,7 +1597,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -1620,13 +1622,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Results[3].Data.Should().BeNull(); - var newTrackId = Guid.Parse(responseDocument.Results[2].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[2].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(newTrackId); trackInDatabase.Title.Should().Be(newTrackTitle); @@ -1640,7 +1640,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_remove_from_ManyToMany_relationship_using_local_ID() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); + existingPlaylist.PlaylistMusicTracks = new[] { new PlaylistMusicTrack @@ -1653,7 +1654,7 @@ public async Task Can_remove_from_ManyToMany_relationship_using_local_ID() } }; - var newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; const string trackLocalId = "track-1"; @@ -1740,7 +1741,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -1763,7 +1765,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(existingPlaylist.Id); @@ -1780,7 +1782,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_delete_resource_using_local_ID() { // Arrange - var newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; const string trackLocalId = "track-1"; @@ -1816,7 +1818,8 @@ public async Task Can_delete_resource_using_local_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -1830,11 +1833,11 @@ public async Task Can_delete_resource_using_local_ID() responseDocument.Results[1].Data.Should().BeNull(); - var newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); + Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks.FirstWithIdOrDefaultAsync(newTrackId); + MusicTrack trackInDatabase = await dbContext.MusicTracks.FirstWithIdOrDefaultAsync(newTrackId); trackInDatabase.Should().BeNull(); }); @@ -1872,14 +1875,14 @@ public async Task Cannot_consume_unassigned_local_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Server-generated value for local ID is not available at this point."); error.Detail.Should().Be("Server-generated value for local ID 'doesNotExist' is not available at this point."); @@ -1921,14 +1924,14 @@ public async Task Cannot_consume_unassigned_local_ID_in_data_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Server-generated value for local ID is not available at this point."); error.Detail.Should().Be("Server-generated value for local ID 'doesNotExist' is not available at this point."); @@ -1939,7 +1942,7 @@ public async Task Cannot_consume_unassigned_local_ID_in_data_element() public async Task Cannot_consume_unassigned_local_ID_in_data_array() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1984,14 +1987,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Server-generated value for local ID is not available at this point."); error.Detail.Should().Be("Server-generated value for local ID 'doesNotExist' is not available at this point."); @@ -2040,14 +2043,14 @@ public async Task Cannot_consume_unassigned_local_ID_in_relationship_data_elemen const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Server-generated value for local ID is not available at this point."); error.Detail.Should().Be("Server-generated value for local ID 'doesNotExist' is not available at this point."); @@ -2099,14 +2102,14 @@ public async Task Cannot_consume_unassigned_local_ID_in_relationship_data_array( const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Server-generated value for local ID is not available at this point."); error.Detail.Should().Be("Server-generated value for local ID 'doesNotExist' is not available at this point."); @@ -2158,14 +2161,14 @@ public async Task Cannot_consume_local_ID_of_different_type_in_same_operation() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Type mismatch in local ID usage."); error.Detail.Should().Be("Local ID 'track-1' belongs to resource type 'musicTracks' instead of 'recordCompanies'."); @@ -2215,14 +2218,14 @@ public async Task Cannot_consume_local_ID_of_different_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Type mismatch in local ID usage."); error.Detail.Should().Be("Local ID 'company-1' belongs to resource type 'recordCompanies' instead of 'musicTracks'."); @@ -2275,14 +2278,14 @@ public async Task Cannot_consume_local_ID_of_different_type_in_data_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Type mismatch in local ID usage."); error.Detail.Should().Be("Local ID 'performer-1' belongs to resource type 'performers' instead of 'playlists'."); @@ -2293,7 +2296,7 @@ public async Task Cannot_consume_local_ID_of_different_type_in_data_element() public async Task Cannot_consume_local_ID_of_different_type_in_data_array() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); const string companyLocalId = "company-1"; @@ -2349,14 +2352,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Type mismatch in local ID usage."); error.Detail.Should().Be("Local ID 'company-1' belongs to resource type 'recordCompanies' instead of 'performers'."); @@ -2367,7 +2370,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_consume_local_ID_of_different_type_in_relationship_data_element() { // Arrange - var newPlaylistName = _fakers.Playlist.Generate().Name; + string newPlaylistName = _fakers.Playlist.Generate().Name; const string playlistLocalId = "playlist-1"; @@ -2422,14 +2425,14 @@ public async Task Cannot_consume_local_ID_of_different_type_in_relationship_data const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Type mismatch in local ID usage."); error.Detail.Should().Be("Local ID 'playlist-1' belongs to resource type 'playlists' instead of 'recordCompanies'."); @@ -2492,14 +2495,14 @@ public async Task Cannot_consume_local_ID_of_different_type_in_relationship_data const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Type mismatch in local ID usage."); error.Detail.Should().Be("Local ID 'performer-1' belongs to resource type 'performers' instead of 'musicTracks'."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Lyric.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Lyric.cs index 31e9e5106a..c3c155fde7 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Lyric.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Lyric.cs @@ -19,7 +19,7 @@ public sealed class Lyric : Identifiable [Attr(Capabilities = AttrCapabilities.None)] public DateTimeOffset CreatedAt { get; set; } - + [HasOne] public MusicTrack Track { get; set; } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs index ecbfb911e2..a0df52450c 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Extensions; @@ -12,8 +13,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Meta { - public sealed class AtomicResourceMetaTests - : IClassFixture, OperationsDbContext>> + public sealed class AtomicResourceMetaTests : IClassFixture, OperationsDbContext>> { private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); @@ -35,8 +35,8 @@ public AtomicResourceMetaTests(ExampleIntegrationTestContext(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -92,7 +93,7 @@ public async Task Returns_resource_meta_in_create_resource_with_side_effects() public async Task Returns_top_level_meta_in_update_resource_with_side_effects() { // Arrange - var existingLanguage = _fakers.TextLanguage.Generate(); + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -122,7 +123,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMeta.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMeta.cs index 65f95ff844..09a59d5507 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMeta.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMeta.cs @@ -21,4 +21,4 @@ public IReadOnlyDictionary GetMeta() }; } } -} \ No newline at end of file +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMetaTests.cs index bb6d147bcc..ae24a50a4a 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMetaTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMetaTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization; @@ -13,8 +14,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Meta { - public sealed class AtomicResponseMetaTests - : IClassFixture, OperationsDbContext>> + public sealed class AtomicResponseMetaTests : IClassFixture, OperationsDbContext>> { private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); @@ -56,7 +56,8 @@ public async Task Returns_top_level_meta_in_create_resource_with_side_effects() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -65,7 +66,7 @@ public async Task Returns_top_level_meta_in_create_resource_with_side_effects() responseDocument.Meta["license"].Should().Be("MIT"); responseDocument.Meta["projectUrl"].Should().Be("https://github.com/json-api-dotnet/JsonApiDotNetCore/"); - var versionArray = ((IEnumerable) responseDocument.Meta["versions"]).Select(token => token.ToString()).ToArray(); + string[] versionArray = ((IEnumerable)responseDocument.Meta["versions"]).Select(token => token.ToString()).ToArray(); versionArray.Should().HaveCount(4); versionArray.Should().Contain("v4.0.0"); @@ -78,7 +79,7 @@ public async Task Returns_top_level_meta_in_create_resource_with_side_effects() public async Task Returns_top_level_meta_in_update_resource_with_side_effects() { // Arrange - var existingLanguage = _fakers.TextLanguage.Generate(); + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -108,7 +109,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -117,7 +119,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Meta["license"].Should().Be("MIT"); responseDocument.Meta["projectUrl"].Should().Be("https://github.com/json-api-dotnet/JsonApiDotNetCore/"); - var versionArray = ((IEnumerable) responseDocument.Meta["versions"]).Select(token => token.ToString()).ToArray(); + string[] versionArray = ((IEnumerable)responseDocument.Meta["versions"]).Select(token => token.ToString()).ToArray(); versionArray.Should().HaveCount(4); versionArray.Should().Contain("v4.0.0"); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs index 27e13c0b7e..8c9546742e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs @@ -9,7 +9,8 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Meta [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class MusicTrackMetaDefinition : JsonApiResourceDefinition { - public MusicTrackMetaDefinition(IResourceGraph resourceGraph) : base(resourceGraph) + public MusicTrackMetaDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs index ace9c8828f..f59c5d3991 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs @@ -11,7 +11,8 @@ public sealed class TextLanguageMetaDefinition : JsonApiResourceDefinition, OperationsDbContext>> + public sealed class AtomicRequestBodyTests : IClassFixture, OperationsDbContext>> { private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; @@ -28,14 +29,14 @@ public async Task Cannot_process_for_missing_request_body() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, null); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, null); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Missing request body."); error.Detail.Should().BeNull(); @@ -43,10 +44,10 @@ public async Task Cannot_process_for_missing_request_body() await _testContext.RunOnDatabaseAsync(async dbContext => { - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().BeEmpty(); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().BeEmpty(); }); } @@ -60,14 +61,14 @@ public async Task Cannot_process_for_broken_JSON_request_body() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().StartWith("Unexpected end of content while loading JObject."); @@ -86,14 +87,14 @@ public async Task Cannot_process_empty_operations_array() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: No operations found."); error.Detail.Should().BeNull(); @@ -101,10 +102,10 @@ public async Task Cannot_process_empty_operations_array() await _testContext.RunOnDatabaseAsync(async dbContext => { - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().BeEmpty(); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().BeEmpty(); }); } @@ -134,14 +135,14 @@ public async Task Cannot_process_for_unknown_operation_code() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().StartWith("Error converting value \"merge\" to type"); @@ -149,10 +150,10 @@ public async Task Cannot_process_for_unknown_operation_code() await _testContext.RunOnDatabaseAsync(async dbContext => { - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().BeEmpty(); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().BeEmpty(); }); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs index 2d497077c4..96af33f8cd 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -27,7 +28,7 @@ public MaximumOperationsPerRequestTests(ExampleIntegrationTestContext(); + var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService(); options.MaximumOperationsPerRequest = 2; var requestBody = new @@ -61,14 +62,14 @@ public async Task Cannot_process_more_operations_than_maximum() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request exceeds the maximum number of operations."); error.Detail.Should().Be("The number of operations in this request (3) is higher than 2."); @@ -79,7 +80,7 @@ public async Task Cannot_process_more_operations_than_maximum() public async Task Can_process_operations_same_as_maximum() { // Arrange - var options = (JsonApiOptions) _testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService(); options.MaximumOperationsPerRequest = 2; var requestBody = new @@ -114,7 +115,7 @@ public async Task Can_process_operations_same_as_maximum() const string route = "/operations"; // Act - var (httpResponse, _) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument _) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -124,12 +125,13 @@ public async Task Can_process_operations_same_as_maximum() public async Task Can_process_high_number_of_operations_when_unconstrained() { // Arrange - var options = (JsonApiOptions) _testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService(); options.MaximumOperationsPerRequest = null; const int elementCount = 100; var operationElements = new List(elementCount); + for (int index = 0; index < elementCount; index++) { operationElements.Add(new @@ -153,7 +155,7 @@ public async Task Can_process_high_number_of_operations_when_unconstrained() const string route = "/operations"; // Act - var (httpResponse, _) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument _) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ModelStateValidation/AtomicModelStateValidationTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ModelStateValidation/AtomicModelStateValidationTests.cs index 5f6e19fc92..24d3f851dc 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ModelStateValidation/AtomicModelStateValidationTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ModelStateValidation/AtomicModelStateValidationTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -48,20 +49,20 @@ public async Task Cannot_create_resource_with_multiple_violations() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(2); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error1.Title.Should().Be("Input validation failed."); error1.Detail.Should().Be("The Title field is required."); error1.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/title"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error2.Title.Should().Be("Input validation failed."); error2.Detail.Should().Be("The field LengthInSeconds must be between 1 and 1440."); @@ -72,8 +73,8 @@ public async Task Cannot_create_resource_with_multiple_violations() public async Task Can_create_resource_with_annotated_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); - var newPlaylistName = _fakers.Playlist.Generate().Name; + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + string newPlaylistName = _fakers.Playlist.Generate().Name; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -117,21 +118,22 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); responseDocument.Results.Should().HaveCount(1); - var newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); + long newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(newPlaylistId); @@ -148,7 +150,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_update_resource_with_multiple_violations() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -169,7 +171,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => id = existingTrack.StringId, attributes = new { - title = (string) null, + title = (string)null, lengthInSeconds = -1 } } @@ -180,20 +182,20 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(2); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error1.Title.Should().Be("Input validation failed."); error1.Detail.Should().Be("The Title field is required."); error1.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/title"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error2.Title.Should().Be("Input validation failed."); error2.Detail.Should().Be("The field LengthInSeconds must be between 1 and 1440."); @@ -204,8 +206,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_omitted_required_attribute() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); - var newTrackGenre = _fakers.MusicTrack.Generate().Genre; + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + string newTrackGenre = _fakers.MusicTrack.Generate().Genre; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -236,7 +238,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -245,7 +247,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(existingTrack.Id); trackInDatabase.Title.Should().Be(existingTrack.Title); trackInDatabase.Genre.Should().Be(newTrackGenre); @@ -256,8 +258,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_annotated_relationship() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); - var existingTrack = _fakers.MusicTrack.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -298,7 +300,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -310,7 +312,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(existingPlaylist.Id); @@ -327,8 +329,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_ToOne_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); - var existingCompany = _fakers.RecordCompany.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -361,7 +363,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -370,9 +372,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.OwnedBy.Should().NotBeNull(); trackInDatabase.OwnedBy.Id.Should().Be(existingCompany.Id); @@ -383,8 +383,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_ToMany_relationship() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); - var existingTrack = _fakers.MusicTrack.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -420,7 +420,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -432,7 +432,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(existingPlaylist.Id); @@ -462,7 +462,7 @@ public async Task Validates_all_operations_before_execution_starts() id = 99999999, attributes = new { - name = (string) null + name = (string)null } } }, @@ -484,26 +484,26 @@ public async Task Validates_all_operations_before_execution_starts() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(3); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error1.Title.Should().Be("Input validation failed."); error1.Detail.Should().Be("The Name field is required."); error1.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/name"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error2.Title.Should().Be("Input validation failed."); error2.Detail.Should().Be("The Title field is required."); error2.Source.Pointer.Should().Be("/atomic:operations[1]/data/attributes/title"); - var error3 = responseDocument.Errors[2]; + Error error3 = responseDocument.Errors[2]; error3.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error3.Title.Should().Be("Input validation failed."); error3.Detail.Should().Be("The field LengthInSeconds must be between 1 and 1440."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs index 991d999f1d..94733028d9 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs @@ -28,8 +28,8 @@ public sealed class MusicTrack : Identifiable public DateTimeOffset ReleasedAt { get; set; } [HasOne] - public Lyric Lyric { get; set;} - + public Lyric Lyric { get; set; } + [HasOne] public RecordCompany OwnedBy { get; set; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs index 9cdbe029ac..ed2ffd6044 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs @@ -8,8 +8,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations { public sealed class MusicTracksController : JsonApiController { - public MusicTracksController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public MusicTracksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs index 839c6cd7b8..c7ab6d943c 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs @@ -24,7 +24,11 @@ public OperationsDbContext(DbContextOptions options) protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() - .HasKey(playlistMusicTrack => new {playlistMusicTrack.PlaylistId, playlistMusicTrack.MusicTrackId}); + .HasKey(playlistMusicTrack => new + { + playlistMusicTrack.PlaylistId, + playlistMusicTrack.MusicTrackId + }); builder.Entity() .HasOne(musicTrack => musicTrack.Lyric) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/AtomicQueryStringTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/AtomicQueryStringTests.cs index 4b71f7215c..2e41befd2e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/AtomicQueryStringTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/AtomicQueryStringTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Extensions; @@ -14,8 +16,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.QueryStrings { - public sealed class AtomicQueryStringTests - : IClassFixture, OperationsDbContext>> + public sealed class AtomicQueryStringTests : IClassFixture, OperationsDbContext>> { private static readonly DateTime FrozenTime = 30.July(2018).At(13, 46, 12); @@ -30,11 +31,15 @@ public AtomicQueryStringTests(ExampleIntegrationTestContext(new FrozenSystemClock {UtcNow = FrozenTime}); + services.AddSingleton(new FrozenSystemClock + { + UtcNow = FrozenTime + }); + services.AddScoped, MusicTrackReleaseDefinition>(); }); - var options = (JsonApiOptions) testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.AllowQueryStringOverrideForSerializerDefaultValueHandling = true; options.AllowQueryStringOverrideForSerializerNullValueHandling = true; } @@ -64,14 +69,14 @@ public async Task Cannot_include_on_operations_endpoint() const string route = "/operations?include=recordCompanies"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Usage of one or more query string parameters is not allowed at the requested endpoint."); error.Detail.Should().Be("The parameter 'include' cannot be used at this endpoint."); @@ -103,14 +108,14 @@ public async Task Cannot_filter_on_operations_endpoint() const string route = "/operations?filter=equals(id,'1')"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Usage of one or more query string parameters is not allowed at the requested endpoint."); error.Detail.Should().Be("The parameter 'filter' cannot be used at this endpoint."); @@ -142,14 +147,14 @@ public async Task Cannot_sort_on_operations_endpoint() const string route = "/operations?sort=-id"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Usage of one or more query string parameters is not allowed at the requested endpoint."); error.Detail.Should().Be("The parameter 'sort' cannot be used at this endpoint."); @@ -181,14 +186,14 @@ public async Task Cannot_use_pagination_number_on_operations_endpoint() const string route = "/operations?page[number]=1"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Usage of one or more query string parameters is not allowed at the requested endpoint."); error.Detail.Should().Be("The parameter 'page[number]' cannot be used at this endpoint."); @@ -220,14 +225,14 @@ public async Task Cannot_use_pagination_size_on_operations_endpoint() const string route = "/operations?page[size]=1"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Usage of one or more query string parameters is not allowed at the requested endpoint."); error.Detail.Should().Be("The parameter 'page[size]' cannot be used at this endpoint."); @@ -259,14 +264,14 @@ public async Task Cannot_use_sparse_fieldset_on_operations_endpoint() const string route = "/operations?fields[recordCompanies]=id"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Usage of one or more query string parameters is not allowed at the requested endpoint."); error.Detail.Should().Be("The parameter 'fields[recordCompanies]' cannot be used at this endpoint."); @@ -277,7 +282,7 @@ public async Task Cannot_use_sparse_fieldset_on_operations_endpoint() public async Task Can_use_Queryable_handler_on_resource_endpoint() { // Arrange - var musicTracks = _fakers.MusicTrack.Generate(3); + List musicTracks = _fakers.MusicTrack.Generate(3); musicTracks[0].ReleasedAt = FrozenTime.AddMonths(5); musicTracks[1].ReleasedAt = FrozenTime.AddMonths(-5); musicTracks[2].ReleasedAt = FrozenTime.AddMonths(-1); @@ -292,7 +297,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/musicTracks?isRecentlyReleased=true"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -305,7 +310,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_use_Queryable_handler_on_operations_endpoint() { // Arrange - var newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; var requestBody = new { @@ -329,18 +334,20 @@ public async Task Cannot_use_Queryable_handler_on_operations_endpoint() const string route = "/operations?isRecentlyReleased=true"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Unknown query string parameter."); + error.Detail.Should().Be("Query string parameter 'isRecentlyReleased' is unknown. " + "Set 'AllowUnknownQueryStringParameters' to 'true' in options to ignore unknown parameters."); + error.Source.Parameter.Should().Be("isRecentlyReleased"); } @@ -348,8 +355,8 @@ public async Task Cannot_use_Queryable_handler_on_operations_endpoint() public async Task Can_use_defaults_on_operations_endpoint() { // Arrange - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newTrackLength = _fakers.MusicTrack.Generate().LengthInSeconds; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + decimal? newTrackLength = _fakers.MusicTrack.Generate().LengthInSeconds; var requestBody = new { @@ -374,7 +381,8 @@ public async Task Can_use_defaults_on_operations_endpoint() const string route = "/operations?defaults=false"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -391,8 +399,8 @@ public async Task Can_use_defaults_on_operations_endpoint() public async Task Can_use_nulls_on_operations_endpoint() { // Arrange - var newTrackTitle = _fakers.MusicTrack.Generate().Title; - var newTrackLength = _fakers.MusicTrack.Generate().LengthInSeconds; + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + decimal? newTrackLength = _fakers.MusicTrack.Generate().LengthInSeconds; var requestBody = new { @@ -417,7 +425,8 @@ public async Task Can_use_nulls_on_operations_endpoint() const string route = "/operations?nulls=false"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs index d3869ba33d..857f68ef25 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs @@ -32,13 +32,11 @@ public override QueryStringParameterHandlers OnRegisterQueryableHand private IQueryable FilterOnRecentlyReleased(IQueryable source, StringValues parameterValue) { - var tracks = source; + IQueryable tracks = source; if (bool.Parse(parameterValue)) { - tracks = tracks.Where(musicTrack => - musicTrack.ReleasedAt < _systemClock.UtcNow && - musicTrack.ReleasedAt > _systemClock.UtcNow.AddMonths(-3)); + tracks = tracks.Where(musicTrack => musicTrack.ReleasedAt < _systemClock.UtcNow && musicTrack.ReleasedAt > _systemClock.UtcNow.AddMonths(-3)); } return tracks; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/AtomicSparseFieldSetResourceDefinitionTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/AtomicSparseFieldSetResourceDefinitionTests.cs index 01ce190904..de4c1ecaaf 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/AtomicSparseFieldSetResourceDefinitionTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/AtomicSparseFieldSetResourceDefinitionTests.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Resources; @@ -38,7 +40,7 @@ public async Task Hides_text_in_create_resource_with_side_effects() provider.CanViewText = false; provider.HitCount = 0; - var newLyrics = _fakers.Lyric.Generate(2); + List newLyrics = _fakers.Lyric.Generate(2); var requestBody = new { @@ -76,7 +78,8 @@ public async Task Hides_text_in_create_resource_with_side_effects() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -100,7 +103,7 @@ public async Task Hides_text_in_update_resource_with_side_effects() provider.CanViewText = false; provider.HitCount = 0; - var existingLyrics = _fakers.Lyric.Generate(2); + List existingLyrics = _fakers.Lyric.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -142,7 +145,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs index ad624f6189..e3ff40fb65 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs @@ -1,4 +1,7 @@ +using System; +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -9,8 +12,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Transactions { - public sealed class AtomicRollbackTests - : IClassFixture, OperationsDbContext>> + public sealed class AtomicRollbackTests : IClassFixture, OperationsDbContext>> { private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); @@ -26,9 +28,9 @@ public AtomicRollbackTests(ExampleIntegrationTestContext { @@ -84,14 +86,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("A related resource does not exist."); error.Detail.Should().Be("Related resource of type 'performers' with ID '99999999' in relationship 'performers' does not exist."); @@ -99,10 +101,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().BeEmpty(); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().BeEmpty(); }); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs index 4b18f53178..899ee2510d 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -61,14 +62,14 @@ public async Task Cannot_use_non_transactional_repository() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Unsupported resource type in atomic:operations request."); error.Detail.Should().Be("Operations on resources of type 'performers' cannot be used because transaction support is unavailable."); @@ -100,14 +101,14 @@ public async Task Cannot_use_transactional_repository_without_active_transaction const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Unsupported combination of resource types in atomic:operations request."); error.Detail.Should().Be("All operations need to participate in a single shared transaction, which is not the case for this request."); @@ -139,14 +140,14 @@ public async Task Cannot_use_distributed_transaction() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Unsupported combination of resource types in atomic:operations request."); error.Detail.Should().Be("All operations need to participate in a single shared transaction, which is not the case for this request."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs index 4417ee9d3b..ef08acc7fe 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs @@ -16,9 +16,8 @@ public sealed class LyricRepository : EntityFrameworkCoreRepository public override Guid? TransactionId => _extraDbContext.Database.CurrentTransaction.TransactionId; - public LyricRepository(ExtraDbContext extraDbContext, ITargetedFields targetedFields, - IDbContextResolver contextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, - IEnumerable constraintProviders, ILoggerFactory loggerFactory) + public LyricRepository(ExtraDbContext extraDbContext, ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) { _extraDbContext = extraDbContext; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs index 554f6b6d3e..612333855e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs @@ -14,9 +14,8 @@ public sealed class MusicTrackRepository : EntityFrameworkCoreRepository null; - public MusicTrackRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, - IResourceGraph resourceGraph, IResourceFactory resourceFactory, - IEnumerable constraintProviders, ILoggerFactory loggerFactory) + public MusicTrackRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs index f22a3d8846..555c9ba955 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs @@ -58,8 +58,7 @@ public Task AddToToManyRelationshipAsync(int primaryId, ISet seco throw new NotImplementedException(); } - public Task RemoveFromToManyRelationshipAsync(Performer primaryResource, ISet secondaryResourceIds, - CancellationToken cancellationToken) + public Task RemoveFromToManyRelationshipAsync(Performer primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs index 3a0f791045..87c407cd41 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore; @@ -29,8 +30,8 @@ public AtomicAddToToManyRelationshipTests(ExampleIntegrationTestContext { @@ -63,14 +64,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Only to-many relationships can be targeted in 'add' operations."); error.Detail.Should().Be("Relationship 'ownedBy' must be a to-many relationship."); @@ -80,10 +81,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_add_to_HasMany_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Performers = _fakers.Performer.Generate(1); - var existingPerformers = _fakers.Performer.Generate(2); + List existingPerformers = _fakers.Performer.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -138,7 +139,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -147,9 +148,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Performers.Should().HaveCount(3); trackInDatabase.Performers.Should().ContainSingle(performer => performer.Id == existingTrack.Performers[0].Id); @@ -162,7 +161,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_add_to_HasManyThrough_relationship() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); + existingPlaylist.PlaylistMusicTracks = new List { new PlaylistMusicTrack @@ -171,7 +171,7 @@ public async Task Can_add_to_HasManyThrough_relationship() } }; - var existingTracks = _fakers.MusicTrack.Generate(2); + List existingTracks = _fakers.MusicTrack.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -226,7 +226,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -238,7 +238,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(existingPlaylist.Id); @@ -274,14 +274,14 @@ public async Task Cannot_add_for_href_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Usage of the 'href' element is not supported."); error.Detail.Should().BeNull(); @@ -311,14 +311,14 @@ public async Task Cannot_add_for_missing_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.type' element is required."); error.Detail.Should().BeNull(); @@ -349,14 +349,14 @@ public async Task Cannot_add_for_unknown_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -386,14 +386,14 @@ public async Task Cannot_add_for_missing_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -404,7 +404,7 @@ public async Task Cannot_add_for_missing_ID_in_ref() public async Task Cannot_add_for_unknown_ID_in_ref() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -440,14 +440,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("The requested resource does not exist."); error.Detail.Should().Be("Resource of type 'recordCompanies' with ID '9999' does not exist."); @@ -479,14 +479,14 @@ public async Task Cannot_add_for_ID_and_local_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -516,14 +516,14 @@ public async Task Cannot_add_for_missing_relationship_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.relationship' element is required."); error.Detail.Should().BeNull(); @@ -554,14 +554,14 @@ public async Task Cannot_add_for_unknown_relationship_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The referenced relationship does not exist."); error.Detail.Should().Be("Resource of type 'performers' does not contain a relationship named 'doesNotExist'."); @@ -572,7 +572,7 @@ public async Task Cannot_add_for_unknown_relationship_in_ref() public async Task Cannot_add_for_null_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -593,7 +593,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => id = existingTrack.StringId, relationship = "performers" }, - data = (object) null + data = (object)null } } }; @@ -601,14 +601,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected data[] element for to-many relationship."); error.Detail.Should().Be("Expected data[] element for 'performers' relationship."); @@ -646,14 +646,14 @@ public async Task Cannot_add_for_missing_type_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data[].type' element is required."); error.Detail.Should().BeNull(); @@ -692,14 +692,14 @@ public async Task Cannot_add_for_unknown_type_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -737,14 +737,14 @@ public async Task Cannot_add_for_missing_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data[].id' or 'data[].lid' element is required."); error.Detail.Should().BeNull(); @@ -784,14 +784,14 @@ public async Task Cannot_add_for_ID_and_local_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data[].id' or 'data[].lid' element is required."); error.Detail.Should().BeNull(); @@ -802,8 +802,8 @@ public async Task Cannot_add_for_ID_and_local_ID_in_data() public async Task Cannot_add_for_unknown_IDs_in_data() { // Arrange - var existingCompany = _fakers.RecordCompany.Generate(); - var trackIds = ArrayFactory.Create(Guid.NewGuid(), Guid.NewGuid()); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + Guid[] trackIds = ArrayFactory.Create(Guid.NewGuid(), Guid.NewGuid()); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -844,20 +844,20 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(2); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.NotFound); error1.Title.Should().Be("A related resource does not exist."); error1.Detail.Should().Be($"Related resource of type 'musicTracks' with ID '{trackIds[0]}' in relationship 'tracks' does not exist."); error1.Source.Pointer.Should().Be("/atomic:operations[0]"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.NotFound); error2.Title.Should().Be("A related resource does not exist."); error2.Detail.Should().Be($"Related resource of type 'musicTracks' with ID '{trackIds[1]}' in relationship 'tracks' does not exist."); @@ -868,7 +868,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_add_for_relationship_mismatch_between_ref_and_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -904,14 +904,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource type mismatch between 'ref.relationship' and 'data[].type' element."); error.Detail.Should().Be("Expected resource of type 'performers' in 'data[].type', instead of 'playlists'."); @@ -922,7 +922,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_add_with_empty_data_array() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Performers = _fakers.Performer.Generate(1); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -952,7 +952,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -961,9 +961,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Performers.Should().HaveCount(1); trackInDatabase.Performers[0].Id.Should().Be(existingTrack.Performers[0].Id); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs index 50de26773d..4ba1b44634 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore; @@ -29,7 +30,7 @@ public AtomicRemoveFromToManyRelationshipTests(ExampleIntegrationTestContext @@ -63,14 +64,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Only to-many relationships can be targeted in 'remove' operations."); error.Detail.Should().Be("Relationship 'ownedBy' must be a to-many relationship."); @@ -80,7 +81,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_remove_from_HasMany_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Performers = _fakers.Performer.Generate(3); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -136,7 +137,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -145,14 +146,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Performers.Should().HaveCount(1); trackInDatabase.Performers[0].Id.Should().Be(existingTrack.Performers[1].Id); - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().HaveCount(3); }); } @@ -161,7 +160,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_remove_from_HasManyThrough_relationship() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); + existingPlaylist.PlaylistMusicTracks = new List { new PlaylistMusicTrack @@ -231,7 +231,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -243,7 +243,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(existingPlaylist.Id); @@ -254,7 +254,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => playlistInDatabase.PlaylistMusicTracks.Should().HaveCount(1); playlistInDatabase.PlaylistMusicTracks[0].MusicTrack.Id.Should().Be(existingPlaylist.PlaylistMusicTracks[1].MusicTrack.Id); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(3); }); } @@ -278,14 +278,14 @@ public async Task Cannot_remove_for_href_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Usage of the 'href' element is not supported."); error.Detail.Should().BeNull(); @@ -315,14 +315,14 @@ public async Task Cannot_remove_for_missing_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.type' element is required."); error.Detail.Should().BeNull(); @@ -353,14 +353,14 @@ public async Task Cannot_remove_for_unknown_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -390,14 +390,14 @@ public async Task Cannot_remove_for_missing_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -408,7 +408,7 @@ public async Task Cannot_remove_for_missing_ID_in_ref() public async Task Cannot_remove_for_unknown_ID_in_ref() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -444,14 +444,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("The requested resource does not exist."); error.Detail.Should().Be("Resource of type 'recordCompanies' with ID '9999' does not exist."); @@ -483,14 +483,14 @@ public async Task Cannot_remove_for_ID_and_local_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -521,14 +521,14 @@ public async Task Cannot_remove_for_unknown_relationship_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The referenced relationship does not exist."); error.Detail.Should().Be("Resource of type 'performers' does not contain a relationship named 'doesNotExist'."); @@ -539,7 +539,7 @@ public async Task Cannot_remove_for_unknown_relationship_in_ref() public async Task Cannot_remove_for_null_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -560,7 +560,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => id = existingTrack.StringId, relationship = "performers" }, - data = (object) null + data = (object)null } } }; @@ -568,14 +568,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected data[] element for to-many relationship."); error.Detail.Should().Be("Expected data[] element for 'performers' relationship."); @@ -613,14 +613,14 @@ public async Task Cannot_remove_for_missing_type_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data[].type' element is required."); error.Detail.Should().BeNull(); @@ -659,14 +659,14 @@ public async Task Cannot_remove_for_unknown_type_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -704,14 +704,14 @@ public async Task Cannot_remove_for_missing_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data[].id' or 'data[].lid' element is required."); error.Detail.Should().BeNull(); @@ -751,14 +751,14 @@ public async Task Cannot_remove_for_ID_and_local_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data[].id' or 'data[].lid' element is required."); error.Detail.Should().BeNull(); @@ -769,8 +769,8 @@ public async Task Cannot_remove_for_ID_and_local_ID_in_data() public async Task Cannot_remove_for_unknown_IDs_in_data() { // Arrange - var existingCompany = _fakers.RecordCompany.Generate(); - var trackIds = ArrayFactory.Create(Guid.NewGuid(), Guid.NewGuid()); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + Guid[] trackIds = ArrayFactory.Create(Guid.NewGuid(), Guid.NewGuid()); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -811,20 +811,20 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(2); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.NotFound); error1.Title.Should().Be("A related resource does not exist."); error1.Detail.Should().Be($"Related resource of type 'musicTracks' with ID '{trackIds[0]}' in relationship 'tracks' does not exist."); error1.Source.Pointer.Should().Be("/atomic:operations[0]"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.NotFound); error2.Title.Should().Be("A related resource does not exist."); error2.Detail.Should().Be($"Related resource of type 'musicTracks' with ID '{trackIds[1]}' in relationship 'tracks' does not exist."); @@ -835,7 +835,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_remove_for_relationship_mismatch_between_ref_and_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -871,14 +871,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource type mismatch between 'ref.relationship' and 'data[].type' element."); error.Detail.Should().Be("Expected resource of type 'performers' in 'data[].type', instead of 'playlists'."); @@ -889,7 +889,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_remove_with_empty_data_array() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Performers = _fakers.Performer.Generate(1); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -920,7 +920,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -929,9 +929,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Performers.Should().HaveCount(1); trackInDatabase.Performers[0].Id.Should().Be(existingTrack.Performers[0].Id); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs index 108a070664..9eca84249c 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore; @@ -29,7 +30,7 @@ public AtomicReplaceToManyRelationshipTests(ExampleIntegrationTestContext @@ -60,7 +61,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -69,13 +70,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Performers.Should().BeEmpty(); - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().HaveCount(2); }); } @@ -84,7 +83,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_clear_HasManyThrough_relationship() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); + existingPlaylist.PlaylistMusicTracks = new List { new PlaylistMusicTrack @@ -125,7 +125,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -137,7 +137,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(existingPlaylist.Id); @@ -147,7 +147,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => playlistInDatabase.PlaylistMusicTracks.Should().BeEmpty(); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(2); }); } @@ -156,10 +156,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_HasMany_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Performers = _fakers.Performer.Generate(1); - var existingPerformers = _fakers.Performer.Generate(2); + List existingPerformers = _fakers.Performer.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -202,7 +202,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -211,15 +211,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Performers.Should().HaveCount(2); trackInDatabase.Performers.Should().ContainSingle(performer => performer.Id == existingPerformers[0].Id); trackInDatabase.Performers.Should().ContainSingle(performer => performer.Id == existingPerformers[1].Id); - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().HaveCount(3); }); } @@ -228,7 +226,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_HasManyThrough_relationship() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); + existingPlaylist.PlaylistMusicTracks = new List { new PlaylistMusicTrack @@ -237,7 +236,7 @@ public async Task Can_replace_HasManyThrough_relationship() } }; - var existingTracks = _fakers.MusicTrack.Generate(2); + List existingTracks = _fakers.MusicTrack.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -280,7 +279,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -292,7 +291,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(existingPlaylist.Id); @@ -304,7 +303,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => playlistInDatabase.PlaylistMusicTracks.Should().ContainSingle(playlistMusicTrack => playlistMusicTrack.MusicTrack.Id == existingTracks[0].Id); playlistInDatabase.PlaylistMusicTracks.Should().ContainSingle(playlistMusicTrack => playlistMusicTrack.MusicTrack.Id == existingTracks[1].Id); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(3); }); } @@ -328,14 +327,14 @@ public async Task Cannot_replace_for_href_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Usage of the 'href' element is not supported."); error.Detail.Should().BeNull(); @@ -365,14 +364,14 @@ public async Task Cannot_replace_for_missing_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.type' element is required."); error.Detail.Should().BeNull(); @@ -403,14 +402,14 @@ public async Task Cannot_replace_for_unknown_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -440,14 +439,14 @@ public async Task Cannot_replace_for_missing_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -458,7 +457,7 @@ public async Task Cannot_replace_for_missing_ID_in_ref() public async Task Cannot_replace_for_unknown_ID_in_ref() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -494,14 +493,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("The requested resource does not exist."); error.Detail.Should().Be("Resource of type 'recordCompanies' with ID '9999' does not exist."); @@ -512,9 +511,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_replace_for_incompatible_ID_in_ref() { // Arrange - var guid = Guid.NewGuid().ToString(); + string guid = Guid.NewGuid().ToString(); - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -550,14 +549,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().Be($"Failed to convert '{guid}' of type 'String' to type 'Int16'."); @@ -589,14 +588,14 @@ public async Task Cannot_replace_for_ID_and_local_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -627,14 +626,14 @@ public async Task Cannot_replace_for_unknown_relationship_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The referenced relationship does not exist."); error.Detail.Should().Be("Resource of type 'performers' does not contain a relationship named 'doesNotExist'."); @@ -645,7 +644,7 @@ public async Task Cannot_replace_for_unknown_relationship_in_ref() public async Task Cannot_replace_for_null_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -666,7 +665,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => id = existingTrack.StringId, relationship = "performers" }, - data = (object) null + data = (object)null } } }; @@ -674,14 +673,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected data[] element for to-many relationship."); error.Detail.Should().Be("Expected data[] element for 'performers' relationship."); @@ -719,14 +718,14 @@ public async Task Cannot_replace_for_missing_type_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data[].type' element is required."); error.Detail.Should().BeNull(); @@ -765,14 +764,14 @@ public async Task Cannot_replace_for_unknown_type_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -810,14 +809,14 @@ public async Task Cannot_replace_for_missing_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data[].id' or 'data[].lid' element is required."); error.Detail.Should().BeNull(); @@ -857,14 +856,14 @@ public async Task Cannot_replace_for_ID_and_local_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data[].id' or 'data[].lid' element is required."); error.Detail.Should().BeNull(); @@ -875,8 +874,8 @@ public async Task Cannot_replace_for_ID_and_local_ID_in_data() public async Task Cannot_replace_for_unknown_IDs_in_data() { // Arrange - var existingCompany = _fakers.RecordCompany.Generate(); - var trackIds = ArrayFactory.Create(Guid.NewGuid(), Guid.NewGuid()); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + Guid[] trackIds = ArrayFactory.Create(Guid.NewGuid(), Guid.NewGuid()); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -917,20 +916,20 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(2); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.NotFound); error1.Title.Should().Be("A related resource does not exist."); error1.Detail.Should().Be($"Related resource of type 'musicTracks' with ID '{trackIds[0]}' in relationship 'tracks' does not exist."); error1.Source.Pointer.Should().Be("/atomic:operations[0]"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.NotFound); error2.Title.Should().Be("A related resource does not exist."); error2.Detail.Should().Be($"Related resource of type 'musicTracks' with ID '{trackIds[1]}' in relationship 'tracks' does not exist."); @@ -941,7 +940,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_replace_for_incompatible_ID_in_data() { // Arrange - var existingCompany = _fakers.RecordCompany.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -977,14 +976,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().Be("Failed to convert 'invalid-guid' of type 'String' to type 'Guid'."); @@ -995,7 +994,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_replace_for_relationship_mismatch_between_ref_and_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1031,14 +1030,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource type mismatch between 'ref.relationship' and 'data[].type' element."); error.Detail.Should().Be("Expected resource of type 'performers' in 'data[].type', instead of 'playlists'."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs index 216878d00e..54365fd4da 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -27,7 +29,7 @@ public AtomicUpdateToOneRelationshipTests(ExampleIntegrationTestContext @@ -50,7 +52,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => id = existingLyric.StringId, relationship = "track" }, - data = (object) null + data = (object)null } } }; @@ -58,7 +60,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -67,13 +69,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var lyricInDatabase = await dbContext.Lyrics - .Include(lyric => lyric.Track) - .FirstWithIdAsync(existingLyric.Id); + Lyric lyricInDatabase = await dbContext.Lyrics.Include(lyric => lyric.Track).FirstWithIdAsync(existingLyric.Id); lyricInDatabase.Track.Should().BeNull(); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(1); }); } @@ -82,7 +82,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_clear_OneToOne_relationship_from_dependent_side() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Lyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -105,7 +105,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => id = existingTrack.StringId, relationship = "lyric" }, - data = (object) null + data = (object)null } } }; @@ -113,7 +113,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -122,13 +122,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Lyric) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Lyric).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Lyric.Should().BeNull(); - var lyricsInDatabase = await dbContext.Lyrics.ToListAsync(); + List lyricsInDatabase = await dbContext.Lyrics.ToListAsync(); lyricsInDatabase.Should().HaveCount(1); }); } @@ -137,7 +135,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_clear_ManyToOne_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -160,7 +158,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => id = existingTrack.StringId, relationship = "ownedBy" }, - data = (object) null + data = (object)null } } }; @@ -168,7 +166,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -177,13 +175,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.OwnedBy.Should().BeNull(); - var companiesInDatabase = await dbContext.RecordCompanies.ToListAsync(); + List companiesInDatabase = await dbContext.RecordCompanies.ToListAsync(); companiesInDatabase.Should().HaveCount(1); }); } @@ -192,8 +188,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_OneToOne_relationship_from_principal_side() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); - var existingTrack = _fakers.MusicTrack.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -226,7 +222,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -235,9 +231,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var lyricInDatabase = await dbContext.Lyrics - .Include(lyric => lyric.Track) - .FirstWithIdAsync(existingLyric.Id); + Lyric lyricInDatabase = await dbContext.Lyrics.Include(lyric => lyric.Track).FirstWithIdAsync(existingLyric.Id); lyricInDatabase.Track.Id.Should().Be(existingTrack.Id); }); @@ -247,8 +241,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_OneToOne_relationship_from_dependent_side() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); - var existingLyric = _fakers.Lyric.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -281,7 +275,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -290,9 +284,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Lyric) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Lyric).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Lyric.Id.Should().Be(existingLyric.Id); }); @@ -302,8 +294,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_ManyToOne_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); - var existingCompany = _fakers.RecordCompany.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -336,7 +328,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -345,9 +337,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.OwnedBy.Id.Should().Be(existingCompany.Id); }); @@ -357,10 +347,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_OneToOne_relationship_from_principal_side() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); existingLyric.Track = _fakers.MusicTrack.Generate(); - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -394,7 +384,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -403,13 +393,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var lyricInDatabase = await dbContext.Lyrics - .Include(lyric => lyric.Track) - .FirstWithIdAsync(existingLyric.Id); + Lyric lyricInDatabase = await dbContext.Lyrics.Include(lyric => lyric.Track).FirstWithIdAsync(existingLyric.Id); lyricInDatabase.Track.Id.Should().Be(existingTrack.Id); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(2); }); } @@ -418,10 +406,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_OneToOne_relationship_from_dependent_side() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Lyric = _fakers.Lyric.Generate(); - var existingLyric = _fakers.Lyric.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -455,7 +443,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -464,13 +452,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Lyric) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Lyric).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Lyric.Id.Should().Be(existingLyric.Id); - var lyricsInDatabase = await dbContext.Lyrics.ToListAsync(); + List lyricsInDatabase = await dbContext.Lyrics.ToListAsync(); lyricsInDatabase.Should().HaveCount(2); }); } @@ -479,10 +465,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_ManyToOne_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); - var existingCompany = _fakers.RecordCompany.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -516,7 +502,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -525,13 +511,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.OwnedBy.Id.Should().Be(existingCompany.Id); - var companiesInDatabase = await dbContext.RecordCompanies.ToListAsync(); + List companiesInDatabase = await dbContext.RecordCompanies.ToListAsync(); companiesInDatabase.Should().HaveCount(2); }); } @@ -555,14 +539,14 @@ public async Task Cannot_create_for_href_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Usage of the 'href' element is not supported."); error.Detail.Should().BeNull(); @@ -592,14 +576,14 @@ public async Task Cannot_create_for_missing_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.type' element is required."); error.Detail.Should().BeNull(); @@ -630,14 +614,14 @@ public async Task Cannot_create_for_unknown_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -667,14 +651,14 @@ public async Task Cannot_create_for_missing_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -687,7 +671,7 @@ public async Task Cannot_create_for_unknown_ID_in_ref() // Arrange string missingTrackId = Guid.NewGuid().ToString(); - var existingLyric = _fakers.Lyric.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -720,14 +704,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("The requested resource does not exist."); error.Detail.Should().Be($"Resource of type 'musicTracks' with ID '{missingTrackId}' does not exist."); @@ -738,7 +722,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_for_incompatible_ID_in_ref() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -771,14 +755,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().Be("Failed to convert 'invalid-guid' of type 'String' to type 'Guid'."); @@ -810,14 +794,14 @@ public async Task Cannot_create_for_ID_and_local_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -848,14 +832,14 @@ public async Task Cannot_create_for_unknown_relationship_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The referenced relationship does not exist."); error.Detail.Should().Be("Resource of type 'performers' does not contain a relationship named 'doesNotExist'."); @@ -866,7 +850,7 @@ public async Task Cannot_create_for_unknown_relationship_in_ref() public async Task Cannot_create_for_array_in_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -902,14 +886,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected single data element for to-one relationship."); error.Detail.Should().Be("Expected single data element for 'lyric' relationship."); @@ -944,14 +928,14 @@ public async Task Cannot_create_for_missing_type_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data.type' element is required."); error.Detail.Should().BeNull(); @@ -987,14 +971,14 @@ public async Task Cannot_create_for_unknown_type_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -1029,14 +1013,14 @@ public async Task Cannot_create_for_missing_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data.id' or 'data.lid' element is required."); error.Detail.Should().BeNull(); @@ -1073,14 +1057,14 @@ public async Task Cannot_create_for_ID_and_local_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data.id' or 'data.lid' element is required."); error.Detail.Should().BeNull(); @@ -1091,7 +1075,7 @@ public async Task Cannot_create_for_ID_and_local_ID_in_data() public async Task Cannot_create_for_unknown_ID_in_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1124,14 +1108,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("A related resource does not exist."); error.Detail.Should().Be("Related resource of type 'lyrics' with ID '99999999' in relationship 'lyric' does not exist."); @@ -1142,7 +1126,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_for_incompatible_ID_in_data() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1175,14 +1159,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().Be("Failed to convert 'invalid-guid' of type 'String' to type 'Guid'."); @@ -1193,7 +1177,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_for_relationship_mismatch_between_ref_and_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1226,14 +1210,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource type mismatch between 'ref.relationship' and 'data.type' element."); error.Detail.Should().Be("Expected resource of type 'lyrics' in 'data.type', instead of 'playlists'."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs index 1b4c51d93b..b356585dd2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore; @@ -29,7 +30,7 @@ public AtomicReplaceToManyRelationshipTests(ExampleIntegrationTestContext @@ -65,7 +66,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -74,13 +75,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Performers.Should().BeEmpty(); - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().HaveCount(2); }); } @@ -89,7 +88,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_clear_HasManyThrough_relationship() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); + existingPlaylist.PlaylistMusicTracks = new List { new PlaylistMusicTrack @@ -135,7 +135,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -147,7 +147,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(existingPlaylist.Id); @@ -157,7 +157,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => playlistInDatabase.PlaylistMusicTracks.Should().BeEmpty(); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(2); }); } @@ -166,10 +166,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_HasMany_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Performers = _fakers.Performer.Generate(1); - var existingPerformers = _fakers.Performer.Generate(2); + List existingPerformers = _fakers.Performer.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -217,7 +217,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -226,15 +226,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Performers) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Performers.Should().HaveCount(2); trackInDatabase.Performers.Should().ContainSingle(performer => performer.Id == existingPerformers[0].Id); trackInDatabase.Performers.Should().ContainSingle(performer => performer.Id == existingPerformers[1].Id); - var performersInDatabase = await dbContext.Performers.ToListAsync(); + List performersInDatabase = await dbContext.Performers.ToListAsync(); performersInDatabase.Should().HaveCount(3); }); } @@ -243,7 +241,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_HasManyThrough_relationship() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); + existingPlaylist.PlaylistMusicTracks = new List { new PlaylistMusicTrack @@ -252,7 +251,7 @@ public async Task Can_replace_HasManyThrough_relationship() } }; - var existingTracks = _fakers.MusicTrack.Generate(2); + List existingTracks = _fakers.MusicTrack.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -300,7 +299,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -312,7 +311,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var playlistInDatabase = await dbContext.Playlists + Playlist playlistInDatabase = await dbContext.Playlists .Include(playlist => playlist.PlaylistMusicTracks) .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack) .FirstWithIdAsync(existingPlaylist.Id); @@ -324,7 +323,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => playlistInDatabase.PlaylistMusicTracks.Should().ContainSingle(playlistMusicTrack => playlistMusicTrack.MusicTrack.Id == existingTracks[0].Id); playlistInDatabase.PlaylistMusicTracks.Should().ContainSingle(playlistMusicTrack => playlistMusicTrack.MusicTrack.Id == existingTracks[1].Id); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(3); }); } @@ -333,7 +332,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_replace_for_null_relationship_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -356,7 +355,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => { performers = new { - data = (object) null + data = (object)null } } } @@ -367,14 +366,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected data[] element for to-many relationship."); error.Detail.Should().Be("Expected data[] element for 'performers' relationship."); @@ -417,14 +416,14 @@ public async Task Cannot_replace_for_missing_type_in_relationship_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'type' element."); error.Detail.Should().Be("Expected 'type' element in 'tracks' relationship."); @@ -468,14 +467,14 @@ public async Task Cannot_replace_for_unknown_type_in_relationship_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -518,14 +517,14 @@ public async Task Cannot_replace_for_missing_ID_in_relationship_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'id' or 'lid' element."); error.Detail.Should().Be("Expected 'id' or 'lid' element in 'performers' relationship."); @@ -570,14 +569,14 @@ public async Task Cannot_replace_for_ID_and_local_ID_relationship_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'id' or 'lid' element."); error.Detail.Should().Be("Expected 'id' or 'lid' element in 'performers' relationship."); @@ -588,8 +587,8 @@ public async Task Cannot_replace_for_ID_and_local_ID_relationship_in_data() public async Task Cannot_replace_for_unknown_IDs_in_relationship_data() { // Arrange - var existingCompany = _fakers.RecordCompany.Generate(); - var trackIds = ArrayFactory.Create(Guid.NewGuid(), Guid.NewGuid()); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + Guid[] trackIds = ArrayFactory.Create(Guid.NewGuid(), Guid.NewGuid()); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -635,20 +634,20 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(2); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.NotFound); error1.Title.Should().Be("A related resource does not exist."); error1.Detail.Should().Be($"Related resource of type 'musicTracks' with ID '{trackIds[0]}' in relationship 'tracks' does not exist."); error1.Source.Pointer.Should().Be("/atomic:operations[0]"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.NotFound); error2.Title.Should().Be("A related resource does not exist."); error2.Detail.Should().Be($"Related resource of type 'musicTracks' with ID '{trackIds[1]}' in relationship 'tracks' does not exist."); @@ -659,7 +658,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_for_relationship_mismatch() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -700,14 +699,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Relationship contains incompatible resource type."); error.Detail.Should().Be("Relationship 'performers' contains incompatible resource type 'playlists'."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs index 626fa31e02..b870ca48b3 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Extensions; @@ -13,8 +14,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Updating.Resources { - public sealed class AtomicUpdateResourceTests - : IClassFixture, OperationsDbContext>> + public sealed class AtomicUpdateResourceTests : IClassFixture, OperationsDbContext>> { private readonly ExampleIntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new OperationsFakers(); @@ -32,8 +32,8 @@ public async Task Can_update_resources() // Arrange const int elementCount = 5; - var existingTracks = _fakers.MusicTrack.Generate(elementCount); - var newTrackTitles = _fakers.MusicTrack.Generate(elementCount).Select(musicTrack => musicTrack.Title).ToArray(); + List existingTracks = _fakers.MusicTrack.Generate(elementCount); + string[] newTrackTitles = _fakers.MusicTrack.Generate(elementCount).Select(musicTrack => musicTrack.Title).ToArray(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -43,6 +43,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => }); var operationElements = new List(elementCount); + for (int index = 0; index < elementCount; index++) { operationElements.Add(new @@ -68,7 +69,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -77,14 +78,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var tracksInDatabase = await dbContext.MusicTracks - .ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(elementCount); for (int index = 0; index < elementCount; index++) { - var trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == existingTracks[index].Id); + MusicTrack trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == existingTracks[index].Id); trackInDatabase.Title.Should().Be(newTrackTitles[index]); trackInDatabase.Genre.Should().Be(existingTracks[index].Genre); @@ -96,7 +96,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_without_attributes_or_relationships() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -130,7 +130,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -139,9 +139,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Title.Should().Be(existingTrack.Title); trackInDatabase.Genre.Should().Be(existingTrack.Genre); @@ -155,8 +153,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_unknown_attribute() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); - var newTitle = _fakers.MusicTrack.Generate().Title; + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + string newTitle = _fakers.MusicTrack.Generate().Title; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -188,7 +186,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -197,7 +195,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.FirstWithIdAsync(existingTrack.Id); trackInDatabase.Title.Should().Be(newTitle); }); @@ -207,7 +205,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_unknown_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -245,7 +243,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -257,10 +255,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_partially_update_resource_without_side_effects() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); - var newGenre = _fakers.MusicTrack.Generate().Genre; + string newGenre = _fakers.MusicTrack.Generate().Genre; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -291,7 +289,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -300,9 +298,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Title.Should().Be(existingTrack.Title); trackInDatabase.LengthInSeconds.Should().Be(existingTrack.LengthInSeconds); @@ -318,13 +314,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_completely_update_resource_without_side_effects() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); - var newTitle = _fakers.MusicTrack.Generate().Title; - var newLengthInSeconds = _fakers.MusicTrack.Generate().LengthInSeconds; - var newGenre = _fakers.MusicTrack.Generate().Genre; - var newReleasedAt = _fakers.MusicTrack.Generate().ReleasedAt; + string newTitle = _fakers.MusicTrack.Generate().Title; + decimal? newLengthInSeconds = _fakers.MusicTrack.Generate().LengthInSeconds; + string newGenre = _fakers.MusicTrack.Generate().Genre; + DateTimeOffset newReleasedAt = _fakers.MusicTrack.Generate().ReleasedAt; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -358,7 +354,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -367,9 +363,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Title.Should().Be(newTitle); trackInDatabase.LengthInSeconds.Should().Be(newLengthInSeconds); @@ -385,8 +379,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_side_effects() { // Arrange - var existingLanguage = _fakers.TextLanguage.Generate(); - var newIsoCode = _fakers.TextLanguage.Generate().IsoCode; + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); + string newIsoCode = _fakers.TextLanguage.Generate().IsoCode; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -417,7 +411,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -431,7 +426,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var languageInDatabase = await dbContext.TextLanguages.FirstWithIdAsync(existingLanguage.Id); + TextLanguage languageInDatabase = await dbContext.TextLanguages.FirstWithIdAsync(existingLanguage.Id); languageInDatabase.IsoCode.Should().Be(newIsoCode); }); @@ -441,7 +436,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Update_resource_with_side_effects_hides_relationship_data_in_response() { // Arrange - var existingLanguage = _fakers.TextLanguage.Generate(); + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); existingLanguage.Lyrics = _fakers.Lyric.Generate(1); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -469,7 +464,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -499,14 +495,14 @@ public async Task Cannot_update_resource_for_href_element() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Usage of the 'href' element is not supported."); error.Detail.Should().BeNull(); @@ -517,8 +513,8 @@ public async Task Cannot_update_resource_for_href_element() public async Task Can_update_resource_for_ref_element() { // Arrange - var existingPerformer = _fakers.Performer.Generate(); - var newArtistName = _fakers.Performer.Generate().ArtistName; + Performer existingPerformer = _fakers.Performer.Generate(); + string newArtistName = _fakers.Performer.Generate().ArtistName; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -554,7 +550,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -563,7 +559,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var performerInDatabase = await dbContext.Performers.FirstWithIdAsync(existingPerformer.Id); + Performer performerInDatabase = await dbContext.Performers.FirstWithIdAsync(existingPerformer.Id); performerInDatabase.ArtistName.Should().Be(newArtistName); performerInDatabase.BornAt.Should().BeCloseTo(existingPerformer.BornAt); @@ -603,14 +599,14 @@ public async Task Cannot_update_resource_for_missing_type_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.type' element is required."); error.Detail.Should().BeNull(); @@ -650,14 +646,14 @@ public async Task Cannot_update_resource_for_missing_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -699,14 +695,14 @@ public async Task Cannot_update_resource_for_ID_and_local_ID_in_ref() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'ref.id' or 'ref.lid' element is required."); error.Detail.Should().BeNull(); @@ -731,14 +727,14 @@ public async Task Cannot_update_resource_for_missing_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data' element is required."); error.Detail.Should().BeNull(); @@ -773,14 +769,14 @@ public async Task Cannot_update_resource_for_missing_type_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data.type' element is required."); error.Detail.Should().BeNull(); @@ -815,14 +811,14 @@ public async Task Cannot_update_resource_for_missing_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data.id' or 'data.lid' element is required."); error.Detail.Should().BeNull(); @@ -859,14 +855,14 @@ public async Task Cannot_update_resource_for_ID_and_local_ID_in_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: The 'data.id' or 'data.lid' element is required."); error.Detail.Should().BeNull(); @@ -877,7 +873,7 @@ public async Task Cannot_update_resource_for_ID_and_local_ID_in_data() public async Task Cannot_update_resource_for_array_in_data() { // Arrange - var existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -911,14 +907,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected single data element for create/update resource operation."); error.Detail.Should().BeNull(); @@ -959,14 +955,14 @@ public async Task Cannot_update_on_resource_type_mismatch_between_ref_and_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource type mismatch between 'ref.type' and 'data.type' element."); error.Detail.Should().Be("Expected resource of type 'performers' in 'data.type', instead of 'playlists'."); @@ -1007,14 +1003,14 @@ public async Task Cannot_update_on_resource_ID_mismatch_between_ref_and_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource ID mismatch between 'ref.id' and 'data.id' element."); error.Detail.Should().Be("Expected resource with ID '12345678' in 'data.id', instead of '87654321'."); @@ -1055,14 +1051,14 @@ public async Task Cannot_update_on_resource_local_ID_mismatch_between_ref_and_da const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource local ID mismatch between 'ref.lid' and 'data.lid' element."); error.Detail.Should().Be("Expected resource with local ID 'local-1' in 'data.lid', instead of 'local-2'."); @@ -1103,14 +1099,14 @@ public async Task Cannot_update_on_mixture_of_ID_and_local_ID_between_ref_and_da const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource identity mismatch between 'ref.id' and 'data.lid' element."); error.Detail.Should().Be("Expected resource with ID '12345678' in 'data.id', instead of 'local-1' in 'data.lid'."); @@ -1151,14 +1147,14 @@ public async Task Cannot_update_on_mixture_of_local_ID_and_ID_between_ref_and_da const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource identity mismatch between 'ref.lid' and 'data.id' element."); error.Detail.Should().Be("Expected resource with local ID 'local-1' in 'data.lid', instead of '12345678' in 'data.id'."); @@ -1194,14 +1190,14 @@ public async Task Cannot_update_resource_for_unknown_type() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -1237,14 +1233,14 @@ public async Task Cannot_update_resource_for_unknown_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("The requested resource does not exist."); error.Detail.Should().Be("Resource of type 'performers' with ID '99999999' does not exist."); @@ -1255,7 +1251,7 @@ public async Task Cannot_update_resource_for_unknown_ID() public async Task Cannot_update_resource_for_incompatible_ID() { // Arrange - var guid = Guid.NewGuid().ToString(); + string guid = Guid.NewGuid().ToString(); var requestBody = new { @@ -1284,14 +1280,14 @@ public async Task Cannot_update_resource_for_incompatible_ID() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().Be($"Failed to convert '{guid}' of type 'String' to type 'Int32'."); @@ -1302,7 +1298,7 @@ public async Task Cannot_update_resource_for_incompatible_ID() public async Task Cannot_update_resource_attribute_with_blocked_capability() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1333,14 +1329,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Changing the value of the requested attribute is not allowed."); error.Detail.Should().Be("Changing the value of 'createdAt' is not allowed."); @@ -1351,7 +1347,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_update_resource_with_readonly_attribute() { // Arrange - var existingPlaylist = _fakers.Playlist.Generate(); + Playlist existingPlaylist = _fakers.Playlist.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1382,14 +1378,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Attribute is read-only."); error.Detail.Should().Be("Attribute 'isArchived' is read-only."); @@ -1400,7 +1396,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_change_ID_of_existing_resource() { // Arrange - var existingCompany = _fakers.RecordCompany.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1431,14 +1427,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Resource ID is read-only."); error.Detail.Should().BeNull(); @@ -1449,7 +1445,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_update_resource_with_incompatible_attribute_value() { // Arrange - var existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1480,14 +1476,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Detail.Should().StartWith("Failed to convert 'not-a-valid-time' of type 'String' to type 'DateTimeOffset'. - Request body:"); @@ -1498,16 +1494,16 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_attributes_and_multiple_relationship_types() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Lyric = _fakers.Lyric.Generate(); existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); existingTrack.Performers = _fakers.Performer.Generate(1); - var newGenre = _fakers.MusicTrack.Generate().Genre; + string newGenre = _fakers.MusicTrack.Generate().Genre; - var existingLyric = _fakers.Lyric.Generate(); - var existingCompany = _fakers.RecordCompany.Generate(); - var existingPerformer = _fakers.Performer.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -1568,7 +1564,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -1580,7 +1576,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var trackInDatabase = await dbContext.MusicTracks + MusicTrack trackInDatabase = await dbContext.MusicTracks .Include(musicTrack => musicTrack.Lyric) .Include(musicTrack => musicTrack.OwnedBy) .Include(musicTrack => musicTrack.Performers) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs index f3069f8f07..52f58e2506 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -27,7 +29,7 @@ public AtomicUpdateToOneRelationshipTests(ExampleIntegrationTestContext @@ -52,7 +54,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => { track = new { - data = (object) null + data = (object)null } } } @@ -63,7 +65,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -72,13 +74,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var lyricInDatabase = await dbContext.Lyrics - .Include(lyric => lyric.Track) - .FirstWithIdAsync(existingLyric.Id); + Lyric lyricInDatabase = await dbContext.Lyrics.Include(lyric => lyric.Track).FirstWithIdAsync(existingLyric.Id); lyricInDatabase.Track.Should().BeNull(); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(1); }); } @@ -87,7 +87,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_clear_OneToOne_relationship_from_dependent_side() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Lyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -112,7 +112,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => { lyric = new { - data = (object) null + data = (object)null } } } @@ -123,7 +123,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -132,13 +132,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Lyric) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Lyric).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Lyric.Should().BeNull(); - var lyricsInDatabase = await dbContext.Lyrics.ToListAsync(); + List lyricsInDatabase = await dbContext.Lyrics.ToListAsync(); lyricsInDatabase.Should().HaveCount(1); }); } @@ -147,7 +145,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_clear_ManyToOne_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -172,7 +170,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => { ownedBy = new { - data = (object) null + data = (object)null } } } @@ -183,7 +181,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -192,13 +190,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.OwnedBy.Should().BeNull(); - var companiesInDatabase = await dbContext.RecordCompanies.ToListAsync(); + List companiesInDatabase = await dbContext.RecordCompanies.ToListAsync(); companiesInDatabase.Should().HaveCount(1); }); } @@ -207,8 +203,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_OneToOne_relationship_from_principal_side() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); - var existingTrack = _fakers.MusicTrack.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -246,7 +242,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -255,9 +251,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var lyricInDatabase = await dbContext.Lyrics - .Include(lyric => lyric.Track) - .FirstWithIdAsync(existingLyric.Id); + Lyric lyricInDatabase = await dbContext.Lyrics.Include(lyric => lyric.Track).FirstWithIdAsync(existingLyric.Id); lyricInDatabase.Track.Id.Should().Be(existingTrack.Id); }); @@ -267,8 +261,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_OneToOne_relationship_from_dependent_side() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); - var existingLyric = _fakers.Lyric.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -306,7 +300,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -315,9 +309,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Lyric) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Lyric).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Lyric.Id.Should().Be(existingLyric.Id); }); @@ -327,8 +319,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_ManyToOne_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); - var existingCompany = _fakers.RecordCompany.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -366,7 +358,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -375,9 +367,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.OwnedBy.Id.Should().Be(existingCompany.Id); }); @@ -387,10 +377,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_OneToOne_relationship_from_principal_side() { // Arrange - var existingLyric = _fakers.Lyric.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); existingLyric.Track = _fakers.MusicTrack.Generate(); - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -429,7 +419,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -438,13 +428,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var lyricInDatabase = await dbContext.Lyrics - .Include(lyric => lyric.Track) - .FirstWithIdAsync(existingLyric.Id); + Lyric lyricInDatabase = await dbContext.Lyrics.Include(lyric => lyric.Track).FirstWithIdAsync(existingLyric.Id); lyricInDatabase.Track.Id.Should().Be(existingTrack.Id); - var tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); + List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); tracksInDatabase.Should().HaveCount(2); }); } @@ -453,10 +441,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_OneToOne_relationship_from_dependent_side() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.Lyric = _fakers.Lyric.Generate(); - var existingLyric = _fakers.Lyric.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -495,7 +483,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -504,13 +492,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.Lyric) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Lyric).FirstWithIdAsync(existingTrack.Id); trackInDatabase.Lyric.Id.Should().Be(existingLyric.Id); - var lyricsInDatabase = await dbContext.Lyrics.ToListAsync(); + List lyricsInDatabase = await dbContext.Lyrics.ToListAsync(); lyricsInDatabase.Should().HaveCount(2); }); } @@ -519,10 +505,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_replace_ManyToOne_relationship() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); - var existingCompany = _fakers.RecordCompany.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -561,7 +547,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -570,13 +556,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var trackInDatabase = await dbContext.MusicTracks - .Include(musicTrack => musicTrack.OwnedBy) - .FirstWithIdAsync(existingTrack.Id); + MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.OwnedBy).FirstWithIdAsync(existingTrack.Id); trackInDatabase.OwnedBy.Id.Should().Be(existingCompany.Id); - var companiesInDatabase = await dbContext.RecordCompanies.ToListAsync(); + List companiesInDatabase = await dbContext.RecordCompanies.ToListAsync(); companiesInDatabase.Should().HaveCount(2); }); } @@ -585,7 +569,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_for_array_in_relationship_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -626,14 +610,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Expected single data element for to-one relationship."); error.Detail.Should().Be("Expected single data element for 'lyric' relationship."); @@ -673,14 +657,14 @@ public async Task Cannot_create_for_missing_type_in_relationship_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'type' element."); error.Detail.Should().Be("Expected 'type' element in 'track' relationship."); @@ -721,14 +705,14 @@ public async Task Cannot_create_for_unknown_type_in_relationship_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body includes unknown resource type."); error.Detail.Should().Be("Resource type 'doesNotExist' does not exist."); @@ -768,14 +752,14 @@ public async Task Cannot_create_for_missing_ID_in_relationship_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'id' or 'lid' element."); error.Detail.Should().Be("Expected 'id' or 'lid' element in 'lyric' relationship."); @@ -817,14 +801,14 @@ public async Task Cannot_create_for_ID_and_local_ID_in_relationship_data() const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Request body must include 'id' or 'lid' element."); error.Detail.Should().Be("Expected 'id' or 'lid' element in 'lyric' relationship."); @@ -835,7 +819,7 @@ public async Task Cannot_create_for_ID_and_local_ID_in_relationship_data() public async Task Cannot_create_for_unknown_ID_in_relationship_data() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -873,14 +857,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("A related resource does not exist."); error.Detail.Should().Be("Related resource of type 'lyrics' with ID '99999999' in relationship 'lyric' does not exist."); @@ -891,7 +875,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_for_relationship_mismatch() { // Arrange - var existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -929,14 +913,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/operations"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body: Relationship contains incompatible resource type."); error.Detail.Should().Be("Relationship 'lyric' contains incompatible resource type 'playlists'."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Car.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Car.cs index eccd7acda0..5263e4e362 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Car.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Car.cs @@ -15,7 +15,8 @@ public override string Id get => $"{RegionId}:{LicensePlate}"; set { - var elements = value.Split(':'); + string[] elements = value.Split(':'); + if (elements.Length == 2) { if (int.TryParse(elements[0], out int regionId)) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs index 2da565d34e..ee0ae50c6f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs @@ -11,11 +11,11 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CompositeKeys { /// - /// Rewrites an expression tree, updating all references to with - /// the combination of and . + /// Rewrites an expression tree, updating all references to with the combination of and + /// . /// /// - /// This enables queries to use , which is not mapped in the database. + /// This enables queries to use , which is not mapped in the database. /// internal sealed class CarExpressionRewriter : QueryExpressionRewriter { @@ -24,23 +24,19 @@ internal sealed class CarExpressionRewriter : QueryExpressionRewriter public CarExpressionRewriter(IResourceContextProvider resourceContextProvider) { - var carResourceContext = resourceContextProvider.GetResourceContext(); + ResourceContext carResourceContext = resourceContextProvider.GetResourceContext(); - _regionIdAttribute = - carResourceContext.Attributes.Single(attribute => - attribute.Property.Name == nameof(Car.RegionId)); + _regionIdAttribute = carResourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Car.RegionId)); - _licensePlateAttribute = - carResourceContext.Attributes.Single(attribute => - attribute.Property.Name == nameof(Car.LicensePlate)); + _licensePlateAttribute = carResourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Car.LicensePlate)); } public override QueryExpression VisitComparison(ComparisonExpression expression, object argument) { - if (expression.Left is ResourceFieldChainExpression leftChain && - expression.Right is LiteralConstantExpression rightConstant) + if (expression.Left is ResourceFieldChainExpression leftChain && expression.Right is LiteralConstantExpression rightConstant) { PropertyInfo leftProperty = leftChain.Fields.Last().Property; + if (IsCarId(leftProperty)) { if (expression.Operator != ComparisonOperator.Equals) @@ -58,9 +54,10 @@ public override QueryExpression VisitComparison(ComparisonExpression expression, public override QueryExpression VisitEqualsAnyOf(EqualsAnyOfExpression expression, object argument) { PropertyInfo property = expression.TargetAttribute.Fields.Last().Property; + if (IsCarId(property)) { - var carStringIds = expression.Constants.Select(constant => constant.Value).ToArray(); + string[] carStringIds = expression.Constants.Select(constant => constant.Value).ToArray(); return RewriteFilterOnCarStringIds(expression.TargetAttribute, carStringIds); } @@ -70,6 +67,7 @@ public override QueryExpression VisitEqualsAnyOf(EqualsAnyOfExpression expressio public override QueryExpression VisitMatchText(MatchTextExpression expression, object argument) { PropertyInfo property = expression.TargetAttribute.Fields.Last().Property; + if (IsCarId(property)) { throw new NotSupportedException("Partial text matching on Car IDs is not possible."); @@ -83,34 +81,34 @@ private static bool IsCarId(PropertyInfo property) return property.Name == nameof(Identifiable.Id) && property.DeclaringType == typeof(Car); } - private QueryExpression RewriteFilterOnCarStringIds(ResourceFieldChainExpression existingCarIdChain, - IEnumerable carStringIds) + private QueryExpression RewriteFilterOnCarStringIds(ResourceFieldChainExpression existingCarIdChain, IEnumerable carStringIds) { var outerTerms = new List(); - foreach (var carStringId in carStringIds) + foreach (string carStringId in carStringIds) { var tempCar = new Car { StringId = carStringId }; - var keyComparison = - CreateEqualityComparisonOnCompositeKey(existingCarIdChain, tempCar.RegionId, tempCar.LicensePlate); + QueryExpression keyComparison = CreateEqualityComparisonOnCompositeKey(existingCarIdChain, tempCar.RegionId, tempCar.LicensePlate); outerTerms.Add(keyComparison); } return outerTerms.Count == 1 ? outerTerms[0] : new LogicalExpression(LogicalOperator.Or, outerTerms); } - private QueryExpression CreateEqualityComparisonOnCompositeKey(ResourceFieldChainExpression existingCarIdChain, - long regionIdValue, string licensePlateValue) + private QueryExpression CreateEqualityComparisonOnCompositeKey(ResourceFieldChainExpression existingCarIdChain, long regionIdValue, + string licensePlateValue) { - var regionIdChain = ReplaceLastAttributeInChain(existingCarIdChain, _regionIdAttribute); + ResourceFieldChainExpression regionIdChain = ReplaceLastAttributeInChain(existingCarIdChain, _regionIdAttribute); + var regionIdComparison = new ComparisonExpression(ComparisonOperator.Equals, regionIdChain, new LiteralConstantExpression(regionIdValue.ToString())); - var licensePlateChain = ReplaceLastAttributeInChain(existingCarIdChain, _licensePlateAttribute); + ResourceFieldChainExpression licensePlateChain = ReplaceLastAttributeInChain(existingCarIdChain, _licensePlateAttribute); + var licensePlateComparison = new ComparisonExpression(ComparisonOperator.Equals, licensePlateChain, new LiteralConstantExpression(licensePlateValue)); @@ -125,15 +123,14 @@ public override QueryExpression VisitSort(SortExpression expression, object argu { var newSortElements = new List(); - foreach (var sortElement in expression.Elements) + foreach (SortElementExpression sortElement in expression.Elements) { if (IsSortOnCarId(sortElement)) { - var regionIdSort = ReplaceLastAttributeInChain(sortElement.TargetAttribute, _regionIdAttribute); + ResourceFieldChainExpression regionIdSort = ReplaceLastAttributeInChain(sortElement.TargetAttribute, _regionIdAttribute); newSortElements.Add(new SortElementExpression(regionIdSort, sortElement.IsAscending)); - var licensePlateSort = - ReplaceLastAttributeInChain(sortElement.TargetAttribute, _licensePlateAttribute); + ResourceFieldChainExpression licensePlateSort = ReplaceLastAttributeInChain(sortElement.TargetAttribute, _licensePlateAttribute); newSortElements.Add(new SortElementExpression(licensePlateSort, sortElement.IsAscending)); } else @@ -150,6 +147,7 @@ private static bool IsSortOnCarId(SortElementExpression sortElement) if (sortElement.TargetAttribute != null) { PropertyInfo property = sortElement.TargetAttribute.Fields.Last().Property; + if (IsCarId(property)) { return true; @@ -159,10 +157,9 @@ private static bool IsSortOnCarId(SortElementExpression sortElement) return false; } - private static ResourceFieldChainExpression ReplaceLastAttributeInChain( - ResourceFieldChainExpression resourceFieldChain, AttrAttribute attribute) + private static ResourceFieldChainExpression ReplaceLastAttributeInChain(ResourceFieldChainExpression resourceFieldChain, AttrAttribute attribute) { - var fields = resourceFieldChain.Fields.ToList(); + List fields = resourceFieldChain.Fields.ToList(); fields[^1] = attribute; return new ResourceFieldChainExpression(fields); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs index a167ae4fff..8142d8d378 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs @@ -15,8 +15,7 @@ public sealed class CarRepository : EntityFrameworkCoreRepository { private readonly IResourceGraph _resourceGraph; - public CarRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, - IResourceGraph resourceGraph, IResourceFactory resourceFactory, + public CarRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) { @@ -35,13 +34,13 @@ private void RecursiveRewriteFilterInLayer(QueryLayer queryLayer) if (queryLayer.Filter != null) { var writer = new CarExpressionRewriter(_resourceGraph); - queryLayer.Filter = (FilterExpression) writer.Visit(queryLayer.Filter, null); + queryLayer.Filter = (FilterExpression)writer.Visit(queryLayer.Filter, null); } if (queryLayer.Sort != null) { var writer = new CarExpressionRewriter(_resourceGraph); - queryLayer.Sort = (SortExpression) writer.Visit(queryLayer.Sort, null); + queryLayer.Sort = (SortExpression)writer.Visit(queryLayer.Sort, null); } if (queryLayer.Projection != null) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarsController.cs index f264c043e3..982f72d625 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarsController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CompositeKeys { public sealed class CarsController : JsonApiController { - public CarsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public CarsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs index 602dd90e15..13609a17ec 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs @@ -20,7 +20,11 @@ public CompositeDbContext(DbContextOptions options) protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() - .HasKey(car => new {car.RegionId, car.LicensePlate}); + .HasKey(car => new + { + car.RegionId, + car.LicensePlate + }); builder.Entity() .HasOne(engine => engine.Car) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CompositeKeyTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CompositeKeyTests.cs index b8a74e6232..f4453b50c7 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CompositeKeyTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CompositeKeyTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -13,8 +14,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CompositeKeys { - public sealed class CompositeKeyTests - : IClassFixture, CompositeDbContext>> + public sealed class CompositeKeyTests : IClassFixture, CompositeDbContext>> { private readonly ExampleIntegrationTestContext, CompositeDbContext> _testContext; @@ -27,7 +27,7 @@ public CompositeKeyTests(ExampleIntegrationTestContext(); }); - var options = (JsonApiOptions) testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.AllowClientGeneratedIds = true; } @@ -51,7 +51,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/cars?filter=any(id,'123:AA-BB-11','999:XX-YY-22')"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -77,10 +77,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/cars/" + car.StringId; + string route = "/cars/" + car.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -109,7 +109,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/cars?sort=id"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -138,7 +138,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/cars?fields[cars]=id"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -172,7 +172,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/cars"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -181,8 +181,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var carInDatabase = await dbContext.Cars - .FirstOrDefaultAsync(car => car.RegionId == 123 && car.LicensePlate == "AA-BB-11"); + Car carInDatabase = await dbContext.Cars.FirstOrDefaultAsync(car => car.RegionId == 123 && car.LicensePlate == "AA-BB-11"); carInDatabase.Should().NotBeNull(); carInDatabase.Id.Should().Be("123:AA-BB-11"); @@ -231,10 +230,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = "/engines/" + existingEngine.StringId; + string route = "/engines/" + existingEngine.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -243,9 +242,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var engineInDatabase = await dbContext.Engines - .Include(engine => engine.Car) - .FirstWithIdAsync(existingEngine.Id); + Engine engineInDatabase = await dbContext.Engines.Include(engine => engine.Car).FirstWithIdAsync(existingEngine.Id); engineInDatabase.Car.Should().NotBeNull(); engineInDatabase.Car.Id.Should().Be(existingCar.StringId); @@ -283,16 +280,16 @@ await _testContext.RunOnDatabaseAsync(async dbContext => { car = new { - data = (object) null + data = (object)null } } } }; - var route = "/engines/" + existingEngine.StringId; + string route = "/engines/" + existingEngine.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -301,9 +298,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var engineInDatabase = await dbContext.Engines - .Include(engine => engine.Car) - .FirstWithIdAsync(existingEngine.Id); + Engine engineInDatabase = await dbContext.Engines.Include(engine => engine.Car).FirstWithIdAsync(existingEngine.Id); engineInDatabase.Car.Should().BeNull(); }); @@ -350,10 +345,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = $"/dealerships/{existingDealership.StringId}/relationships/inventory"; + string route = $"/dealerships/{existingDealership.StringId}/relationships/inventory"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -362,9 +357,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var dealershipInDatabase = await dbContext.Dealerships - .Include(dealership => dealership.Inventory) - .FirstWithIdOrDefaultAsync(existingDealership.Id); + Dealership dealershipInDatabase = await dbContext.Dealerships + .Include(dealership => dealership.Inventory).FirstWithIdOrDefaultAsync(existingDealership.Id); dealershipInDatabase.Inventory.Should().HaveCount(1); dealershipInDatabase.Inventory.Should().ContainSingle(car => car.Id == existingDealership.Inventory.ElementAt(1).Id); @@ -379,6 +373,7 @@ public async Task Can_add_to_OneToMany_relationship() { Address = "Dam 1, 1012JS Amsterdam, the Netherlands" }; + var existingCar = new Car { RegionId = 123, @@ -404,10 +399,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = $"/dealerships/{existingDealership.StringId}/relationships/inventory"; + string route = $"/dealerships/{existingDealership.StringId}/relationships/inventory"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -416,9 +411,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var dealershipInDatabase = await dbContext.Dealerships - .Include(dealership => dealership.Inventory) - .FirstWithIdOrDefaultAsync(existingDealership.Id); + Dealership dealershipInDatabase = await dbContext.Dealerships + .Include(dealership => dealership.Inventory).FirstWithIdOrDefaultAsync(existingDealership.Id); dealershipInDatabase.Inventory.Should().HaveCount(1); dealershipInDatabase.Inventory.Should().ContainSingle(car => car.Id == existingCar.Id); @@ -446,6 +440,7 @@ public async Task Can_replace_OneToMany_relationship() } } }; + var existingCar = new Car { RegionId = 789, @@ -473,14 +468,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => type = "cars", id = "789:EE-FF-33" } - } }; - var route = $"/dealerships/{existingDealership.StringId}/relationships/inventory"; + string route = $"/dealerships/{existingDealership.StringId}/relationships/inventory"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -489,9 +483,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var dealershipInDatabase = await dbContext.Dealerships - .Include(dealership => dealership.Inventory) - .FirstWithIdOrDefaultAsync(existingDealership.Id); + Dealership dealershipInDatabase = await dbContext.Dealerships + .Include(dealership => dealership.Inventory).FirstWithIdOrDefaultAsync(existingDealership.Id); dealershipInDatabase.Inventory.Should().HaveCount(2); dealershipInDatabase.Inventory.Should().ContainSingle(car => car.Id == existingCar.Id); @@ -527,17 +520,17 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = $"/dealerships/{existingDealership.StringId}/relationships/inventory"; + string route = $"/dealerships/{existingDealership.StringId}/relationships/inventory"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("A related resource does not exist."); error.Detail.Should().Be("Related resource of type 'cars' with ID '999:XX-YY-22' in relationship 'inventory' does not exist."); @@ -560,10 +553,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/cars/" + existingCar.StringId; + string route = "/cars/" + existingCar.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteDeleteAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -572,8 +565,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var carInDatabase = await dbContext.Cars - .FirstOrDefaultAsync(car => car.RegionId == existingCar.RegionId && car.LicensePlate == existingCar.LicensePlate); + Car carInDatabase = + await dbContext.Cars.FirstOrDefaultAsync(car => car.RegionId == existingCar.RegionId && car.LicensePlate == existingCar.LicensePlate); carInDatabase.Should().BeNull(); }); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Dealership.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Dealership.cs index 8ac8d4e50d..aaa1449254 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Dealership.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Dealership.cs @@ -11,7 +11,7 @@ public sealed class Dealership : Identifiable [Attr] public string Address { get; set; } - [HasMany] + [HasMany] public ISet Inventory { get; set; } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/DealershipsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/DealershipsController.cs index 53b4f281e1..7301033afd 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/DealershipsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/DealershipsController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CompositeKeys { public sealed class DealershipsController : JsonApiController { - public DealershipsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public DealershipsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Engine.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Engine.cs index b58c3b53ec..8ccbada031 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Engine.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/Engine.cs @@ -10,7 +10,7 @@ public sealed class Engine : Identifiable [Attr] public string SerialCode { get; set; } - [HasOne] + [HasOne] public Car Car { get; set; } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/EnginesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/EnginesController.cs index 4833292cd8..b4371cd63d 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/EnginesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/EnginesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CompositeKeys { public sealed class EnginesController : JsonApiController { - public EnginesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public EnginesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs index a892129ddc..308ef876f2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using FluentAssertions; @@ -10,8 +11,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ContentNegotiation { - public sealed class AcceptHeaderTests - : IClassFixture, PolicyDbContext>> + public sealed class AcceptHeaderTests : IClassFixture, PolicyDbContext>> { private readonly ExampleIntegrationTestContext, PolicyDbContext> _testContext; @@ -31,7 +31,7 @@ public async Task Permits_no_Accept_headers() var acceptHeaders = new MediaTypeWithQualityHeaderValue[0]; // Act - var (httpResponse, _) = await _testContext.ExecuteGetAsync(route, acceptHeaders); + (HttpResponseMessage httpResponse, Document _) = await _testContext.ExecuteGetAsync(route, acceptHeaders); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -66,7 +66,8 @@ public async Task Permits_no_Accept_headers_at_operations_endpoint() var acceptHeaders = new MediaTypeWithQualityHeaderValue[0]; // Act - var (httpResponse, _) = await _testContext.ExecutePostAsync(route, requestBody, contentType, acceptHeaders); + (HttpResponseMessage httpResponse, ErrorDocument _) = + await _testContext.ExecutePostAsync(route, requestBody, contentType, acceptHeaders); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -78,14 +79,14 @@ public async Task Permits_global_wildcard_in_Accept_headers() // Arrange const string route = "/policies"; - var acceptHeaders = new[] + MediaTypeWithQualityHeaderValue[] acceptHeaders = { MediaTypeWithQualityHeaderValue.Parse("text/html"), MediaTypeWithQualityHeaderValue.Parse("*/*") }; // Act - var (httpResponse, _) = await _testContext.ExecuteGetAsync(route, acceptHeaders); + (HttpResponseMessage httpResponse, Document _) = await _testContext.ExecuteGetAsync(route, acceptHeaders); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -97,14 +98,14 @@ public async Task Permits_application_wildcard_in_Accept_headers() // Arrange const string route = "/policies"; - var acceptHeaders = new[] + MediaTypeWithQualityHeaderValue[] acceptHeaders = { MediaTypeWithQualityHeaderValue.Parse("text/html;q=0.8"), MediaTypeWithQualityHeaderValue.Parse("application/*;q=0.2") }; // Act - var (httpResponse, _) = await _testContext.ExecuteGetAsync(route, acceptHeaders); + (HttpResponseMessage httpResponse, Document _) = await _testContext.ExecuteGetAsync(route, acceptHeaders); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -116,7 +117,7 @@ public async Task Permits_JsonApi_without_parameters_in_Accept_headers() // Arrange const string route = "/policies"; - var acceptHeaders = new[] + MediaTypeWithQualityHeaderValue[] acceptHeaders = { MediaTypeWithQualityHeaderValue.Parse("text/html"), MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType + "; profile=some"), @@ -126,7 +127,7 @@ public async Task Permits_JsonApi_without_parameters_in_Accept_headers() }; // Act - var (httpResponse, _) = await _testContext.ExecuteGetAsync(route, acceptHeaders); + (HttpResponseMessage httpResponse, Document _) = await _testContext.ExecuteGetAsync(route, acceptHeaders); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -158,17 +159,18 @@ public async Task Permits_JsonApi_with_AtomicOperations_extension_in_Accept_head const string route = "/operations"; const string contentType = HeaderConstants.AtomicOperationsMediaType; - var acceptHeaders = new[] + MediaTypeWithQualityHeaderValue[] acceptHeaders = { MediaTypeWithQualityHeaderValue.Parse("text/html"), MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType + "; profile=some"), MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType), MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType + "; unknown=unexpected"), - MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType+";ext=\"https://jsonapi.org/ext/atomic\"; q=0.2") + MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType + ";ext=\"https://jsonapi.org/ext/atomic\"; q=0.2") }; // Act - var (httpResponse, _) = await _testContext.ExecutePostAsync(route, requestBody, contentType, acceptHeaders); + (HttpResponseMessage httpResponse, ErrorDocument _) = + await _testContext.ExecutePostAsync(route, requestBody, contentType, acceptHeaders); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -180,7 +182,7 @@ public async Task Denies_JsonApi_with_parameters_in_Accept_headers() // Arrange const string route = "/policies"; - var acceptHeaders = new[] + MediaTypeWithQualityHeaderValue[] acceptHeaders = { MediaTypeWithQualityHeaderValue.Parse("text/html"), MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType + "; profile=some"), @@ -190,14 +192,14 @@ public async Task Denies_JsonApi_with_parameters_in_Accept_headers() }; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route, acceptHeaders); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route, acceptHeaders); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotAcceptable); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotAcceptable); error.Title.Should().Be("The specified Accept header value does not contain any supported media types."); error.Detail.Should().Be("Please include 'application/vnd.api+json' in the Accept header values."); @@ -229,20 +231,21 @@ public async Task Denies_JsonApi_in_Accept_headers_at_operations_endpoint() const string route = "/operations"; const string contentType = HeaderConstants.AtomicOperationsMediaType; - var acceptHeaders = new[] + MediaTypeWithQualityHeaderValue[] acceptHeaders = { MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType) }; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody, contentType, acceptHeaders); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = + await _testContext.ExecutePostAsync(route, requestBody, contentType, acceptHeaders); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotAcceptable); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotAcceptable); error.Title.Should().Be("The specified Accept header value does not contain any supported media types."); error.Detail.Should().Be("Please include 'application/vnd.api+json; ext=\"https://jsonapi.org/ext/atomic\"' in the Accept header values."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs index f32001e4a1..c06dd3c891 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Middleware; @@ -9,8 +10,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ContentNegotiation { - public sealed class ContentTypeHeaderTests - : IClassFixture, PolicyDbContext>> + public sealed class ContentTypeHeaderTests : IClassFixture, PolicyDbContext>> { private readonly ExampleIntegrationTestContext, PolicyDbContext> _testContext; @@ -28,7 +28,7 @@ public async Task Returns_JsonApi_ContentType_header() const string route = "/policies"; // Act - var (httpResponse, _) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document _) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -61,7 +61,7 @@ public async Task Returns_JsonApi_ContentType_header_with_AtomicOperations_exten const string route = "/operations"; // Act - var (httpResponse, _) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document _) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -88,14 +88,15 @@ public async Task Denies_unknown_ContentType_header() const string contentType = "text/html"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = + await _testContext.ExecutePostAsync(route, requestBody, contentType); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); error.Title.Should().Be("The specified Content-Type header value is not supported."); error.Detail.Should().Be("Please specify 'application/vnd.api+json' instead of 'text/html' for the Content-Type header value."); @@ -122,7 +123,7 @@ public async Task Permits_JsonApi_ContentType_header() // Act // ReSharper disable once RedundantArgumentDefaultValue - var (httpResponse, _) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + (HttpResponseMessage httpResponse, ErrorDocument _) = await _testContext.ExecutePostAsync(route, requestBody, contentType); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -155,7 +156,7 @@ public async Task Permits_JsonApi_ContentType_header_with_AtomicOperations_exten const string contentType = HeaderConstants.AtomicOperationsMediaType; // Act - var (httpResponse, _) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + (HttpResponseMessage httpResponse, ErrorDocument _) = await _testContext.ExecutePostAsync(route, requestBody, contentType); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -181,14 +182,15 @@ public async Task Denies_JsonApi_ContentType_header_with_profile() const string contentType = HeaderConstants.MediaType + "; profile=something"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = + await _testContext.ExecutePostAsync(route, requestBody, contentType); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); error.Title.Should().Be("The specified Content-Type header value is not supported."); error.Detail.Should().Be($"Please specify 'application/vnd.api+json' instead of '{contentType}' for the Content-Type header value."); @@ -214,14 +216,15 @@ public async Task Denies_JsonApi_ContentType_header_with_extension() const string contentType = HeaderConstants.MediaType + "; ext=something"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = + await _testContext.ExecutePostAsync(route, requestBody, contentType); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); error.Title.Should().Be("The specified Content-Type header value is not supported."); error.Detail.Should().Be($"Please specify 'application/vnd.api+json' instead of '{contentType}' for the Content-Type header value."); @@ -247,14 +250,15 @@ public async Task Denies_JsonApi_ContentType_header_with_AtomicOperations_extens const string contentType = HeaderConstants.AtomicOperationsMediaType; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = + await _testContext.ExecutePostAsync(route, requestBody, contentType); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); error.Title.Should().Be("The specified Content-Type header value is not supported."); error.Detail.Should().Be($"Please specify 'application/vnd.api+json' instead of '{contentType}' for the Content-Type header value."); @@ -280,14 +284,15 @@ public async Task Denies_JsonApi_ContentType_header_with_CharSet() const string contentType = HeaderConstants.MediaType + "; charset=ISO-8859-4"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = + await _testContext.ExecutePostAsync(route, requestBody, contentType); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); error.Title.Should().Be("The specified Content-Type header value is not supported."); error.Detail.Should().Be($"Please specify 'application/vnd.api+json' instead of '{contentType}' for the Content-Type header value."); @@ -313,14 +318,15 @@ public async Task Denies_JsonApi_ContentType_header_with_unknown_parameter() const string contentType = HeaderConstants.MediaType + "; unknown=unexpected"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = + await _testContext.ExecutePostAsync(route, requestBody, contentType); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); error.Title.Should().Be("The specified Content-Type header value is not supported."); error.Detail.Should().Be($"Please specify 'application/vnd.api+json' instead of '{contentType}' for the Content-Type header value."); @@ -354,7 +360,8 @@ public async Task Denies_JsonApi_ContentType_header_at_operations_endpoint() // Act // ReSharper disable once RedundantArgumentDefaultValue - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = + await _testContext.ExecutePostAsync(route, requestBody, contentType); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType); @@ -363,7 +370,7 @@ public async Task Denies_JsonApi_ContentType_header_at_operations_endpoint() string detail = $"Please specify '{HeaderConstants.AtomicOperationsMediaType}' instead of '{contentType}' for the Content-Type header value."; - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); error.Title.Should().Be("The specified Content-Type header value is not supported."); error.Detail.Should().Be(detail); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/PoliciesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/PoliciesController.cs index d99ab9bd6a..5cf5119f08 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/PoliciesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/PoliciesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ContentNegotiation { public sealed class PoliciesController : JsonApiController { - public PoliciesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public PoliciesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/ActionResultTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/ActionResultTests.cs index daffab38a9..dc1d6e3670 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/ActionResultTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/ActionResultTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -8,8 +9,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ControllerActionResults { - public sealed class ActionResultTests - : IClassFixture, ActionResultDbContext>> + public sealed class ActionResultTests : IClassFixture, ActionResultDbContext>> { private readonly ExampleIntegrationTestContext, ActionResultDbContext> _testContext; @@ -30,10 +30,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/toothbrushes/" + toothbrush.StringId; + string route = "/toothbrushes/" + toothbrush.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -46,17 +46,17 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Converts_empty_ActionResult_to_error_collection() { // Arrange - var route = "/toothbrushes/" + BaseToothbrushesController.EmptyActionResultId; + string route = "/toothbrushes/" + BaseToothbrushesController.EmptyActionResultId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("NotFound"); error.Detail.Should().BeNull(); @@ -66,17 +66,17 @@ public async Task Converts_empty_ActionResult_to_error_collection() public async Task Converts_ActionResult_with_error_object_to_error_collection() { // Arrange - var route = "/toothbrushes/" + BaseToothbrushesController.ActionResultWithErrorObjectId; + string route = "/toothbrushes/" + BaseToothbrushesController.ActionResultWithErrorObjectId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("No toothbrush with that ID exists."); error.Detail.Should().BeNull(); @@ -86,17 +86,17 @@ public async Task Converts_ActionResult_with_error_object_to_error_collection() public async Task Cannot_convert_ActionResult_with_string_parameter_to_error_collection() { // Arrange - var route = "/toothbrushes/" + BaseToothbrushesController.ActionResultWithStringParameter; + string route = "/toothbrushes/" + BaseToothbrushesController.ActionResultWithStringParameter; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.InternalServerError); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.InternalServerError); error.Title.Should().Be("An unhandled error occurred while processing this request."); error.Detail.Should().Be("Data being returned must be errors or resources."); @@ -106,17 +106,17 @@ public async Task Cannot_convert_ActionResult_with_string_parameter_to_error_col public async Task Converts_ObjectResult_with_error_object_to_error_collection() { // Arrange - var route = "/toothbrushes/" + BaseToothbrushesController.ObjectResultWithErrorObjectId; + string route = "/toothbrushes/" + BaseToothbrushesController.ObjectResultWithErrorObjectId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadGateway); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadGateway); error.Title.Should().BeNull(); error.Detail.Should().BeNull(); @@ -126,27 +126,27 @@ public async Task Converts_ObjectResult_with_error_object_to_error_collection() public async Task Converts_ObjectResult_with_error_objects_to_error_collection() { // Arrange - var route = "/toothbrushes/" + BaseToothbrushesController.ObjectResultWithErrorCollectionId; + string route = "/toothbrushes/" + BaseToothbrushesController.ObjectResultWithErrorCollectionId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(3); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.PreconditionFailed); error1.Title.Should().BeNull(); error1.Detail.Should().BeNull(); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.Unauthorized); error2.Title.Should().BeNull(); error2.Detail.Should().BeNull(); - var error3 = responseDocument.Errors[2]; + Error error3 = responseDocument.Errors[2]; error3.StatusCode.Should().Be(HttpStatusCode.ExpectationFailed); error3.Title.Should().Be("This is not a very great request."); error3.Detail.Should().BeNull(); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/BaseToothbrushesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/BaseToothbrushesController.cs index e8d6f252f4..00fb4ac4dc 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/BaseToothbrushesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/BaseToothbrushesController.cs @@ -18,8 +18,7 @@ public abstract class BaseToothbrushesController : BaseJsonApiController resourceService) + protected BaseToothbrushesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } @@ -60,6 +59,7 @@ public override async Task GetAsync(int id, CancellationToken can Title = "This is not a very great request." } }; + return Error(errors); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/ToothbrushesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/ToothbrushesController.cs index 8cd4e1d646..a16b083db4 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/ToothbrushesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ControllerActionResults/ToothbrushesController.cs @@ -9,8 +9,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ControllerActionResults { public sealed class ToothbrushesController : BaseToothbrushesController { - public ToothbrushesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public ToothbrushesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/ApiControllerAttributeTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/ApiControllerAttributeTests.cs index 494a9b66e0..8bf3ab0a72 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/ApiControllerAttributeTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/ApiControllerAttributeTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -8,8 +9,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CustomRoutes { - public sealed class ApiControllerAttributeTests - : IClassFixture, CustomRouteDbContext>> + public sealed class ApiControllerAttributeTests : IClassFixture, CustomRouteDbContext>> { private readonly ExampleIntegrationTestContext, CustomRouteDbContext> _testContext; @@ -25,14 +25,14 @@ public async Task ApiController_attribute_transforms_NotFound_action_result_with const string route = "/world-civilians/missing"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.Links.About.Should().Be("https://tools.ietf.org/html/rfc7231#section-6.5.4"); } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CiviliansController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CiviliansController.cs index e8a730e15c..7a0ef9bf3b 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CiviliansController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CiviliansController.cs @@ -13,8 +13,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CustomRoutes [Route("world-civilians")] public sealed class CiviliansController : JsonApiController { - public CiviliansController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public CiviliansController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs index aaebbce763..7bee8663e2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs @@ -1,5 +1,7 @@ +using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -9,8 +11,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CustomRoutes { - public sealed class CustomRouteTests - : IClassFixture, CustomRouteDbContext>> + public sealed class CustomRouteTests : IClassFixture, CustomRouteDbContext>> { private const string HostPrefix = "http://localhost"; @@ -26,7 +27,7 @@ public CustomRouteTests(ExampleIntegrationTestContext { @@ -34,10 +35,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/world-api/civilization/popular/towns/" + town.StringId; + string route = "/world-api/civilization/popular/towns/" + town.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -58,7 +59,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_resources_at_custom_action_method() { // Arrange - var town = _fakers.Town.Generate(7); + List town = _fakers.Town.Generate(7); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -70,7 +71,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/world-api/civilization/popular/towns/largest-5"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/Town.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/Town.cs index ba0ba27fe2..1242def3a3 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/Town.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/Town.cs @@ -13,7 +13,7 @@ public sealed class Town : Identifiable [Attr] public double Latitude { get; set; } - + [Attr] public double Longitude { get; set; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/TownsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/TownsController.cs index a39b9c96c9..4f65adaaf2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/TownsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/TownsController.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -17,8 +18,7 @@ public sealed class TownsController : JsonApiController { private readonly CustomRouteDbContext _dbContext; - public TownsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService, CustomRouteDbContext dbContext) + public TownsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService, CustomRouteDbContext dbContext) : base(options, loggerFactory, resourceService) { _dbContext = dbContext; @@ -27,11 +27,9 @@ public TownsController(IJsonApiOptions options, ILoggerFactory loggerFactory, [HttpGet("largest-{count}")] public async Task GetLargestTownsAsync(int count, CancellationToken cancellationToken) { - var query = _dbContext.Towns - .OrderByDescending(town => town.Civilians.Count) - .Take(count); + IQueryable query = _dbContext.Towns.OrderByDescending(town => town.Civilians.Count).Take(count); - var results = await query.ToListAsync(cancellationToken); + List results = await query.ToListAsync(cancellationToken); return Ok(results); } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/Building.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/Building.cs index 79af5eac30..48334083aa 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/Building.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/Building.cs @@ -47,7 +47,7 @@ public string PrimaryDoorColor [EagerLoad] public Door PrimaryDoor { get; set; } - + [EagerLoad] public Door SecondaryDoor { get; set; } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingRepository.cs index 78501d46b2..8bf5086e06 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingRepository.cs @@ -13,16 +13,15 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.EagerLoading [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class BuildingRepository : EntityFrameworkCoreRepository { - public BuildingRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, - IResourceGraph resourceGraph, IResourceFactory resourceFactory, - IEnumerable constraintProviders, ILoggerFactory loggerFactory) + public BuildingRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) { } public override async Task GetForCreateAsync(int id, CancellationToken cancellationToken) { - var building = await base.GetForCreateAsync(id, cancellationToken); + Building building = await base.GetForCreateAsync(id, cancellationToken); // Must ensure that an instance exists for this required relationship, so that POST succeeds. building.PrimaryDoor = new Door(); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingsController.cs index 4a0b9bb366..be28a84035 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingsController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.EagerLoading { public sealed class BuildingsController : JsonApiController { - public BuildingsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public BuildingsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/EagerLoadingTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/EagerLoadingTests.cs index 2acabc76c4..862c6d12fd 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/EagerLoadingTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/EagerLoadingTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -10,8 +11,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.EagerLoading { - public sealed class EagerLoadingTests - : IClassFixture, EagerLoadingDbContext>> + public sealed class EagerLoadingTests : IClassFixture, EagerLoadingDbContext>> { private readonly ExampleIntegrationTestContext, EagerLoadingDbContext> _testContext; private readonly EagerLoadingFakers _fakers = new EagerLoadingFakers(); @@ -30,7 +30,7 @@ public EagerLoadingTests(ExampleIntegrationTestContext await dbContext.SaveChangesAsync(); }); - var route = "/buildings/" + building.StringId; + string route = "/buildings/" + building.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -61,7 +61,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_primary_resource_with_nested_eager_loads() { // Arrange - var street = _fakers.Street.Generate(); + Street street = _fakers.Street.Generate(); street.Buildings = _fakers.Building.Generate(2); street.Buildings[0].Windows = _fakers.Window.Generate(2); @@ -77,10 +77,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/streets/" + street.StringId; + string route = "/streets/" + street.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -97,7 +97,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_primary_resource_with_fieldset() { // Arrange - var street = _fakers.Street.Generate(); + Street street = _fakers.Street.Generate(); street.Buildings = _fakers.Building.Generate(1); street.Buildings[0].Windows = _fakers.Window.Generate(3); street.Buildings[0].PrimaryDoor = _fakers.Door.Generate(); @@ -108,10 +108,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/streets/{street.StringId}?fields[streets]=windowTotalCount"; + string route = $"/streets/{street.StringId}?fields[streets]=windowTotalCount"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -127,7 +127,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_primary_resource_with_includes() { // Arrange - var state = _fakers.State.Generate(); + State state = _fakers.State.Generate(); state.Cities = _fakers.City.Generate(1); state.Cities[0].Streets = _fakers.Street.Generate(1); state.Cities[0].Streets[0].Buildings = _fakers.Building.Generate(1); @@ -140,10 +140,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/states/{state.StringId}?include=cities.streets"; + string route = $"/states/{state.StringId}?include=cities.streets"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -169,7 +169,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_secondary_resources_with_include_and_fieldsets() { // Arrange - var state = _fakers.State.Generate(); + State state = _fakers.State.Generate(); state.Cities = _fakers.City.Generate(1); state.Cities[0].Streets = _fakers.Street.Generate(1); state.Cities[0].Streets[0].Buildings = _fakers.Building.Generate(1); @@ -183,10 +183,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/states/{state.StringId}/cities?include=streets&fields[cities]=name&fields[streets]=doorTotalCount,windowTotalCount"; + string route = $"/states/{state.StringId}/cities?include=streets&fields[cities]=name&fields[streets]=doorTotalCount,windowTotalCount"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -210,7 +210,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource() { // Arrange - var newBuilding = _fakers.Building.Generate(); + Building newBuilding = _fakers.Building.Generate(); var requestBody = new { @@ -227,7 +227,7 @@ public async Task Can_create_resource() const string route = "/buildings"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -238,14 +238,14 @@ public async Task Can_create_resource() responseDocument.SingleData.Attributes["primaryDoorColor"].Should().BeNull(); responseDocument.SingleData.Attributes["secondaryDoorColor"].Should().BeNull(); - var newBuildingId = int.Parse(responseDocument.SingleData.Id); + int newBuildingId = int.Parse(responseDocument.SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var buildingInDatabase = await dbContext.Buildings + Building buildingInDatabase = await dbContext.Buildings .Include(building => building.PrimaryDoor) .Include(building => building.SecondaryDoor) .Include(building => building.Windows) @@ -266,13 +266,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource() { // Arrange - var existingBuilding = _fakers.Building.Generate(); + Building existingBuilding = _fakers.Building.Generate(); existingBuilding.PrimaryDoor = _fakers.Door.Generate(); existingBuilding.SecondaryDoor = _fakers.Door.Generate(); existingBuilding.Windows = _fakers.Window.Generate(2); - var newBuildingNumber = _fakers.Building.Generate().Number; - var newPrimaryDoorColor = _fakers.Door.Generate().Color; + string newBuildingNumber = _fakers.Building.Generate().Number; + string newPrimaryDoorColor = _fakers.Door.Generate().Color; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -294,10 +294,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = "/buildings/" + existingBuilding.StringId; + string route = "/buildings/" + existingBuilding.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -309,7 +309,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - var buildingInDatabase = await dbContext.Buildings + Building buildingInDatabase = await dbContext.Buildings .Include(building => building.PrimaryDoor) .Include(building => building.SecondaryDoor) .Include(building => building.Windows) @@ -331,7 +331,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_delete_resource() { // Arrange - var existingBuilding = _fakers.Building.Generate(); + Building existingBuilding = _fakers.Building.Generate(); existingBuilding.PrimaryDoor = _fakers.Door.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -340,10 +340,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/buildings/" + existingBuilding.StringId; + string route = "/buildings/" + existingBuilding.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteDeleteAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -352,7 +352,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var buildingInDatabase = await dbContext.Buildings.FirstWithIdOrDefaultAsync(existingBuilding.Id); + Building buildingInDatabase = await dbContext.Buildings.FirstWithIdOrDefaultAsync(existingBuilding.Id); buildingInDatabase.Should().BeNull(); }); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StatesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StatesController.cs index 28c8b795b8..bbaf58cfb2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StatesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StatesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.EagerLoading { public sealed class StatesController : JsonApiController { - public StatesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public StatesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StreetsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StreetsController.cs index e6b4eda7e1..04b4275495 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StreetsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StreetsController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.EagerLoading { public sealed class StreetsController : JsonApiController { - public StreetsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public StreetsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs index 7013299c6a..2b63cf8a1e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs @@ -16,22 +16,19 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ExceptionHandling [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class ConsumerArticleService : JsonApiResourceService { - internal const string UnavailableArticlePrefix = "X"; - private const string SupportEmailAddress = "company@email.com"; + internal const string UnavailableArticlePrefix = "X"; public ConsumerArticleService(IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, - IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, - IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, - IResourceHookExecutorFacade hookExecutor) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, - resourceChangeTracker, hookExecutor) + IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, + IResourceChangeTracker resourceChangeTracker, IResourceHookExecutorFacade hookExecutor) + : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, hookExecutor) { } public override async Task GetAsync(int id, CancellationToken cancellationToken) { - var consumerArticle = await base.GetAsync(id, cancellationToken); + ConsumerArticle consumerArticle = await base.GetAsync(id, cancellationToken); if (consumerArticle.Code.StartsWith(UnavailableArticlePrefix, StringComparison.Ordinal)) { diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticlesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticlesController.cs index dcc0ad7e8e..7b36586706 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticlesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticlesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ExceptionHandling { public sealed class ConsumerArticlesController : JsonApiController { - public ConsumerArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public ConsumerArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ExceptionHandlerTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ExceptionHandlerTests.cs index b86bd84787..93801ce51c 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ExceptionHandlerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ExceptionHandlerTests.cs @@ -1,5 +1,7 @@ +using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -14,8 +16,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ExceptionHandling { - public sealed class ExceptionHandlerTests - : IClassFixture, ErrorDbContext>> + public sealed class ExceptionHandlerTests : IClassFixture, ErrorDbContext>> { private readonly ExampleIntegrationTestContext, ErrorDbContext> _testContext; @@ -67,17 +68,17 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/consumerArticles/" + consumerArticle.StringId; + string route = "/consumerArticles/" + consumerArticle.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Gone); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.Gone); error.Title.Should().Be("The requested article is no longer available."); error.Detail.Should().Be("Article with code 'X123' is no longer available."); @@ -103,22 +104,22 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/throwingArticles/" + throwingArticle.StringId; + string route = "/throwingArticles/" + throwingArticle.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.InternalServerError); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.InternalServerError); error.Title.Should().Be("An unhandled error occurred while processing this request."); error.Detail.Should().Be("Exception has been thrown by the target of an invocation."); - var stackTraceLines = ((JArray) error.Meta.Data["stackTrace"]).Select(token => token.Value()); + IEnumerable stackTraceLines = ((JArray)error.Meta.Data["stackTrace"]).Select(token => token.Value()); stackTraceLines.Should().ContainMatch("* System.InvalidOperationException: Article status could not be determined.*"); loggerFactory.Logger.Messages.Should().HaveCount(1); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ThrowingArticlesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ThrowingArticlesController.cs index 6616498f85..f2e6def6ed 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ThrowingArticlesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ThrowingArticlesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ExceptionHandling { public sealed class ThrowingArticlesController : JsonApiController { - public ThrowingArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public ThrowingArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/ArtGalleriesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/ArtGalleriesController.cs index d7383df1eb..4ed2ccb73e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/ArtGalleriesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/ArtGalleriesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS { public sealed class ArtGalleriesController : JsonApiController { - public ArtGalleriesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public ArtGalleriesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingTests.cs index e3e35b7f72..954b59fa5f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingTests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -8,8 +9,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS { - public sealed class HostingTests - : IClassFixture, HostingDbContext>> + public sealed class HostingTests : IClassFixture, HostingDbContext>> { private const string HostPrefix = "http://localhost"; @@ -25,7 +25,7 @@ public HostingTests(ExampleIntegrationTestContext @@ -38,7 +38,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/iis-application-virtual-directory/public-api/artGalleries?include=paintings"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -71,7 +71,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_primary_resources_with_include_on_custom_route_returns_links() { // Arrange - var painting = _fakers.Painting.Generate(); + Painting painting = _fakers.Painting.Generate(); painting.ExposedAt = _fakers.ArtGallery.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -84,7 +84,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/iis-application-virtual-directory/custom/path/to/paintings?include=exposedAt"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/PaintingsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/PaintingsController.cs index ced2427169..5a6a51da2f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/PaintingsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/PaintingsController.cs @@ -7,11 +7,11 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS { - [DisableRoutingConvention, Route("custom/path/to/paintings")] + [DisableRoutingConvention] + [Route("custom/path/to/paintings")] public sealed class PaintingsController : JsonApiController { - public PaintingsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public PaintingsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/BankAccountsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/BankAccountsController.cs index 91793dfc8c..c7751fde1a 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/BankAccountsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/BankAccountsController.cs @@ -6,8 +6,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation { public sealed class BankAccountsController : ObfuscatedIdentifiableController { - public BankAccountsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public BankAccountsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCardsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCardsController.cs index b72cea109e..d17fb2b016 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCardsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCardsController.cs @@ -6,8 +6,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation { public sealed class DebitCardsController : ObfuscatedIdentifiableController { - public DebitCardsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public DebitCardsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/HexadecimalCodec.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/HexadecimalCodec.cs index cc563b5f4b..19dfbea6b0 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/HexadecimalCodec.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/HexadecimalCodec.cs @@ -32,15 +32,16 @@ public static int Decode(string value) private static string FromHexString(string hexString) { - List bytes = new List(hexString.Length / 2); + var bytes = new List(hexString.Length / 2); + for (int index = 0; index < hexString.Length; index += 2) { - var hexChar = hexString.Substring(index, 2); + string hexChar = hexString.Substring(index, 2); byte bt = byte.Parse(hexChar, NumberStyles.HexNumber); bytes.Add(bt); } - var chars = Encoding.ASCII.GetChars(bytes.ToArray()); + char[] chars = Encoding.ASCII.GetChars(bytes.ToArray()); return new string(chars); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs index 71e130bb4b..99da5dacb9 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -9,8 +11,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation { - public sealed class IdObfuscationTests - : IClassFixture, ObfuscationDbContext>> + public sealed class IdObfuscationTests : IClassFixture, ObfuscationDbContext>> { private readonly ExampleIntegrationTestContext, ObfuscationDbContext> _testContext; private readonly ObfuscationFakers _fakers = new ObfuscationFakers(); @@ -24,7 +25,7 @@ public IdObfuscationTests(ExampleIntegrationTestContext accounts = _fakers.BankAccount.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -33,10 +34,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/bankAccounts?filter=equals(id,'{accounts[1].StringId}')"; + string route = $"/bankAccounts?filter=equals(id,'{accounts[1].StringId}')"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -49,7 +50,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_filter_any_in_primary_resources() { // Arrange - var accounts = _fakers.BankAccount.Generate(2); + List accounts = _fakers.BankAccount.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -58,10 +59,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/bankAccounts?filter=any(id,'{accounts[1].StringId}','{HexadecimalCodec.Encode(99999999)}')"; + string route = $"/bankAccounts?filter=any(id,'{accounts[1].StringId}','{HexadecimalCodec.Encode(99999999)}')"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -77,14 +78,14 @@ public async Task Cannot_get_primary_resource_for_invalid_ID() const string route = "/bankAccounts/not-a-hex-value"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Invalid ID value."); error.Detail.Should().Be("The value 'not-a-hex-value' is not a valid hexadecimal value."); @@ -94,7 +95,7 @@ public async Task Cannot_get_primary_resource_for_invalid_ID() public async Task Can_get_primary_resource_by_ID() { // Arrange - var card = _fakers.DebitCard.Generate(); + DebitCard card = _fakers.DebitCard.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -102,10 +103,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/debitCards/" + card.StringId; + string route = "/debitCards/" + card.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -118,7 +119,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_secondary_resources() { // Arrange - var account = _fakers.BankAccount.Generate(); + BankAccount account = _fakers.BankAccount.Generate(); account.Cards = _fakers.DebitCard.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -127,10 +128,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/bankAccounts/{account.StringId}/cards"; + string route = $"/bankAccounts/{account.StringId}/cards"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -144,7 +145,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_include_resource_with_sparse_fieldset() { // Arrange - var account = _fakers.BankAccount.Generate(); + BankAccount account = _fakers.BankAccount.Generate(); account.Cards = _fakers.DebitCard.Generate(1); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -153,10 +154,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/bankAccounts/{account.StringId}?include=cards&fields[debitCards]=ownerName"; + string route = $"/bankAccounts/{account.StringId}?include=cards&fields[debitCards]=ownerName"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -174,7 +175,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_relationship() { // Arrange - var account = _fakers.BankAccount.Generate(); + BankAccount account = _fakers.BankAccount.Generate(); account.Cards = _fakers.DebitCard.Generate(1); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -183,10 +184,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/bankAccounts/{account.StringId}/relationships/cards"; + string route = $"/bankAccounts/{account.StringId}/relationships/cards"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -199,8 +200,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource_with_relationship() { // Arrange - var existingAccount = _fakers.BankAccount.Generate(); - var newCard = _fakers.DebitCard.Generate(); + BankAccount existingAccount = _fakers.BankAccount.Generate(); + DebitCard newCard = _fakers.DebitCard.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -235,7 +236,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/debitCards"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -243,13 +244,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.SingleData.Attributes["ownerName"].Should().Be(newCard.OwnerName); responseDocument.SingleData.Attributes["pinCode"].Should().Be(newCard.PinCode); - var newCardId = HexadecimalCodec.Decode(responseDocument.SingleData.Id); + int newCardId = HexadecimalCodec.Decode(responseDocument.SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { - var cardInDatabase = await dbContext.DebitCards - .Include(card => card.Account) - .FirstWithIdAsync(newCardId); + DebitCard cardInDatabase = await dbContext.DebitCards.Include(card => card.Account).FirstWithIdAsync(newCardId); cardInDatabase.OwnerName.Should().Be(newCard.OwnerName); cardInDatabase.PinCode.Should().Be(newCard.PinCode); @@ -264,12 +263,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_relationship() { // Arrange - var existingAccount = _fakers.BankAccount.Generate(); + BankAccount existingAccount = _fakers.BankAccount.Generate(); existingAccount.Cards = _fakers.DebitCard.Generate(1); - var existingCard = _fakers.DebitCard.Generate(); + DebitCard existingCard = _fakers.DebitCard.Generate(); - var newIban = _fakers.BankAccount.Generate().Iban; + string newIban = _fakers.BankAccount.Generate().Iban; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -303,11 +302,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } } }; - - var route = "/bankAccounts/" + existingAccount.StringId; + + string route = "/bankAccounts/" + existingAccount.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -316,9 +315,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var accountInDatabase = await dbContext.BankAccounts - .Include(account => account.Cards) - .FirstWithIdAsync(existingAccount.Id); + BankAccount accountInDatabase = await dbContext.BankAccounts.Include(account => account.Cards).FirstWithIdAsync(existingAccount.Id); accountInDatabase.Iban.Should().Be(newIban); @@ -326,17 +323,16 @@ await _testContext.RunOnDatabaseAsync(async dbContext => accountInDatabase.Cards[0].Id.Should().Be(existingCard.Id); accountInDatabase.Cards[0].StringId.Should().Be(existingCard.StringId); }); - } [Fact] public async Task Can_add_to_ToMany_relationship() { // Arrange - var existingAccount = _fakers.BankAccount.Generate(); + BankAccount existingAccount = _fakers.BankAccount.Generate(); existingAccount.Cards = _fakers.DebitCard.Generate(1); - var existingDebitCard = _fakers.DebitCard.Generate(); + DebitCard existingDebitCard = _fakers.DebitCard.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -355,11 +351,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } } }; - - var route = $"/bankAccounts/{existingAccount.StringId}/relationships/cards"; + + string route = $"/bankAccounts/{existingAccount.StringId}/relationships/cards"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -368,9 +364,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var accountInDatabase = await dbContext.BankAccounts - .Include(account => account.Cards) - .FirstWithIdAsync(existingAccount.Id); + BankAccount accountInDatabase = await dbContext.BankAccounts.Include(account => account.Cards).FirstWithIdAsync(existingAccount.Id); accountInDatabase.Cards.Should().HaveCount(2); }); @@ -380,7 +374,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_remove_from_ToMany_relationship() { // Arrange - var existingAccount = _fakers.BankAccount.Generate(); + BankAccount existingAccount = _fakers.BankAccount.Generate(); existingAccount.Cards = _fakers.DebitCard.Generate(2); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -400,11 +394,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } } }; - - var route = $"/bankAccounts/{existingAccount.StringId}/relationships/cards"; + + string route = $"/bankAccounts/{existingAccount.StringId}/relationships/cards"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -413,9 +407,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var accountInDatabase = await dbContext.BankAccounts - .Include(account => account.Cards) - .FirstWithIdAsync(existingAccount.Id); + BankAccount accountInDatabase = await dbContext.BankAccounts.Include(account => account.Cards).FirstWithIdAsync(existingAccount.Id); accountInDatabase.Cards.Should().HaveCount(1); }); @@ -425,7 +417,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_delete_resource() { // Arrange - var existingAccount = _fakers.BankAccount.Generate(); + BankAccount existingAccount = _fakers.BankAccount.Generate(); existingAccount.Cards = _fakers.DebitCard.Generate(1); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -434,10 +426,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/bankAccounts/" + existingAccount.StringId; + string route = "/bankAccounts/" + existingAccount.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteDeleteAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -446,9 +438,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var accountInDatabase = await dbContext.BankAccounts - .Include(account => account.Cards) - .FirstWithIdOrDefaultAsync(existingAccount.Id); + BankAccount accountInDatabase = await dbContext.BankAccounts.Include(account => account.Cards).FirstWithIdOrDefaultAsync(existingAccount.Id); accountInDatabase.Should().BeNull(); }); @@ -458,19 +448,19 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_delete_missing_resource() { // Arrange - var stringId = HexadecimalCodec.Encode(99999999); + string stringId = HexadecimalCodec.Encode(99999999); - var route = "/bankAccounts/" + stringId; + string route = "/bankAccounts/" + stringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecuteDeleteAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); error.Title.Should().Be("The requested resource does not exist."); error.Detail.Should().Be($"Resource of type 'bankAccounts' with ID '{stringId}' does not exist."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs index c762873886..ee6e3de278 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs @@ -13,8 +13,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation public abstract class ObfuscatedIdentifiableController : BaseJsonApiController where TResource : class, IIdentifiable { - protected ObfuscatedIdentifiableController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + protected ObfuscatedIdentifiableController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } @@ -53,8 +52,8 @@ public override Task PostAsync([FromBody] TResource resource, Can } [HttpPost("{id}/relationships/{relationshipName}")] - public Task PostRelationshipAsync(string id, string relationshipName, - [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + public Task PostRelationshipAsync(string id, string relationshipName, [FromBody] ISet secondaryResourceIds, + CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); return base.PostRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); @@ -68,8 +67,8 @@ public Task PatchAsync(string id, [FromBody] TResource resource, } [HttpPatch("{id}/relationships/{relationshipName}")] - public Task PatchRelationshipAsync(string id, string relationshipName, - [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) + public Task PatchRelationshipAsync(string id, string relationshipName, [FromBody] object secondaryResourceIds, + CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); return base.PatchRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); @@ -83,8 +82,8 @@ public Task DeleteAsync(string id, CancellationToken cancellation } [HttpDelete("{id}/relationships/{relationshipName}")] - public Task DeleteRelationshipAsync(string id, string relationshipName, - [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + public Task DeleteRelationshipAsync(string id, string relationshipName, [FromBody] ISet secondaryResourceIds, + CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); return base.DeleteRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/AbsoluteLinksWithNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/AbsoluteLinksWithNamespaceTests.cs index 36ca78da28..fdca0b77dd 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/AbsoluteLinksWithNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/AbsoluteLinksWithNamespaceTests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -29,7 +30,7 @@ public AbsoluteLinksWithNamespaceTests(ExampleIntegrationTestContext), typeof(NeverSameResourceChangeTracker<>)); }); - var options = (JsonApiOptions) testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.IncludeTotalResourceCount = true; } @@ -37,7 +38,7 @@ public AbsoluteLinksWithNamespaceTests(ExampleIntegrationTestContext { @@ -45,10 +46,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/api/photoAlbums/" + album.StringId; + string route = "/api/photoAlbums/" + album.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -70,7 +71,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_primary_resources_with_include_returns_absolute_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -83,7 +84,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/api/photoAlbums?include=photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -114,7 +115,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_secondary_resource_returns_absolute_links() { // Arrange - var photo = _fakers.Photo.Generate(); + Photo photo = _fakers.Photo.Generate(); photo.Album = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -123,10 +124,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/api/photos/{photo.StringId}/album"; + string route = $"/api/photos/{photo.StringId}/album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -150,7 +151,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_secondary_resources_returns_absolute_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -159,10 +160,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/api/photoAlbums/{album.StringId}/photos"; + string route = $"/api/photoAlbums/{album.StringId}/photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -186,7 +187,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_HasOne_relationship_returns_absolute_links() { // Arrange - var photo = _fakers.Photo.Generate(); + Photo photo = _fakers.Photo.Generate(); photo.Album = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -195,10 +196,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/api/photos/{photo.StringId}/relationships/album"; + string route = $"/api/photos/{photo.StringId}/relationships/album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -219,7 +220,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_HasMany_relationship_returns_absolute_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -228,10 +229,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/api/photoAlbums/{album.StringId}/relationships/photos"; + string route = $"/api/photoAlbums/{album.StringId}/relationships/photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -252,7 +253,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Create_resource_with_side_effects_and_include_returns_absolute_links() { // Arrange - var existingPhoto = _fakers.Photo.Generate(); + Photo existingPhoto = _fakers.Photo.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -285,7 +286,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/api/photoAlbums?include=photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -315,8 +316,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Update_resource_with_side_effects_and_include_returns_absolute_links() { // Arrange - var existingPhoto = _fakers.Photo.Generate(); - var existingAlbum = _fakers.PhotoAlbum.Generate(); + Photo existingPhoto = _fakers.Photo.Generate(); + PhotoAlbum existingAlbum = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -344,10 +345,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = $"/api/photos/{existingPhoto.StringId}?include=album"; + string route = $"/api/photos/{existingPhoto.StringId}?include=album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/AbsoluteLinksWithoutNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/AbsoluteLinksWithoutNamespaceTests.cs index 37faa2c92a..e625ae5c55 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/AbsoluteLinksWithoutNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/AbsoluteLinksWithoutNamespaceTests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -29,7 +30,7 @@ public AbsoluteLinksWithoutNamespaceTests(ExampleIntegrationTestContext), typeof(NeverSameResourceChangeTracker<>)); }); - var options = (JsonApiOptions) testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.IncludeTotalResourceCount = true; } @@ -37,7 +38,7 @@ public AbsoluteLinksWithoutNamespaceTests(ExampleIntegrationTestContext { @@ -45,10 +46,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/photoAlbums/" + album.StringId; + string route = "/photoAlbums/" + album.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -70,7 +71,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_primary_resources_with_include_returns_absolute_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -83,7 +84,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/photoAlbums?include=photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -114,7 +115,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_secondary_resource_returns_absolute_links() { // Arrange - var photo = _fakers.Photo.Generate(); + Photo photo = _fakers.Photo.Generate(); photo.Album = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -123,10 +124,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/photos/{photo.StringId}/album"; + string route = $"/photos/{photo.StringId}/album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -150,7 +151,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_secondary_resources_returns_absolute_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -159,10 +160,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/photoAlbums/{album.StringId}/photos"; + string route = $"/photoAlbums/{album.StringId}/photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -186,7 +187,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_HasOne_relationship_returns_absolute_links() { // Arrange - var photo = _fakers.Photo.Generate(); + Photo photo = _fakers.Photo.Generate(); photo.Album = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -195,10 +196,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/photos/{photo.StringId}/relationships/album"; + string route = $"/photos/{photo.StringId}/relationships/album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -219,7 +220,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_HasMany_relationship_returns_absolute_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -228,10 +229,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/photoAlbums/{album.StringId}/relationships/photos"; + string route = $"/photoAlbums/{album.StringId}/relationships/photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -252,7 +253,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Create_resource_with_side_effects_and_include_returns_absolute_links() { // Arrange - var existingPhoto = _fakers.Photo.Generate(); + Photo existingPhoto = _fakers.Photo.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -285,7 +286,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/photoAlbums?include=photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -315,8 +316,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Update_resource_with_side_effects_and_include_returns_absolute_links() { // Arrange - var existingPhoto = _fakers.Photo.Generate(); - var existingAlbum = _fakers.PhotoAlbum.Generate(); + Photo existingPhoto = _fakers.Photo.Generate(); + PhotoAlbum existingAlbum = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -344,10 +345,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = $"/photos/{existingPhoto.StringId}?include=album"; + string route = $"/photos/{existingPhoto.StringId}?include=album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinkInclusionTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinkInclusionTests.cs index 760a1ebb8c..b7838e77e1 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinkInclusionTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinkInclusionTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -8,8 +9,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Links { - public sealed class LinkInclusionTests - : IClassFixture, LinksDbContext>> + public sealed class LinkInclusionTests : IClassFixture, LinksDbContext>> { private readonly ExampleIntegrationTestContext, LinksDbContext> _testContext; private readonly LinksFakers _fakers = new LinksFakers(); @@ -23,7 +23,7 @@ public LinkInclusionTests(ExampleIntegrationTestContext await dbContext.SaveChangesAsync(); }); - var route = $"/photoLocations/{location.StringId}?include=photo,album"; + string route = $"/photoLocations/{location.StringId}?include=photo,album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoAlbumsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoAlbumsController.cs index 8d13f66b99..bc40b8e1c0 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoAlbumsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoAlbumsController.cs @@ -8,8 +8,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Links { public sealed class PhotoAlbumsController : JsonApiController { - public PhotoAlbumsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public PhotoAlbumsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocationsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocationsController.cs index 1398ee84b1..0da524de7f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocationsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocationsController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Links { public sealed class PhotoLocationsController : JsonApiController { - public PhotoLocationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public PhotoLocationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotosController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotosController.cs index e0dcb9a316..b7b2660b6d 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotosController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotosController.cs @@ -8,8 +8,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Links { public sealed class PhotosController : JsonApiController { - public PhotosController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public PhotosController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/RelativeLinksWithNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/RelativeLinksWithNamespaceTests.cs index 7dbd40ae0f..91fa367b04 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/RelativeLinksWithNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/RelativeLinksWithNamespaceTests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -27,7 +28,7 @@ public RelativeLinksWithNamespaceTests(ExampleIntegrationTestContext), typeof(NeverSameResourceChangeTracker<>)); }); - var options = (JsonApiOptions) testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.IncludeTotalResourceCount = true; } @@ -35,7 +36,7 @@ public RelativeLinksWithNamespaceTests(ExampleIntegrationTestContext { @@ -43,10 +44,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/api/photoAlbums/" + album.StringId; + string route = "/api/photoAlbums/" + album.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -68,7 +69,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_primary_resources_with_include_returns_relative_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -81,7 +82,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/api/photoAlbums?include=photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -112,7 +113,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_secondary_resource_returns_relative_links() { // Arrange - var photo = _fakers.Photo.Generate(); + Photo photo = _fakers.Photo.Generate(); photo.Album = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -121,10 +122,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/api/photos/{photo.StringId}/album"; + string route = $"/api/photos/{photo.StringId}/album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -148,7 +149,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_secondary_resources_returns_relative_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -157,10 +158,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/api/photoAlbums/{album.StringId}/photos"; + string route = $"/api/photoAlbums/{album.StringId}/photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -184,7 +185,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_HasOne_relationship_returns_relative_links() { // Arrange - var photo = _fakers.Photo.Generate(); + Photo photo = _fakers.Photo.Generate(); photo.Album = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -193,10 +194,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/api/photos/{photo.StringId}/relationships/album"; + string route = $"/api/photos/{photo.StringId}/relationships/album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -217,7 +218,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_HasMany_relationship_returns_relative_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -226,10 +227,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/api/photoAlbums/{album.StringId}/relationships/photos"; + string route = $"/api/photoAlbums/{album.StringId}/relationships/photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -250,7 +251,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Create_resource_with_side_effects_and_include_returns_relative_links() { // Arrange - var existingPhoto = _fakers.Photo.Generate(); + Photo existingPhoto = _fakers.Photo.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -283,7 +284,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/api/photoAlbums?include=photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -313,8 +314,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Update_resource_with_side_effects_and_include_returns_relative_links() { // Arrange - var existingPhoto = _fakers.Photo.Generate(); - var existingAlbum = _fakers.PhotoAlbum.Generate(); + Photo existingPhoto = _fakers.Photo.Generate(); + PhotoAlbum existingAlbum = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -342,10 +343,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = $"/api/photos/{existingPhoto.StringId}?include=album"; + string route = $"/api/photos/{existingPhoto.StringId}?include=album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/RelativeLinksWithoutNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/RelativeLinksWithoutNamespaceTests.cs index b42f2b8c1b..13acd8c735 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/RelativeLinksWithoutNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/RelativeLinksWithoutNamespaceTests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -27,7 +28,7 @@ public RelativeLinksWithoutNamespaceTests(ExampleIntegrationTestContext), typeof(NeverSameResourceChangeTracker<>)); }); - var options = (JsonApiOptions) testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.IncludeTotalResourceCount = true; } @@ -35,7 +36,7 @@ public RelativeLinksWithoutNamespaceTests(ExampleIntegrationTestContext { @@ -43,10 +44,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/photoAlbums/" + album.StringId; + string route = "/photoAlbums/" + album.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -68,7 +69,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_primary_resources_with_include_returns_relative_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -81,7 +82,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/photoAlbums?include=photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -112,7 +113,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_secondary_resource_returns_relative_links() { // Arrange - var photo = _fakers.Photo.Generate(); + Photo photo = _fakers.Photo.Generate(); photo.Album = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -121,10 +122,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/photos/{photo.StringId}/album"; + string route = $"/photos/{photo.StringId}/album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -148,7 +149,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_secondary_resources_returns_relative_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -157,10 +158,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/photoAlbums/{album.StringId}/photos"; + string route = $"/photoAlbums/{album.StringId}/photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -184,7 +185,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_HasOne_relationship_returns_relative_links() { // Arrange - var photo = _fakers.Photo.Generate(); + Photo photo = _fakers.Photo.Generate(); photo.Album = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -193,10 +194,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/photos/{photo.StringId}/relationships/album"; + string route = $"/photos/{photo.StringId}/relationships/album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -217,7 +218,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Get_HasMany_relationship_returns_relative_links() { // Arrange - var album = _fakers.PhotoAlbum.Generate(); + PhotoAlbum album = _fakers.PhotoAlbum.Generate(); album.Photos = _fakers.Photo.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -226,10 +227,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/photoAlbums/{album.StringId}/relationships/photos"; + string route = $"/photoAlbums/{album.StringId}/relationships/photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -250,7 +251,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Create_resource_with_side_effects_and_include_returns_relative_links() { // Arrange - var existingPhoto = _fakers.Photo.Generate(); + Photo existingPhoto = _fakers.Photo.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -283,7 +284,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/photoAlbums?include=photos"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -313,8 +314,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Update_resource_with_side_effects_and_include_returns_relative_links() { // Arrange - var existingPhoto = _fakers.Photo.Generate(); - var existingAlbum = _fakers.PhotoAlbum.Generate(); + Photo existingPhoto = _fakers.Photo.Generate(); + PhotoAlbum existingAlbum = _fakers.PhotoAlbum.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -342,10 +343,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = $"/photos/{existingPhoto.StringId}?include=album"; + string route = $"/photos/{existingPhoto.StringId}?include=album"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/AuditEntriesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/AuditEntriesController.cs index 5a1e4d74e7..b825a3fb0b 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/AuditEntriesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/AuditEntriesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Logging { public sealed class AuditEntriesController : JsonApiController { - public AuditEntriesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public AuditEntriesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/LoggingTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/LoggingTests.cs index 44ba7be6c4..a69839c4f1 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/LoggingTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/LoggingTests.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCoreExampleTests.Startups; @@ -10,8 +11,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Logging { - public sealed class LoggingTests - : IClassFixture, AuditDbContext>> + public sealed class LoggingTests : IClassFixture, AuditDbContext>> { private readonly ExampleIntegrationTestContext, AuditDbContext> _testContext; private readonly AuditFakers _fakers = new AuditFakers(); @@ -48,7 +48,7 @@ public async Task Logs_request_body_at_Trace_level() var loggerFactory = _testContext.Factory.Services.GetRequiredService(); loggerFactory.Logger.Clear(); - var newEntry = _fakers.AuditEntry.Generate(); + AuditEntry newEntry = _fakers.AuditEntry.Generate(); var requestBody = new { @@ -67,7 +67,7 @@ public async Task Logs_request_body_at_Trace_level() const string route = "/auditEntries"; // Act - var (httpResponse, _) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, string _) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -89,14 +89,14 @@ public async Task Logs_response_body_at_Trace_level() const string route = "/auditEntries"; // Act - var (httpResponse, _) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, string _) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); loggerFactory.Logger.Messages.Should().NotBeEmpty(); - loggerFactory.Logger.Messages.Should().ContainSingle(message => message.LogLevel == LogLevel.Trace && + loggerFactory.Logger.Messages.Should().ContainSingle(message => message.LogLevel == LogLevel.Trace && message.Text.StartsWith("Sending 200 response for request at 'http://localhost/auditEntries' with body: <<", StringComparison.Ordinal)); } @@ -113,7 +113,7 @@ public async Task Logs_invalid_request_body_error_at_Information_level() const string route = "/auditEntries"; // Act - var (httpResponse, _) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, string _) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ProductFamiliesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ProductFamiliesController.cs index 94b827c2f7..a276769c63 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ProductFamiliesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ProductFamiliesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Meta { public sealed class ProductFamiliesController : JsonApiController { - public ProductFamiliesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public ProductFamiliesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs index 951f752d20..c47ab9aaa2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Resources; @@ -10,8 +12,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Meta { - public sealed class ResourceMetaTests - : IClassFixture, SupportDbContext>> + public sealed class ResourceMetaTests : IClassFixture, SupportDbContext>> { private readonly ExampleIntegrationTestContext, SupportDbContext> _testContext; private readonly SupportFakers _fakers = new SupportFakers(); @@ -30,7 +31,7 @@ public ResourceMetaTests(ExampleIntegrationTestContext tickets = _fakers.SupportTicket.Generate(3); tickets[0].Description = "Critical: " + tickets[0].Description; tickets[2].Description = "Critical: " + tickets[2].Description; @@ -44,7 +45,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/supportTickets"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -59,7 +60,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Returns_resource_meta_from_ResourceDefinition_in_included_resources() { // Arrange - var family = _fakers.ProductFamily.Generate(); + ProductFamily family = _fakers.ProductFamily.Generate(); family.Tickets = _fakers.SupportTicket.Generate(1); family.Tickets[0].Description = "Critical: " + family.Tickets[0].Description; @@ -70,10 +71,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/productFamilies/{family.StringId}?include=tickets"; + string route = $"/productFamilies/{family.StringId}?include=tickets"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ResponseMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ResponseMetaTests.cs index d98fd84e8d..5edbc027ee 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ResponseMetaTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/ResponseMetaTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -10,8 +11,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Meta { - public sealed class ResponseMetaTests - : IClassFixture, SupportDbContext>> + public sealed class ResponseMetaTests : IClassFixture, SupportDbContext>> { private readonly ExampleIntegrationTestContext, SupportDbContext> _testContext; @@ -24,7 +24,7 @@ public ResponseMetaTests(ExampleIntegrationTestContext(); }); - var options = (JsonApiOptions) testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.IncludeTotalResourceCount = false; } @@ -40,7 +40,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/supportTickets"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs index 1f3d1be0a2..53304cb09f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs @@ -9,7 +9,8 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Meta [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class SupportTicketDefinition : JsonApiResourceDefinition { - public SupportTicketDefinition(IResourceGraph resourceGraph) : base(resourceGraph) + public SupportTicketDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) { } @@ -22,7 +23,7 @@ public override IDictionary GetMeta(SupportTicket resource) ["hasHighPriority"] = true }; } - + return base.GetMeta(resource); } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportTicketsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportTicketsController.cs index ad155dc489..a08e11371c 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportTicketsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportTicketsController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Meta { public sealed class SupportTicketsController : JsonApiController { - public SupportTicketsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public SupportTicketsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs index e2aa06abab..15df5c055c 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -11,8 +12,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Meta { - public sealed class TopLevelCountTests - : IClassFixture, SupportDbContext>> + public sealed class TopLevelCountTests : IClassFixture, SupportDbContext>> { private readonly ExampleIntegrationTestContext, SupportDbContext> _testContext; private readonly SupportFakers _fakers = new SupportFakers(); @@ -26,7 +26,7 @@ public TopLevelCountTests(ExampleIntegrationTestContext), typeof(NeverSameResourceChangeTracker<>)); }); - var options = (JsonApiOptions) testContext.Factory.Services.GetRequiredService(); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.IncludeTotalResourceCount = true; } @@ -34,7 +34,7 @@ public TopLevelCountTests(ExampleIntegrationTestContext { @@ -46,7 +46,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/supportTickets"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -67,7 +67,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/supportTickets"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -80,7 +80,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Hides_resource_count_in_create_resource_response() { // Arrange - var newDescription = _fakers.SupportTicket.Generate().Description; + string newDescription = _fakers.SupportTicket.Generate().Description; var requestBody = new { @@ -97,7 +97,7 @@ public async Task Hides_resource_count_in_create_resource_response() const string route = "/supportTickets"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -109,9 +109,9 @@ public async Task Hides_resource_count_in_create_resource_response() public async Task Hides_resource_count_in_update_resource_response() { // Arrange - var existingTicket = _fakers.SupportTicket.Generate(); + SupportTicket existingTicket = _fakers.SupportTicket.Generate(); - var newDescription = _fakers.SupportTicket.Generate().Description; + string newDescription = _fakers.SupportTicket.Generate().Description; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -132,10 +132,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = "/supportTickets/" + existingTicket.StringId; + string route = "/supportTickets/" + existingTicket.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateDbContext.cs index 6de2695ae9..c130f596b6 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateDbContext.cs @@ -11,7 +11,8 @@ public sealed class ModelStateDbContext : DbContext public DbSet Directories { get; set; } public DbSet Files { get; set; } - public ModelStateDbContext(DbContextOptions options) : base(options) + public ModelStateDbContext(DbContextOptions options) + : base(options) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateValidationTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateValidationTests.cs index 337caf9c9a..84c3bc694c 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateValidationTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateValidationTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -9,7 +10,8 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ModelStateValidation { - public sealed class ModelStateValidationTests : IClassFixture, ModelStateDbContext>> + public sealed class ModelStateValidationTests + : IClassFixture, ModelStateDbContext>> { private readonly ExampleIntegrationTestContext, ModelStateDbContext> _testContext; @@ -37,14 +39,14 @@ public async Task Cannot_create_resource_with_omitted_required_attribute() const string route = "/systemDirectories"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Input validation failed."); error.Detail.Should().Be("The Name field is required."); @@ -62,7 +64,7 @@ public async Task Cannot_create_resource_with_null_for_required_attribute_value( type = "systemDirectories", attributes = new { - name = (string) null, + name = (string)null, isCaseSensitive = true } } @@ -71,14 +73,14 @@ public async Task Cannot_create_resource_with_null_for_required_attribute_value( const string route = "/systemDirectories"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Input validation failed."); error.Detail.Should().Be("The Name field is required."); @@ -105,14 +107,14 @@ public async Task Cannot_create_resource_with_invalid_attribute_value() const string route = "/systemDirectories"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Input validation failed."); error.Detail.Should().Be("The field Name must match the regular expression '^[\\w\\s]+$'."); @@ -139,7 +141,7 @@ public async Task Can_create_resource_with_valid_attribute_value() const string route = "/systemDirectories"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -168,26 +170,26 @@ public async Task Cannot_create_resource_with_multiple_violations() const string route = "/systemDirectories"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(3); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error1.Title.Should().Be("Input validation failed."); error1.Detail.Should().Be("The Name field is required."); error1.Source.Pointer.Should().Be("/data/attributes/name"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error2.Title.Should().Be("Input validation failed."); error2.Detail.Should().Be("The field SizeInBytes must be between 0 and 9223372036854775807."); error2.Source.Pointer.Should().Be("/data/attributes/sizeInBytes"); - var error3 = responseDocument.Errors[2]; + Error error3 = responseDocument.Errors[2]; error3.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error3.Title.Should().Be("Input validation failed."); error3.Detail.Should().Be("The IsCaseSensitive field is required."); @@ -272,7 +274,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/systemDirectories"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -288,7 +290,7 @@ public async Task Can_add_to_annotated_ToMany_relationship() // Arrange var directory = new SystemDirectory { - Name="Projects", + Name = "Projects", IsCaseSensitive = true }; @@ -319,7 +321,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = $"/systemDirectories/{directory.StringId}/relationships/files"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -359,7 +361,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -391,7 +393,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => id = directory.StringId, attributes = new { - name = (string) null + name = (string)null } } }; @@ -399,14 +401,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Input validation failed."); error.Detail.Should().Be("The Name field is required."); @@ -445,14 +447,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Input validation failed."); error.Detail.Should().Be("The field Name must match the regular expression '^[\\w\\s]+$'."); @@ -505,20 +507,20 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/systemDirectories/-1"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(2); - var error1 = responseDocument.Errors[0]; + Error error1 = responseDocument.Errors[0]; error1.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error1.Title.Should().Be("Input validation failed."); error1.Detail.Should().Be("The field Id must match the regular expression '^[0-9]+$'."); error1.Source.Pointer.Should().Be("/data/attributes/id"); - var error2 = responseDocument.Errors[1]; + Error error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error2.Title.Should().Be("Input validation failed."); error2.Detail.Should().Be("The field Id must match the regular expression '^[0-9]+$'."); @@ -557,7 +559,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -668,7 +670,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -727,7 +729,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -781,7 +783,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -828,7 +830,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId + "/relationships/parent"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -884,7 +886,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId + "/relationships/files"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); @@ -898,7 +900,7 @@ public async Task Can_remove_from_annotated_ToMany_relationship() // Arrange var directory = new SystemDirectory { - Name="Projects", + Name = "Projects", IsCaseSensitive = true, Files = new List { @@ -924,7 +926,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = $"/systemDirectories/{directory.StringId}/relationships/files"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/NoModelStateValidationTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/NoModelStateValidationTests.cs index 1780d32aba..56e600e6e0 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/NoModelStateValidationTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/NoModelStateValidationTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -37,7 +38,7 @@ public async Task Can_create_resource_with_invalid_attribute_value() const string route = "/systemDirectories"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -78,7 +79,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string route = "/systemDirectories/" + directory.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectoriesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectoriesController.cs index 5228903c5d..63ee816e0d 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectoriesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectoriesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ModelStateValidation { public sealed class SystemDirectoriesController : JsonApiController { - public SystemDirectoriesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public SystemDirectoriesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectory.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectory.cs index f78848863a..d291370ae6 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectory.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectory.cs @@ -26,10 +26,10 @@ public sealed class SystemDirectory : Identifiable [Range(typeof(long), "0", "9223372036854775807")] public long SizeInBytes { get; set; } - [HasMany] + [HasMany] public ICollection Subdirectories { get; set; } - [HasMany] + [HasMany] public ICollection Files { get; set; } [HasOne] @@ -38,7 +38,7 @@ public sealed class SystemDirectory : Identifiable [HasOne] public SystemDirectory AlsoSelf { get; set; } - [HasOne] + [HasOne] public SystemDirectory Parent { get; set; } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemFilesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemFilesController.cs index 9278f59766..0a18e248f1 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemFilesController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemFilesController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ModelStateValidation { public sealed class SystemFilesController : JsonApiController { - public SystemFilesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public SystemFilesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/DivingBoardsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/DivingBoardsController.cs index 826249bb22..0e055f84b0 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/DivingBoardsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/DivingBoardsController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.NamingConventions { public sealed class DivingBoardsController : JsonApiController { - public DivingBoardsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public DivingBoardsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/KebabCasingTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/KebabCasingTests.cs index 91efaed905..1f96742ed1 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/KebabCasingTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/KebabCasingTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; @@ -8,8 +9,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.NamingConventions { - public sealed class KebabCasingTests - : IClassFixture, SwimmingDbContext>> + public sealed class KebabCasingTests : IClassFixture, SwimmingDbContext>> { private readonly ExampleIntegrationTestContext, SwimmingDbContext> _testContext; private readonly SwimmingFakers _fakers = new SwimmingFakers(); @@ -23,7 +23,7 @@ public KebabCasingTests(ExampleIntegrationTestContext pools = _fakers.SwimmingPool.Generate(2); pools[1].DivingBoards = _fakers.DivingBoard.Generate(1); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -36,7 +36,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => const string route = "/public-api/swimming-pools?include=diving-boards"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -61,7 +61,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_filter_secondary_resources_with_sparse_fieldset() { // Arrange - var pool = _fakers.SwimmingPool.Generate(); + SwimmingPool pool = _fakers.SwimmingPool.Generate(); pool.WaterSlides = _fakers.WaterSlide.Generate(2); pool.WaterSlides[0].LengthInMeters = 1; pool.WaterSlides[1].LengthInMeters = 5; @@ -72,11 +72,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = $"/public-api/swimming-pools/{pool.StringId}/water-slides" + + string route = $"/public-api/swimming-pools/{pool.StringId}/water-slides" + "?filter=greaterThan(length-in-meters,'1')&fields[water-slides]=length-in-meters"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -91,7 +91,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource() { // Arrange - var newPool = _fakers.SwimmingPool.Generate(); + SwimmingPool newPool = _fakers.SwimmingPool.Generate(); var requestBody = new { @@ -108,7 +108,7 @@ public async Task Can_create_resource() const string route = "/public-api/swimming-pools"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); @@ -117,7 +117,7 @@ public async Task Can_create_resource() responseDocument.SingleData.Type.Should().Be("swimming-pools"); responseDocument.SingleData.Attributes["is-indoor"].Should().Be(newPool.IsIndoor); - var newPoolId = int.Parse(responseDocument.SingleData.Id); + int newPoolId = int.Parse(responseDocument.SingleData.Id); string poolLink = route + $"/{newPoolId}"; responseDocument.SingleData.Relationships.Should().NotBeEmpty(); @@ -128,7 +128,7 @@ public async Task Can_create_resource() await _testContext.RunOnDatabaseAsync(async dbContext => { - var poolInDatabase = await dbContext.SwimmingPools.FirstWithIdAsync(newPoolId); + SwimmingPool poolInDatabase = await dbContext.SwimmingPools.FirstWithIdAsync(newPoolId); poolInDatabase.IsIndoor.Should().Be(newPool.IsIndoor); }); @@ -143,14 +143,14 @@ public async Task Applies_casing_convention_on_error_stack_trace() const string route = "/public-api/swimming-pools"; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Failed to deserialize request body."); error.Meta.Data.Should().ContainKey("stack-trace"); @@ -160,7 +160,7 @@ public async Task Applies_casing_convention_on_error_stack_trace() public async Task Applies_casing_convention_on_source_pointer_from_ModelState() { // Arrange - var existingBoard = _fakers.DivingBoard.Generate(); + DivingBoard existingBoard = _fakers.DivingBoard.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -181,17 +181,17 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - var route = "/public-api/diving-boards/" + existingBoard.StringId; + string route = "/public-api/diving-boards/" + existingBoard.StringId; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); responseDocument.Errors.Should().HaveCount(1); - var error = responseDocument.Errors[0]; + Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error.Title.Should().Be("Input validation failed."); error.Detail.Should().Be("The field HeightInMeters must be between 1 and 20."); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/SwimmingPoolsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/SwimmingPoolsController.cs index 6af99a7ea4..760227f2ff 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/SwimmingPoolsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/SwimmingPoolsController.cs @@ -7,8 +7,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.NamingConventions { public sealed class SwimmingPoolsController : JsonApiController { - public SwimmingPoolsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public SwimmingPoolsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NonJsonApiControllers/NonJsonApiControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NonJsonApiControllers/NonJsonApiControllerTests.cs index db76e954ab..19934d7fc3 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NonJsonApiControllers/NonJsonApiControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NonJsonApiControllers/NonJsonApiControllerTests.cs @@ -25,10 +25,10 @@ public async Task Get_skips_middleware_and_formatters() // Arrange using var request = new HttpRequestMessage(HttpMethod.Get, "/NonJsonApi"); - var client = _factory.CreateClient(); + HttpClient client = _factory.CreateClient(); // Act - var httpResponse = await client.SendAsync(request); + HttpResponseMessage httpResponse = await client.SendAsync(request); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -53,10 +53,10 @@ public async Task Post_skips_middleware_and_formatters() } }; - var client = _factory.CreateClient(); + HttpClient client = _factory.CreateClient(); // Act - var httpResponse = await client.SendAsync(request); + HttpResponseMessage httpResponse = await client.SendAsync(request); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -72,10 +72,10 @@ public async Task Post_skips_error_handler() // Arrange using var request = new HttpRequestMessage(HttpMethod.Post, "/NonJsonApi"); - var client = _factory.CreateClient(); + HttpClient client = _factory.CreateClient(); // Act - var httpResponse = await client.SendAsync(request); + HttpResponseMessage httpResponse = await client.SendAsync(request); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); @@ -100,10 +100,10 @@ public async Task Put_skips_middleware_and_formatters() } }; - var client = _factory.CreateClient(); + HttpClient client = _factory.CreateClient(); // Act - var httpResponse = await client.SendAsync(request); + HttpResponseMessage httpResponse = await client.SendAsync(request); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -119,10 +119,10 @@ public async Task Patch_skips_middleware_and_formatters() // Arrange using var request = new HttpRequestMessage(HttpMethod.Patch, "/NonJsonApi?name=Janice"); - var client = _factory.CreateClient(); + HttpClient client = _factory.CreateClient(); // Act - var httpResponse = await client.SendAsync(request); + HttpResponseMessage httpResponse = await client.SendAsync(request); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); @@ -138,10 +138,10 @@ public async Task Delete_skips_middleware_and_formatters() // Arrange using var request = new HttpRequestMessage(HttpMethod.Delete, "/NonJsonApi"); - var client = _factory.CreateClient(); + HttpClient client = _factory.CreateClient(); // Act - var httpResponse = await client.SendAsync(request); + HttpResponseMessage httpResponse = await client.SendAsync(request); // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/Blog.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/Blog.cs index 2edbb10c99..6bcd37d005 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/Blog.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/Blog.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.QueryStrings [UsedImplicitly(ImplicitUseTargetFlags.Members)] public sealed class Blog : Identifiable { - [Attr] + [Attr] public string Title { get; set; } [Attr] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/BlogPost.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/BlogPost.cs index dea9789436..8805bda98a 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/BlogPost.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/BlogPost.cs @@ -24,6 +24,7 @@ public sealed class BlogPost : Identifiable [NotMapped] [HasManyThrough(nameof(BlogPostLabels))] public ISet