Skip to content

Commit 36bc489

Browse files
author
Bart Koelman
authored
Merge pull request #731 from bart-degreed/various-fixes
Various fixes
2 parents c259150 + 4834612 commit 36bc489

File tree

172 files changed

+3414
-2022
lines changed

Some content is hidden

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

172 files changed

+3414
-2022
lines changed

benchmarks/DependencyFactory.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using JsonApiDotNetCore.Internal.Contracts;
55
using JsonApiDotNetCore.Models;
66
using JsonApiDotNetCore.Query;
7+
using Microsoft.Extensions.Logging.Abstractions;
78
using Moq;
89

910
namespace Benchmarks
@@ -12,7 +13,7 @@ internal static class DependencyFactory
1213
{
1314
public static IResourceGraph CreateResourceGraph(IJsonApiOptions options)
1415
{
15-
IResourceGraphBuilder builder = new ResourceGraphBuilder(options);
16+
IResourceGraphBuilder builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance);
1617
builder.AddResource<BenchmarkResource>(BenchmarkResourcePublicNames.Type);
1718
return builder.Build();
1819
}

benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.ComponentModel.Design;
34
using BenchmarkDotNet.Attributes;
45
using JsonApiDotNetCore.Configuration;
6+
using JsonApiDotNetCore.Internal;
57
using JsonApiDotNetCore.Internal.Contracts;
68
using JsonApiDotNetCore.Models;
79
using JsonApiDotNetCore.Serialization;
@@ -37,7 +39,7 @@ public JsonApiDeserializerBenchmarks()
3739
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
3840
var targetedFields = new TargetedFields();
3941

40-
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, targetedFields);
42+
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new DefaultResourceFactory(new ServiceContainer()), targetedFields);
4143
}
4244

4345
[Benchmark]

benchmarks/Serialization/JsonApiSerializerBenchmarks.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using BenchmarkDotNet.Attributes;
33
using JsonApiDotNetCore.Configuration;
4-
using JsonApiDotNetCore.Graph;
54
using JsonApiDotNetCore.Internal.Contracts;
65
using JsonApiDotNetCore.Managers;
76
using JsonApiDotNetCore.Query;

src/Examples/GettingStarted/Models/Person.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ public sealed class Person : Identifiable
77
{
88
[Attr]
99
public string Name { get; set; }
10+
1011
[HasMany]
11-
public List<Article> Articles { get; set; }
12+
public ICollection<Article> Articles { get; set; }
1213
}
1314
}

src/Examples/GettingStarted/Startup.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Microsoft.AspNetCore.Builder;
22
using Microsoft.Extensions.DependencyInjection;
33
using Microsoft.EntityFrameworkCore;
4-
using JsonApiDotNetCore.Extensions;
4+
using JsonApiDotNetCore;
55

66
namespace GettingStarted
77
{
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,50 @@
1-
using JsonApiDotNetCore.Configuration;
1+
using System.Threading.Tasks;
2+
using JsonApiDotNetCore.Configuration;
23
using JsonApiDotNetCore.Controllers;
34
using JsonApiDotNetCore.Services;
45
using JsonApiDotNetCoreExample.Models;
6+
using Microsoft.AspNetCore.Mvc;
57
using Microsoft.Extensions.Logging;
68

79
namespace JsonApiDotNetCoreExample.Controllers
810
{
9-
public sealed class PassportsController : JsonApiController<Passport>
11+
public sealed class PassportsController : BaseJsonApiController<Passport>
1012
{
1113
public PassportsController(
1214
IJsonApiOptions jsonApiOptions,
1315
ILoggerFactory loggerFactory,
1416
IResourceService<Passport, int> resourceService)
1517
: base(jsonApiOptions, loggerFactory, resourceService)
1618
{ }
19+
20+
[HttpGet]
21+
public override async Task<IActionResult> GetAsync() => await base.GetAsync();
22+
23+
[HttpGet("{id}")]
24+
public async Task<IActionResult> GetAsync(string id)
25+
{
26+
int idValue = HexadecimalObfuscationCodec.Decode(id);
27+
return await base.GetAsync(idValue);
28+
}
29+
30+
[HttpPatch("{id}")]
31+
public async Task<IActionResult> PatchAsync(string id, [FromBody] Passport entity)
32+
{
33+
int idValue = HexadecimalObfuscationCodec.Decode(id);
34+
return await base.PatchAsync(idValue, entity);
35+
}
36+
37+
[HttpPost]
38+
public override async Task<IActionResult> PostAsync([FromBody] Passport entity)
39+
{
40+
return await base.PostAsync(entity);
41+
}
42+
43+
[HttpDelete("{id}")]
44+
public async Task<IActionResult> DeleteAsync(string id)
45+
{
46+
int idValue = HexadecimalObfuscationCodec.Decode(id);
47+
return await base.DeleteAsync(idValue);
48+
}
1749
}
1850
}

src/Examples/JsonApiDotNetCoreExample/Controllers/TestValuesController.cs

+20
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,25 @@ public IActionResult Get()
1111
var result = new[] { "value" };
1212
return Ok(result);
1313
}
14+
15+
[HttpPost]
16+
public IActionResult Post(string name)
17+
{
18+
var result = "Hello, " + name;
19+
return Ok(result);
20+
}
21+
22+
[HttpPatch]
23+
public IActionResult Patch(string name)
24+
{
25+
var result = "Hello, " + name;
26+
return Ok(result);
27+
}
28+
29+
[HttpDelete]
30+
public IActionResult Delete()
31+
{
32+
return Ok("Deleted");
33+
}
1434
}
1535
}

