Skip to content

Commit a1a351a

Browse files
author
Bart Koelman
authored
Merge pull request #714 from bart-degreed/extensibility
Error handling and logging
2 parents 1610f28 + 05a9dbe commit a1a351a

File tree

174 files changed

+4000
-1778
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

174 files changed

+4000
-1778
lines changed

benchmarks/Query/QueryParserBenchmarks.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using JsonApiDotNetCore.Services;
1010
using Microsoft.AspNetCore.Http;
1111
using Microsoft.AspNetCore.WebUtilities;
12+
using Microsoft.Extensions.Logging.Abstractions;
1213

1314
namespace Benchmarks.Query
1415
{
@@ -44,7 +45,7 @@ private static QueryParameterParser CreateQueryParameterDiscoveryForSort(IResour
4445
sortService
4546
};
4647

47-
return new QueryParameterParser(options, queryStringAccessor, queryServices);
48+
return new QueryParameterParser(options, queryStringAccessor, queryServices, NullLoggerFactory.Instance);
4849
}
4950

5051
private static QueryParameterParser CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph,
@@ -65,7 +66,7 @@ private static QueryParameterParser CreateQueryParameterDiscoveryForAll(IResourc
6566
omitNullService
6667
};
6768

68-
return new QueryParameterParser(options, queryStringAccessor, queryServices);
69+
return new QueryParameterParser(options, queryStringAccessor, queryServices, NullLoggerFactory.Instance);
6970
}
7071

7172
[Benchmark]

benchmarks/Serialization/JsonApiSerializerBenchmarks.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using BenchmarkDotNet.Attributes;
3+
using JsonApiDotNetCore.Graph;
34
using JsonApiDotNetCore.Internal.Contracts;
45
using JsonApiDotNetCore.Managers;
56
using JsonApiDotNetCore.Query;
@@ -33,7 +34,7 @@ public JsonApiSerializerBenchmarks()
3334
var resourceObjectBuilder = new ResourceObjectBuilder(resourceGraph, new ResourceObjectBuilderSettings());
3435

3536
_jsonApiSerializer = new ResponseSerializer<BenchmarkResource>(metaBuilderMock.Object, linkBuilderMock.Object,
36-
includeBuilderMock.Object, fieldsToSerialize, resourceObjectBuilder);
37+
includeBuilderMock.Object, fieldsToSerialize, resourceObjectBuilder, new CamelCaseFormatter());
3738
}
3839

3940
private static FieldsToSerialize CreateFieldsToSerialize(IResourceGraph resourceGraph)