src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,35 @@
1+
using System;
12
using JsonApiDotNetCoreExample.Models;
3+
using Microsoft.AspNetCore.Authentication;
24
using Microsoft.EntityFrameworkCore;
35

46
namespace JsonApiDotNetCoreExample.Data
57
{
68
public sealed class AppDbContext : DbContext
79
{
10+
public ISystemClock SystemClock { get; }
11+
812
public DbSet<TodoItem> TodoItems { get; set; }
913
public DbSet<Passport> Passports { get; set; }
1014
public DbSet<Person> People { get; set; }
1115
public DbSet<TodoItemCollection> TodoItemCollections { get; set; }
1216
public DbSet<KebabCasedModel> KebabCasedModels { get; set; }
1317
public DbSet<Article> Articles { get; set; }
1418
public DbSet<Author> AuthorDifferentDbContextName { get; set; }
15-
public DbSet<NonJsonApiResource> NonJsonApiResources { get; set; }
1619
public DbSet<User> Users { get; set; }
17-
public DbSet<SuperUser> SuperUsers { get; set; }
1820
public DbSet<PersonRole> PersonRoles { get; set; }
1921
public DbSet<ArticleTag> ArticleTags { get; set; }
20-
public DbSet<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
2122
public DbSet<Tag> Tags { get; set; }
22-
public DbSet<ThrowingResource> ThrowingResources { get; set; }
2323

24-
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
24+
public AppDbContext(DbContextOptions<AppDbContext> options, ISystemClock systemClock) : base(options)
25+
{
26+
SystemClock = systemClock ?? throw new ArgumentNullException(nameof(systemClock));
27+
}
2528

2629
protected override void OnModelCreating(ModelBuilder modelBuilder)
2730
{
31+
modelBuilder.Entity<ThrowingResource>();
32+
2833
modelBuilder.Entity<SuperUser>().HasBaseType<User>();
2934

3035
modelBuilder.Entity<TodoItem>()
@@ -66,6 +71,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
6671
.HasForeignKey<Person>(p => p.PassportId)
6772
.OnDelete(DeleteBehavior.SetNull);
6873

74+
modelBuilder.Entity<Passport>()
75+
.HasMany(passport => passport.GrantedVisas)
76+
.WithOne()
77+
.OnDelete(DeleteBehavior.Cascade);
78+
6979
modelBuilder.Entity<TodoItem>()
7080
.HasOne(p => p.OneToOnePerson)
7181
.WithOne(p => p.OneToOneTodoItem)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Text;
5+
6+
namespace JsonApiDotNetCoreExample
7+
{
8+
public static class HexadecimalObfuscationCodec
9+
{
10+
public static int Decode(string value)
11+
{
12+
if (string.IsNullOrEmpty(value))
13+
{
14+
return 0;
15+
}
16+
17+
if (!value.StartsWith("x"))
18+
{
19+
throw new InvalidOperationException("Invalid obfuscated id.");
20+
}
21+
22+
string stringValue = FromHexString(value.Substring(1));
23+
return int.Parse(stringValue);
24+
}
25+
26+
private static string FromHexString(string hexString)
27+
{
28+
List<byte> bytes = new List<byte>(hexString.Length / 2);
29+
for (int index = 0; index < hexString.Length; index += 2)
30+
{
31+
var hexChar = hexString.Substring(index, 2);
32+
byte bt = byte.Parse(hexChar, NumberStyles.HexNumber);
33+
bytes.Add(bt);
34+
}
35+
36+
var chars = Encoding.ASCII.GetChars(bytes.ToArray());
37+
return new string(chars);
38+
}
39+
40+
public static string Encode(object value)
41+
{
42+
if (value is int intValue && intValue == 0)
43+
{
44+
return string.Empty;
45+
}
46+
47+
string stringValue = value.ToString();
48+
return 'x' + ToHexString(stringValue);
49+
}
50+
51+
private static string ToHexString(string value)
52+
{
53+
var builder = new StringBuilder();
54+
55+
foreach (byte bt in Encoding.ASCII.GetBytes(value))
56+
{
57+
builder.Append(bt.ToString("X2"));
58+
}
59+
60+
return builder.ToString();
61+
}
62+
}
63+
}

src/Examples/JsonApiDotNetCoreExample/Models/Article.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@ public sealed class Article : Identifiable
1515

1616
[NotMapped]
1717
[HasManyThrough(nameof(ArticleTags))]
18-
public List<Tag> Tags { get; set; }
19-
public List<ArticleTag> ArticleTags { get; set; }
20-
18+
public ISet<Tag> Tags { get; set; }
19+
public ISet<ArticleTag> ArticleTags { get; set; }
2120

2221
[NotMapped]
2322
[HasManyThrough(nameof(IdentifiableArticleTags))]
24-
public List<Tag> IdentifiableTags { get; set; }
25-
public List<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
23+
public ICollection<Tag> IdentifiableTags { get; set; }
24+
public ICollection<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
2625
}
2726
}

src/Examples/JsonApiDotNetCoreExample/Models/ArticleTag.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System;
12
using JsonApiDotNetCore.Models;
3+
using JsonApiDotNetCoreExample.Data;
24

35
namespace JsonApiDotNetCoreExample.Models
46
{
@@ -9,8 +11,12 @@ public sealed class ArticleTag
911

1012
public int TagId { get; set; }
1113
public Tag Tag { get; set; }
12-
}
1314

15+
public ArticleTag(AppDbContext appDbContext)
16+
{
17+
if (appDbContext == null) throw new ArgumentNullException(nameof(appDbContext));
18+
}
19+
}
1420

1521
public class IdentifiableArticleTag : Identifiable
1622
{

src/Examples/JsonApiDotNetCoreExample/Models/Author.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public sealed class Author : Identifiable
99
public string Name { get; set; }
1010

1111
[HasMany]
12-
public List<Article> Articles { get; set; }
12+
public IList<Article> Articles { get; set; }
1313
}
1414
}
1515

src/Examples/JsonApiDotNetCoreExample/Models/Passport.cs

+22-7
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,26 @@
33
using System.ComponentModel.DataAnnotations.Schema;
44
using System.Linq;
55
using JsonApiDotNetCore.Models;
6+
using JsonApiDotNetCoreExample.Data;
7+
using Microsoft.AspNetCore.Authentication;
68

79
namespace JsonApiDotNetCoreExample.Models
810
{
911
public class Passport : Identifiable
1012
{
13+
private readonly ISystemClock _systemClock;
1114
private int? _socialSecurityNumber;
1215

16+
protected override string GetStringId(object value)
17+
{
18+
return HexadecimalObfuscationCodec.Encode(value);
19+
}
20+
21+
protected override int GetTypedId(string value)
22+
{
23+
return HexadecimalObfuscationCodec.Decode(value);
24+
}
25+
1326
[Attr]
1427
public int? SocialSecurityNumber
1528
{
@@ -18,7 +31,7 @@ public int? SocialSecurityNumber
1831
{
1932
if (value != _socialSecurityNumber)
2033
{
21-
LastSocialSecurityNumberChange = DateTime.Now;
34+
LastSocialSecurityNumberChange = _systemClock.UtcNow.LocalDateTime;
2235
_socialSecurityNumber = value;
2336
}
2437
}
@@ -54,14 +67,16 @@ public string BirthCountryName
5467

5568
[Attr(AttrCapabilities.All & ~AttrCapabilities.AllowMutate)]
5669
[NotMapped]
57-
public string GrantedVisaCountries
58-
{
59-
get => GrantedVisas == null ? null : string.Join(", ", GrantedVisas.Select(v => v.TargetCountry.Name));
60-
// The setter is required only for deserialization in unit tests.
61-
set { }
62-
}
70+
public string GrantedVisaCountries => GrantedVisas == null || !GrantedVisas.Any()
71+
? null
72+
: string.Join(", ", GrantedVisas.Select(v => v.TargetCountry.Name));
6373

6474
[EagerLoad]
6575
public ICollection<Visa> GrantedVisas { get; set; }
76+
77+
public Passport(AppDbContext appDbContext)
78+
{
79+
_systemClock = appDbContext.SystemClock;
80+
}
6681
}
6782
}

src/Examples/JsonApiDotNetCoreExample/Models/Person.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ public string FirstName
4444
public Gender Gender { get; set; }
4545

4646
[HasMany]
47-
public List<TodoItem> TodoItems { get; set; }
47+
public ISet<TodoItem> TodoItems { get; set; }
4848

4949
[HasMany]
50-
public List<TodoItem> AssignedTodoItems { get; set; }
50+
public ISet<TodoItem> AssignedTodoItems { get; set; }
5151

5252
[HasMany]
53-
public List<TodoItemCollection> todoCollections { get; set; }
53+
public HashSet<TodoItemCollection> todoCollections { get; set; }
5454

5555
[HasOne]
5656
public PersonRole Role { get; set; }

0 commit comments

Comments
 (0)