src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace JsonApiDotNetCoreExample.Controllers
88
{
9+
[DisableQuery(StandardQueryStringParameters.Sort | StandardQueryStringParameters.Page)]
910
public sealed class ArticlesController : JsonApiController<Article>
1011
{
1112
public ArticlesController(

src/Examples/JsonApiDotNetCoreExample/Controllers/TagsController.cs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace JsonApiDotNetCoreExample.Controllers
88
{
9+
[DisableQuery("skipCache")]
910
public sealed class TagsController : JsonApiController<Tag>
1011
{
1112
public TagsController(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using JsonApiDotNetCore.Configuration;
2+
using JsonApiDotNetCore.Controllers;
3+
using JsonApiDotNetCore.Services;
4+
using JsonApiDotNetCoreExample.Models;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace JsonApiDotNetCoreExample.Controllers
8+
{
9+
public sealed class ThrowingResourcesController : JsonApiController<ThrowingResource>
10+
{
11+
public ThrowingResourcesController(
12+
IJsonApiOptions jsonApiOptions,
13+
ILoggerFactory loggerFactory,
14+
IResourceService<ThrowingResource> resourceService)
15+
: base(jsonApiOptions, loggerFactory, resourceService)
16+
{ }
17+
}
18+
}

src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs

+32-26
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
using System.Collections.Generic;
2+
using System.Net;
23
using System.Threading.Tasks;
34
using JsonApiDotNetCore.Configuration;
45
using JsonApiDotNetCore.Controllers;
6+
using JsonApiDotNetCore.Exceptions;
57
using JsonApiDotNetCore.Models;
68
using JsonApiDotNetCore.Services;
79
using JsonApiDotNetCoreExample.Models;
810
using Microsoft.AspNetCore.Mvc;
9-
using Microsoft.Extensions.Logging;
1011

1112
namespace JsonApiDotNetCoreExample.Controllers
1213
{
14+
[ApiController]
1315
[DisableRoutingConvention, Route("custom/route/todoItems")]
1416
public class TodoItemsCustomController : CustomJsonApiController<TodoItem>
1517
{
1618
public TodoItemsCustomController(
1719
IJsonApiOptions options,
18-
IResourceService<TodoItem> resourceService,
19-
ILoggerFactory loggerFactory)
20-
: base(options, resourceService, loggerFactory)
20+
IResourceService<TodoItem> resourceService)
21+
: base(options, resourceService)
2122
{ }
2223
}
2324

@@ -26,8 +27,7 @@ public class CustomJsonApiController<T>
2627
{
2728
public CustomJsonApiController(
2829
IJsonApiOptions options,
29-
IResourceService<T, int> resourceService,
30-
ILoggerFactory loggerFactory)
30+
IResourceService<T, int> resourceService)
3131
: base(options, resourceService)
3232
{
3333
}
@@ -41,7 +41,7 @@ public class CustomJsonApiController<T, TId>
4141

4242
private IActionResult Forbidden()
4343
{
44-
return new StatusCodeResult(403);
44+
return new StatusCodeResult((int)HttpStatusCode.Forbidden);
4545
}
4646

4747
public CustomJsonApiController(
@@ -68,22 +68,29 @@ public async Task<IActionResult> GetAsync()
6868
[HttpGet("{id}")]
6969
public async Task<IActionResult> GetAsync(TId id)
7070
{
71-
var entity = await _resourceService.GetAsync(id);
72-
73-
if (entity == null)
71+
try
72+
{
73+
var entity = await _resourceService.GetAsync(id);
74+
return Ok(entity);
75+
}
76+
catch (ResourceNotFoundException)
77+
{
7478
return NotFound();
75-
76-
return Ok(entity);
79+
}
7780
}
7881

7982
[HttpGet("{id}/relationships/{relationshipName}")]
8083
public async Task<IActionResult> GetRelationshipsAsync(TId id, string relationshipName)
8184
{
82-
var relationship = _resourceService.GetRelationshipAsync(id, relationshipName);
83-
if (relationship == null)
85+
try
86+
{
87+
var relationship = await _resourceService.GetRelationshipsAsync(id, relationshipName);
88+
return Ok(relationship);
89+
}
90+
catch (ResourceNotFoundException)
91+
{
8492
return NotFound();
85-
86-
return await GetRelationshipAsync(id, relationshipName);
93+
}
8794
}
8895

8996
[HttpGet("{id}/{relationshipName}")]
@@ -113,12 +120,15 @@ public async Task<IActionResult> PatchAsync(TId id, [FromBody] T entity)
113120
if (entity == null)
114121
return UnprocessableEntity();
115122

116-
var updatedEntity = await _resourceService.UpdateAsync(id, entity);
117-
118-
if (updatedEntity == null)
123+
try
124+
{
125+
var updatedEntity = await _resourceService.UpdateAsync(id, entity);
126+
return Ok(updatedEntity);
127+
}
128+
catch (ResourceNotFoundException)
129+
{
119130
return NotFound();
120-
121-
return Ok(updatedEntity);
131+
}
122132
}
123133

124134
[HttpPatch("{id}/relationships/{relationshipName}")]
@@ -131,11 +141,7 @@ public async Task<IActionResult> PatchRelationshipsAsync(TId id, string relation
131141
[HttpDelete("{id}")]
132142
public async Task<IActionResult> DeleteAsync(TId id)
133143
{
134-
var wasDeleted = await _resourceService.DeleteAsync(id);
135-
136-
if (!wasDeleted)
137-
return NotFound();
138-
144+
await _resourceService.DeleteAsync(id);
139145
return NoContent();
140146
}
141147
}

src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs

+49-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
using System.Net;
2+
using System.Threading.Tasks;
13
using JsonApiDotNetCore.Configuration;
24
using JsonApiDotNetCore.Controllers;
35
using JsonApiDotNetCore.Models;
6+
using JsonApiDotNetCore.Models.JsonApiDocuments;
47
using JsonApiDotNetCore.Services;
58
using JsonApiDotNetCoreExample.Models;
69
using Microsoft.AspNetCore.Mvc;
@@ -9,7 +12,7 @@
912
namespace JsonApiDotNetCoreExample.Controllers
1013
{
1114
public abstract class AbstractTodoItemsController<T>
12-
: JsonApiController<T> where T : class, IIdentifiable<int>
15+
: BaseJsonApiController<T> where T : class, IIdentifiable<int>
1316
{
1417
protected AbstractTodoItemsController(
1518
IJsonApiOptions jsonApiOptions,
@@ -19,6 +22,7 @@ protected AbstractTodoItemsController(
1922
{ }
2023
}
2124

25+
[DisableRoutingConvention]
2226
[Route("/abstract")]
2327
public class TodoItemsTestController : AbstractTodoItemsController<TodoItem>
2428
{
@@ -28,5 +32,49 @@ public TodoItemsTestController(
2832
IResourceService<TodoItem> service)
2933
: base(jsonApiOptions, loggerFactory, service)
3034
{ }
35+
36+
[HttpGet]
37+
public override async Task<IActionResult> GetAsync() => await base.GetAsync();
38+
39+
[HttpGet("{id}")]
40+
public override async Task<IActionResult> GetAsync(int id) => await base.GetAsync(id);
41+
42+
[HttpGet("{id}/relationships/{relationshipName}")]
43+
public override async Task<IActionResult> GetRelationshipsAsync(int id, string relationshipName)
44+
=> await base.GetRelationshipsAsync(id, relationshipName);
45+
46+
[HttpGet("{id}/{relationshipName}")]
47+
public override async Task<IActionResult> GetRelationshipAsync(int id, string relationshipName)
48+
=> await base.GetRelationshipAsync(id, relationshipName);
49+
50+
[HttpPost]
51+
public override async Task<IActionResult> PostAsync(TodoItem entity)
52+
{
53+
await Task.Yield();
54+
55+
return NotFound(new Error(HttpStatusCode.NotFound)
56+
{
57+
Title = "NotFound ActionResult with explicit error object."
58+
});
59+
}
60+
61+
[HttpPatch("{id}")]
62+
public override async Task<IActionResult> PatchAsync(int id, [FromBody] TodoItem entity)
63+
{
64+
return await base.PatchAsync(id, entity);
65+
}
66+
67+
[HttpPatch("{id}/relationships/{relationshipName}")]
68+
public override async Task<IActionResult> PatchRelationshipsAsync(
69+
int id, string relationshipName, [FromBody] object relationships)
70+
=> await base.PatchRelationshipsAsync(id, relationshipName, relationships);
71+
72+
[HttpDelete("{id}")]
73+
public override async Task<IActionResult> DeleteAsync(int id)
74+
{
75+
await Task.Yield();
76+
77+
return NotFound();
78+
}
3179
}
3280
}

src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public sealed class AppDbContext : DbContext
1919
public DbSet<ArticleTag> ArticleTags { get; set; }
2020
public DbSet<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
2121
public DbSet<Tag> Tags { get; set; }
22+
public DbSet<ThrowingResource> ThrowingResources { get; set; }
2223

2324
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
2425

Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
using System.ComponentModel.DataAnnotations;
12
using JsonApiDotNetCore.Models;
23

34
namespace JsonApiDotNetCoreExample.Models
45
{
56
public class Tag : Identifiable
67
{
78
[Attr]
9+
[RegularExpression(@"^\W$")]
810
public string Name { get; set; }
911
}
10-
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
using JsonApiDotNetCore.Formatters;
5+
using JsonApiDotNetCore.Models;
6+
7+
namespace JsonApiDotNetCoreExample.Models
8+
{
9+
public sealed class ThrowingResource : Identifiable
10+
{
11+
[Attr]
12+
public string FailsOnSerialize
13+
{
14+
get
15+
{
16+
var isSerializingResponse = new StackTrace().GetFrames()
17+
.Any(frame => frame.GetMethod().DeclaringType == typeof(JsonApiWriter));
18+
19+
if (isSerializingResponse)
20+
{
21+
throw new InvalidOperationException($"The value for the '{nameof(FailsOnSerialize)}' property is currently unavailable.");
22+
}
23+
24+
return string.Empty;
25+
}
26+
set { }
27+
}
28+
}
29+
}

src/Examples/JsonApiDotNetCoreExample/Properties/launchSettings.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@
1010
"profiles": {
1111
"IIS Express": {
1212
"commandName": "IISExpress",
13-
"launchBrowser": true,
13+
"launchBrowser": false,
1414
"launchUrl": "api/values",
1515
"environmentVariables": {
1616
"ASPNETCORE_ENVIRONMENT": "Development"
1717
}
1818
},
1919
"JsonApiDotNetCoreExample": {
2020
"commandName": "Project",
21-
"launchBrowser": true,
21+
"launchBrowser": false,
2222
"launchUrl": "http://localhost:5000/api/values",
2323
"environmentVariables": {
2424
"ASPNETCORE_ENVIRONMENT": "Development"
2525
},
2626
"applicationUrl": "http://localhost:5000/"
2727
}
2828
}
29-
}
29+
}

src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using System.Collections.Generic;
22
using System.Linq;
3-
using System;
4-
using JsonApiDotNetCore.Internal;
3+
using System.Net;
4+
using JsonApiDotNetCore.Exceptions;
55
using JsonApiDotNetCore.Models;
66
using JsonApiDotNetCore.Hooks;
77
using JsonApiDotNetCoreExample.Models;
88
using JsonApiDotNetCore.Internal.Contracts;
9+
using JsonApiDotNetCore.Models.JsonApiDocuments;
910

1011
namespace JsonApiDotNetCoreExample.Resources
1112
{
@@ -17,9 +18,13 @@ public override IEnumerable<Article> OnReturn(HashSet<Article> entities, Resourc
1718
{
1819
if (pipeline == ResourcePipeline.GetSingle && entities.Single().Name == "Classified")
1920
{
20-
throw new JsonApiException(403, "You are not allowed to see this article!", new UnauthorizedAccessException());
21+
throw new JsonApiException(new Error(HttpStatusCode.Forbidden)
22+
{
23+
Title = "You are not allowed to see this article."
24+
});
2125
}
22-
return entities.Where(t => t.Name != "This should be not be included");
26+
27+
return entities.Where(t => t.Name != "This should not be included");
2328
}
2429
}
2530
}

0 commit comments

Comments
 (0)